architect-py 5.0.0b1__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 (74) hide show
  1. architect_py/__init__.py +10 -3
  2. architect_py/async_client.py +283 -169
  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 +4 -4
  52. architect_py/tests/test_orderflow.py +31 -28
  53. {architect_py-5.0.0b1.dist-info → architect_py-5.0.0b2.dist-info}/METADATA +2 -3
  54. {architect_py-5.0.0b1.dist-info → architect_py-5.0.0b2.dist-info}/RECORD +72 -42
  55. {architect_py-5.0.0b1.dist-info → architect_py-5.0.0b2.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.0b2.dist-info}/licenses/LICENSE +0 -0
  74. {architect_py-5.0.0b1.dist-info → architect_py-5.0.0b2.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,6 +90,17 @@ 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}"
@@ -318,6 +324,14 @@ class AsyncClient:
318
324
  res = await self.graphql_client.user_id_query()
319
325
  return res.user_id, res.user_email
320
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
+ )
334
+
321
335
  # ------------------------------------------------------------
322
336
  # Symbology
323
337
  # ------------------------------------------------------------
@@ -371,6 +385,10 @@ class AsyncClient:
371
385
 
372
386
  Args:
373
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
374
392
 
375
393
  Returns:
376
394
  None if the symbol does not exist
@@ -459,6 +477,13 @@ class AsyncClient:
459
477
  return None
460
478
  return res.product_info.first_notice_date
461
479
 
480
+ async def get_future_series(self, series_symbol: str) -> list[str]:
481
+ """
482
+ @deprecated(reason="Use get_futures_series instead")
483
+ """
484
+ futures = await self.get_futures_series(series_symbol)
485
+ return futures
486
+
462
487
  async def get_futures_series(self, series_symbol: str) -> list[str]:
463
488
  """
464
489
  List all futures in a given series.
@@ -503,11 +528,14 @@ class AsyncClient:
503
528
  the symbol for each future in the series
504
529
 
505
530
  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
- ...
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
+ # ...
510
537
  ]
538
+ ```
511
539
  """
512
540
  futures = await self.get_futures_series(series)
513
541
  exp_and_futures = []
@@ -573,7 +601,7 @@ class AsyncClient:
573
601
  symbol: the symbol to get the market snapshot for, e.g. "ES 20250321 CME Future/USD"
574
602
  venue: the venue that the symbol is traded at, e.g. CME
575
603
  Returns:
576
- MarketTickerFields for the symbol
604
+ L1BookSnapshot for the symbol
577
605
  """
578
606
  return await self.get_l1_book_snapshot(symbol=symbol, venue=venue)
579
607
 
@@ -591,7 +619,7 @@ class AsyncClient:
591
619
  """
592
620
  return await self.get_l1_book_snapshots(
593
621
  venue=venue,
594
- symbols=symbols, # type: ignore
622
+ symbols=symbols, # pyright: ignore[reportArgumentType]
595
623
  )
596
624
 
597
625
  @overload
@@ -634,13 +662,23 @@ class AsyncClient:
634
662
  venue: the venue of the symbol
635
663
  candle_width: the width of the candles
636
664
  start: the start date to get candles for;
637
- For naive datetimes, the server will assume UTC.
638
665
  end: the end date to get candles for;
639
- For naive datetimes, the server will assume UTC.
640
666
  as_dataframe: if True, return a pandas DataFrame
641
667
 
642
668
  """
643
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
+ )
644
682
  req = HistoricalCandlesRequest(
645
683
  venue=venue,
646
684
  symbol=str(symbol),
@@ -650,10 +688,8 @@ class AsyncClient:
650
688
  )
651
689
  res: HistoricalCandlesResponse = await grpc_client.unary_unary(req)
652
690
 
653
- if as_dataframe and FEATURE_PANDAS:
691
+ if as_dataframe:
654
692
  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
693
  else:
658
694
  return res.candles
659
695
 
@@ -686,7 +722,9 @@ class AsyncClient:
686
722
  """
687
723
  grpc_client = await self.marketdata(venue)
688
724
  req = L1BookSnapshotsRequest(symbols=symbols)
689
- res: ArrayOfL1BookSnapshot = await grpc_client.unary_unary(req) # type: ignore
725
+ res: ArrayOfL1BookSnapshot = await grpc_client.unary_unary(
726
+ req # pyright: ignore[reportArgumentType]
727
+ )
690
728
  return res
691
729
 
692
730
  async def get_l2_book_snapshot(
@@ -715,7 +753,7 @@ class AsyncClient:
715
753
 
716
754
  async def stream_l1_book_snapshots(
717
755
  self, symbols: Sequence[TradableProduct | str], venue: Venue
718
- ) -> AsyncIterator[L1BookSnapshot]:
756
+ ) -> AsyncGenerator[L1BookSnapshot, None]:
719
757
  """
720
758
  Subscribe to the stream of L1BookSnapshots for a symbol.
721
759
 
@@ -726,11 +764,12 @@ class AsyncClient:
726
764
  """
727
765
  grpc_client = await self.marketdata(venue)
728
766
  req = SubscribeL1BookSnapshotsRequest(symbols=list(symbols), venue=venue)
729
- return grpc_client.unary_stream(req)
767
+ async for res in grpc_client.unary_stream(req):
768
+ yield res
730
769
 
731
770
  async def stream_l2_book_updates(
732
771
  self, symbol: TradableProduct | str, venue: Venue
733
- ) -> AsyncIterator[L2BookUpdate]:
772
+ ) -> AsyncGenerator[L2BookUpdate, None]:
734
773
  """
735
774
  Subscribe to the stream of L2BookUpdates for a symbol.
736
775
 
@@ -743,7 +782,10 @@ class AsyncClient:
743
782
  """
744
783
  grpc_client = await self.marketdata(venue)
745
784
  req = SubscribeL2BookUpdatesRequest(symbol=str(symbol), venue=venue)
746
- return grpc_client.unary_stream(req) # type: ignore
785
+ async for res in grpc_client.unary_stream(
786
+ req # pyright: ignore[reportArgumentType]
787
+ ):
788
+ yield res
747
789
 
748
790
  async def subscribe_l1_book(
749
791
  self, symbol: TradableProduct | str, venue: Venue
@@ -782,6 +824,9 @@ class AsyncClient:
782
824
  return book
783
825
 
784
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
+ """
785
830
  symbol = TradableProduct(symbol)
786
831
  try:
787
832
  task = self.l1_books[venue][symbol][1]
@@ -865,7 +910,9 @@ class AsyncClient:
865
910
  ):
866
911
  try:
867
912
  req = SubscribeL2BookUpdatesRequest(symbol=str(symbol), venue=venue)
868
- stream = grpc_client.unary_stream(req) # type: ignore
913
+ stream = grpc_client.unary_stream(
914
+ req # pyright: ignore[reportArgumentType]
915
+ )
869
916
  async for up in stream:
870
917
  if isinstance(up, L2BookDiff):
871
918
  if (
@@ -899,20 +946,21 @@ class AsyncClient:
899
946
 
900
947
  async def stream_trades(
901
948
  self, symbol: TradableProduct | str, venue: Venue
902
- ) -> AsyncIterator[Trade]:
949
+ ) -> AsyncGenerator[Trade, None]:
903
950
  """
904
951
  Subscribe to a stream of trades for a symbol.
905
952
  """
906
953
  grpc_client = await self.marketdata(venue)
907
954
  req = SubscribeTradesRequest(symbol=str(symbol), venue=venue)
908
- return grpc_client.unary_stream(req)
955
+ async for res in grpc_client.unary_stream(req):
956
+ yield res
909
957
 
910
958
  async def stream_candles(
911
959
  self,
912
960
  symbol: TradableProduct | str,
913
961
  venue: Venue,
914
962
  candle_widths: Optional[list[CandleWidth]],
915
- ) -> AsyncIterator[Candle]:
963
+ ) -> AsyncGenerator[Candle, None]:
916
964
  """
917
965
  Subscribe to a stream of candles for a symbol.
918
966
  """
@@ -922,13 +970,14 @@ class AsyncClient:
922
970
  venue=venue,
923
971
  candle_widths=candle_widths,
924
972
  )
925
- return grpc_client.unary_stream(req)
973
+ async for res in grpc_client.unary_stream(req):
974
+ yield res
926
975
 
927
976
  # ------------------------------------------------------------
928
977
  # Portfolio management
929
978
  # ------------------------------------------------------------
930
979
 
931
- async def list_accounts(self) -> Sequence[AccountWithPermissionsFields]:
980
+ async def list_accounts(self) -> List[AccountWithPermissions]:
932
981
  """
933
982
  List accounts for the user that the API key belongs to.
934
983
 
@@ -937,17 +986,12 @@ class AsyncClient:
937
986
  a list of AccountWithPermissions for the user that the API key belongs to
938
987
  (use who_am_i to get the user_id / email)
939
988
  """
940
- res = await self.graphql_client.list_accounts_query()
989
+ grpc_client = await self.core()
990
+ req = AccountsRequest(paper=self.paper_trading)
991
+ res = await grpc_client.unary_unary(req)
941
992
  return res.accounts
942
993
 
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:
994
+ async def get_account_summary(self, account: str) -> AccountSummary:
951
995
  """
952
996
  Get account summary, including balances, positions, pnls, etc.
953
997
 
@@ -955,20 +999,16 @@ class AsyncClient:
955
999
  account: account uuid or name
956
1000
  Examples: "00000000-0000-0000-0000-000000000000", "STONEX:000000/JDoe"
957
1001
  """
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
- """
1002
+ grpc_client = await self.core()
1003
+ req = AccountSummaryRequest(account=account)
1004
+ res = await grpc_client.unary_unary(req)
1005
+ return res
966
1006
 
967
1007
  async def get_account_summaries(
968
1008
  self,
969
1009
  accounts: Optional[list[str]] = None,
970
1010
  trader: Optional[str] = None,
971
- ) -> Sequence[AccountSummaryFields]:
1011
+ ) -> list[AccountSummary]:
972
1012
  """
973
1013
  Get account summaries for accounts matching the filters.
974
1014
 
@@ -978,45 +1018,24 @@ class AsyncClient:
978
1018
 
979
1019
  If both arguments are given, the union of matching accounts are returned.
980
1020
  """
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]:
1021
+ grpc_client = await self.core()
991
1022
  request = AccountSummariesRequest(
992
1023
  accounts=accounts,
993
1024
  trader=trader,
994
1025
  )
995
- account_summaries = await self.grpc_client.request(request)
996
- return account_summaries.account_summaries
997
- """
1026
+ res = await grpc_client.unary_unary(request)
1027
+ return res.account_summaries
998
1028
 
999
1029
  async def get_account_history(
1000
1030
  self,
1001
1031
  account: str,
1002
1032
  from_inclusive: Optional[datetime] = None,
1003
1033
  to_exclusive: Optional[datetime] = None,
1004
- ) -> Sequence[AccountSummaryFields]:
1034
+ ) -> list[AccountSummary]:
1005
1035
  """
1006
1036
  Get historical sequence of account summaries for the given account.
1007
1037
  """
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]:
1038
+ grpc_client = await self.core()
1020
1039
  if from_inclusive is not None:
1021
1040
  assert from_inclusive.tzinfo is timezone.utc, (
1022
1041
  "from_inclusive must be a utc datetime:\n"
@@ -1031,12 +1050,11 @@ class AsyncClient:
1031
1050
  "dt = datetime(2025, 4, 15, 12, 0, 0, tzinfo=timezone.utc)"
1032
1051
  )
1033
1052
 
1034
- request = AccountHistoryRequest(
1053
+ req = AccountHistoryRequest(
1035
1054
  account=account, from_inclusive=from_inclusive, to_exclusive=to_exclusive
1036
1055
  )
1037
- history = await self.grpc_client.request(request)
1038
- return history.history
1039
- """
1056
+ res = await grpc_client.unary_unary(req)
1057
+ return res.history
1040
1058
 
1041
1059
  # ------------------------------------------------------------
1042
1060
  # Order management
@@ -1044,13 +1062,13 @@ class AsyncClient:
1044
1062
 
1045
1063
  async def get_open_orders(
1046
1064
  self,
1047
- order_ids: Optional[list[str]] = None,
1065
+ order_ids: Optional[list[OrderId]] = None,
1048
1066
  venue: Optional[str] = None,
1049
1067
  account: Optional[str] = None,
1050
1068
  trader: Optional[str] = None,
1051
1069
  symbol: Optional[str] = None,
1052
- parent_order_id: Optional[str] = None,
1053
- ) -> Sequence[OrderFields]:
1070
+ parent_order_id: Optional[OrderId] = None,
1071
+ ) -> list[Order]:
1054
1072
  """
1055
1073
  Returns a list of open orders for the user that match the filters.
1056
1074
 
@@ -1065,7 +1083,8 @@ class AsyncClient:
1065
1083
  Returns:
1066
1084
  Open orders that match the union of the filters
1067
1085
  """
1068
- res = await self.graphql_client.get_open_orders_query(
1086
+ grpc_client = await self.core()
1087
+ open_orders_request = OpenOrdersRequest(
1069
1088
  venue=venue,
1070
1089
  account=account,
1071
1090
  trader=trader,
@@ -1073,26 +1092,27 @@ class AsyncClient:
1073
1092
  parent_order_id=parent_order_id,
1074
1093
  order_ids=order_ids,
1075
1094
  )
1076
- return res.open_orders
1077
1095
 
1078
- 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]:
1079
1100
  """
1080
1101
  @deprecated(reason="Use get_open_orders with no parameters instead")
1081
1102
 
1082
1103
  Returns a list of all open orders for the authenticated user.
1083
1104
  """
1084
- res = await self.graphql_client.get_open_orders_query()
1085
- return res.open_orders
1105
+ return await self.get_open_orders()
1086
1106
 
1087
1107
  async def get_historical_orders(
1088
1108
  self,
1089
- order_ids: Optional[list[str]] = None,
1109
+ order_ids: Optional[list[OrderId]] = None,
1090
1110
  from_inclusive: Optional[datetime] = None,
1091
1111
  to_exclusive: Optional[datetime] = None,
1092
1112
  venue: Optional[str] = None,
1093
1113
  account: Optional[str] = None,
1094
- parent_order_id: Optional[str] = None,
1095
- ) -> Sequence[OrderFields]:
1114
+ parent_order_id: Optional[OrderId] = None,
1115
+ ) -> list[Order]:
1096
1116
  """
1097
1117
  Returns a list of all historical orders that match the filters.
1098
1118
 
@@ -1112,7 +1132,23 @@ class AsyncClient:
1112
1132
  If order_ids is not specified, then from_inclusive and to_exclusive
1113
1133
  MUST be specified.
1114
1134
  """
1115
- res = 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(
1116
1152
  order_ids=order_ids,
1117
1153
  venue=venue,
1118
1154
  account=account,
@@ -1120,9 +1156,10 @@ class AsyncClient:
1120
1156
  from_inclusive=from_inclusive,
1121
1157
  to_exclusive=to_exclusive,
1122
1158
  )
1123
- return res.historical_orders
1159
+ orders = await grpc_client.unary_unary(historical_orders_request)
1160
+ return orders.orders
1124
1161
 
1125
- async def get_order(self, order_id: str) -> Optional[OrderFields]:
1162
+ async def get_order(self, order_id: OrderId) -> Optional[Order]:
1126
1163
  """
1127
1164
  Returns the specified order. Useful for looking at past sent orders.
1128
1165
  Queries open_orders first, then queries historical_orders.
@@ -1130,18 +1167,23 @@ class AsyncClient:
1130
1167
  Args:
1131
1168
  order_id: the order id to get
1132
1169
  """
1133
- res = await self.graphql_client.get_open_orders_query(order_ids=[order_id])
1170
+ grpc_client = await self.core()
1171
+ req = OpenOrdersRequest.new(
1172
+ order_ids=[order_id],
1173
+ )
1174
+ res = await grpc_client.unary_unary(req)
1134
1175
  for open_order in res.open_orders:
1135
1176
  if open_order.id == order_id:
1136
1177
  return open_order
1137
1178
 
1138
- res = await self.graphql_client.get_historical_orders_query(
1139
- order_ids=[order_id]
1179
+ req = HistoricalOrdersRequest.new(
1180
+ order_ids=[order_id],
1140
1181
  )
1141
- if res.historical_orders and len(res.historical_orders) > 0:
1142
- return res.historical_orders[0]
1182
+ res = await grpc_client.unary_unary(req)
1183
+ if res.orders and len(res.orders) == 1:
1184
+ return res.orders[0]
1143
1185
 
1144
- 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]]:
1145
1187
  """
1146
1188
  Returns the specified orders. Useful for looking at past sent orders.
1147
1189
  Plural form of get_order.
@@ -1149,24 +1191,27 @@ class AsyncClient:
1149
1191
  Args:
1150
1192
  order_ids: a list of order ids to get
1151
1193
  """
1152
- orders_dict: dict[str, Optional[OrderFields]] = {
1194
+ grpc_client = await self.core()
1195
+ orders_dict: dict[OrderId, Optional[Order]] = {
1153
1196
  order_id: None for order_id in order_ids
1154
1197
  }
1198
+ req = OpenOrdersRequest.new(
1199
+ order_ids=order_ids,
1200
+ )
1155
1201
 
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:
1202
+ res = await grpc_client.unary_unary(req)
1203
+ for open_order in res.open_orders:
1159
1204
  orders_dict[open_order.id] = open_order
1160
1205
 
1161
1206
  not_open_order_ids = [
1162
1207
  order_id for order_id in order_ids if orders_dict[order_id] is None
1163
1208
  ]
1164
1209
 
1165
- res = await self.graphql_client.get_historical_orders_query(
1166
- order_ids=not_open_order_ids
1210
+ req = HistoricalOrdersRequest.new(
1211
+ order_ids=not_open_order_ids,
1167
1212
  )
1168
- historical_orders = res.historical_orders
1169
- for historical_order in historical_orders:
1213
+ res = await grpc_client.unary_unary(req)
1214
+ for historical_order in res.orders:
1170
1215
  orders_dict[historical_order.id] = historical_order
1171
1216
 
1172
1217
  return [orders_dict[order_id] for order_id in order_ids]
@@ -1177,8 +1222,9 @@ class AsyncClient:
1177
1222
  to_exclusive: Optional[datetime] = None,
1178
1223
  venue: Optional[str] = None,
1179
1224
  account: Optional[str] = None,
1180
- order_id: Optional[str] = None,
1181
- ) -> GetFillsQueryFolioHistoricalFills:
1225
+ order_id: Optional[OrderId] = None,
1226
+ limit: Optional[int] = None,
1227
+ ) -> HistoricalFillsResponse:
1182
1228
  """
1183
1229
  Returns all fills matching the given filters.
1184
1230
 
@@ -1189,15 +1235,35 @@ class AsyncClient:
1189
1235
  account: account uuid or name
1190
1236
  order_id: the order id to get fills for
1191
1237
  """
1192
- res = await self.graphql_client.get_fills_query(
1193
- venue, account, order_id, from_inclusive, to_exclusive
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
+ )
1245
+
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,
1194
1259
  )
1195
- return res.historical_fills
1260
+ res = await grpc_client.unary_unary(req)
1261
+ return res
1196
1262
 
1197
1263
  async def orderflow(
1198
1264
  self,
1199
1265
  request_iterator: AsyncIterator[OrderflowRequest],
1200
- ) -> AsyncIterator[Orderflow]:
1266
+ ) -> AsyncGenerator[Orderflow, None]:
1201
1267
  """
1202
1268
  A two-way channel for both order entry and listening to order updates (fills, acks, outs, etc.).
1203
1269
 
@@ -1210,12 +1276,12 @@ class AsyncClient:
1210
1276
  """
1211
1277
  grpc_client = await self.core()
1212
1278
  decoder = grpc_client.get_decoder(OrderflowRequestUnannotatedResponseType)
1213
- stub = grpc_client.channel.stream_stream(
1279
+ stub: grpc.aio.StreamStreamMultiCallable = grpc_client.channel.stream_stream(
1214
1280
  OrderflowRequest_route,
1215
1281
  request_serializer=grpc_client.encoder().encode,
1216
1282
  response_deserializer=decoder.decode,
1217
1283
  )
1218
- call = stub(
1284
+ call: grpc.aio._base_call.StreamStreamCall[OrderflowRequest, Orderflow] = stub(
1219
1285
  request_iterator, metadata=(("authorization", f"Bearer {grpc_client.jwt}"),)
1220
1286
  )
1221
1287
  async for update in call:
@@ -1223,10 +1289,10 @@ class AsyncClient:
1223
1289
 
1224
1290
  async def stream_orderflow(
1225
1291
  self,
1226
- account: Optional[grpc_definitions.AccountIdOrName] = None,
1292
+ account: Optional[AccountIdOrName] = None,
1227
1293
  execution_venue: Optional[str] = None,
1228
- trader: Optional[grpc_definitions.TraderIdOrEmail] = None,
1229
- ) -> AsyncIterator[Orderflow]:
1294
+ trader: Optional[TraderIdOrEmail] = None,
1295
+ ) -> AsyncGenerator[Orderflow, None]:
1230
1296
  """
1231
1297
  A stream for listening to order updates (fills, acks, outs, etc.).
1232
1298
 
@@ -1245,12 +1311,16 @@ class AsyncClient:
1245
1311
  )
1246
1312
  grpc_client = await self.core()
1247
1313
  decoder = grpc_client.get_decoder(SubscribeOrderflowRequest)
1248
- stub = grpc_client.channel.unary_stream(
1314
+ stub: grpc.aio.UnaryStreamMultiCallable[
1315
+ SubscribeOrderflowRequest, Orderflow
1316
+ ] = grpc_client.channel.unary_stream(
1249
1317
  SubscribeOrderflowRequest.get_route(),
1250
1318
  request_serializer=grpc_client.encoder().encode,
1251
1319
  response_deserializer=decoder.decode,
1252
1320
  )
1253
- call = stub(request, metadata=(("authorization", f"Bearer {grpc_client.jwt}"),))
1321
+ call: grpc.aio._base_call.UnaryStreamCall[
1322
+ SubscribeOrderflowRequest, Orderflow
1323
+ ] = stub(request, metadata=(("authorization", f"Bearer {grpc_client.jwt}"),))
1254
1324
  async for update in call:
1255
1325
  yield update
1256
1326
 
@@ -1258,39 +1328,49 @@ class AsyncClient:
1258
1328
  # Order entry
1259
1329
  # ------------------------------------------------------------
1260
1330
 
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
+
1261
1341
  async def place_limit_order(
1262
1342
  self,
1263
1343
  *,
1264
- id: Optional[str] = None,
1344
+ id: Optional[OrderId] = None,
1265
1345
  symbol: TradableProduct | str,
1266
- execution_venue: Optional[str],
1267
- odir: OrderDir,
1346
+ execution_venue: Optional[str] = None,
1347
+ dir: Optional[OrderDir] = None,
1268
1348
  quantity: Decimal,
1269
1349
  limit_price: Decimal,
1270
1350
  order_type: OrderType = OrderType.LIMIT,
1271
- time_in_force: TimeInForce = TimeInForce.DAY,
1272
- good_til_date: Optional[datetime] = None,
1351
+ time_in_force: TimeInForce = TimeInForceEnum.DAY,
1273
1352
  price_round_method: Optional[TickRoundMethod] = None,
1274
1353
  account: Optional[str] = None,
1275
1354
  trader: Optional[str] = None,
1276
1355
  post_only: bool = False,
1277
1356
  trigger_price: Optional[Decimal] = None,
1278
- ) -> OrderFields:
1357
+ **kwargs: Any,
1358
+ ) -> Order:
1279
1359
  """
1280
1360
  Sends a regular limit order.
1281
1361
 
1282
1362
  Args:
1363
+ id: in case user wants to generate their own order id, otherwise it will be generated automatically
1283
1364
  symbol: the symbol to send the order for
1284
1365
  execution_venue: the execution venue to send the order to,
1285
1366
  if execution_venue is set to None, the OMS will send the order to the primary_exchange
1286
1367
  the primary_exchange can be deduced from `get_product_info`
1287
- odir: the direction of the order
1368
+ dir: the direction of the order, BUY or SELL
1288
1369
  quantity: the quantity of the order
1289
1370
  limit_price: the limit price of the order
1290
1371
  It is highly recommended to make this a Decimal object from the decimal module to avoid floating point errors
1291
1372
  order_type: the type of the order
1292
1373
  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
1374
  price_round_method: the method to round the price to the nearest tick, will not round if None
1295
1375
  account: the account to send the order for
1296
1376
  While technically optional, for most order types, the account is required
@@ -1299,13 +1379,21 @@ class AsyncClient:
1299
1379
  post_only: whether the order should be post only, not supported by all exchanges
1300
1380
  trigger_price: the trigger price for the order, only relevant for stop / take_profit orders
1301
1381
  Returns:
1302
- the OrderFields object for the order
1382
+ the Order object for the order
1303
1383
  The order.status should be "PENDING" until the order is "OPEN" / "REJECTED" / "OUT" / "CANCELED" / "STALE"
1304
1384
 
1305
1385
  If the order is rejected, the order.reject_reason and order.reject_message will be set
1306
1386
  """
1387
+ grpc_client = await self.core()
1307
1388
  assert quantity > 0, "quantity must be positive"
1308
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
+
1309
1397
  if price_round_method is not None:
1310
1398
  if execution_venue is None:
1311
1399
  product_info = await self.get_product_info(symbol)
@@ -1331,44 +1419,43 @@ class AsyncClient:
1331
1419
  else:
1332
1420
  raise ValueError(f"Could not find market information for {symbol}")
1333
1421
 
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,
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,
1351
1437
  )
1352
-
1353
- return order.place_order
1438
+ res = await grpc_client.unary_unary(req)
1439
+ return res
1354
1440
 
1355
1441
  async def send_market_pro_order(
1356
1442
  self,
1357
1443
  *,
1358
- id: Optional[str] = None,
1444
+ id: Optional[OrderId] = None,
1359
1445
  symbol: TradableProduct | str,
1360
1446
  execution_venue: str,
1361
1447
  odir: OrderDir,
1362
1448
  quantity: Decimal,
1363
- time_in_force: TimeInForce = TimeInForce.DAY,
1449
+ time_in_force: TimeInForce = TimeInForceEnum.DAY,
1364
1450
  account: Optional[str] = None,
1365
1451
  fraction_through_market: Decimal = Decimal("0.001"),
1366
- ) -> OrderFields:
1452
+ ) -> Order:
1367
1453
  """
1368
1454
  Sends a market-order like limit price based on the BBO.
1369
1455
  Meant to behave as a market order but with more protections.
1370
1456
 
1371
1457
  Args:
1458
+ id: in case user wants to generate their own order id, otherwise it will be generated automatically
1372
1459
  symbol: the symbol to send the order for
1373
1460
  execution_venue: the execution venue to send the order to
1374
1461
  odir: the direction of the order
@@ -1440,7 +1527,7 @@ class AsyncClient:
1440
1527
  time_in_force=time_in_force,
1441
1528
  )
1442
1529
 
1443
- async def cancel_order(self, order_id: str) -> CancelFields:
1530
+ async def cancel_order(self, order_id: OrderId) -> Cancel:
1444
1531
  """
1445
1532
  Cancels an order by order id.
1446
1533
 
@@ -1449,10 +1536,17 @@ class AsyncClient:
1449
1536
  Returns:
1450
1537
  the CancelFields object
1451
1538
  """
1452
- cancel = await self.graphql_client.cancel_order_mutation(order_id)
1453
- 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
1454
1543
 
1455
- 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:
1456
1550
  """
1457
1551
  Cancels all open orders.
1458
1552
 
@@ -1460,5 +1554,25 @@ class AsyncClient:
1460
1554
  True if all orders were cancelled successfully
1461
1555
  False if there was an error
1462
1556
  """
1463
- b = await self.graphql_client.cancel_all_orders_mutation()
1464
- 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