architect-py 5.1.4b1__py3-none-any.whl → 5.1.5__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 (35) hide show
  1. architect_py/__init__.py +19 -1
  2. architect_py/async_client.py +158 -45
  3. architect_py/client.py +4 -0
  4. architect_py/client.pyi +33 -9
  5. architect_py/grpc/client.py +24 -9
  6. architect_py/grpc/models/AlgoHelper/AlgoParamTypes.py +31 -0
  7. architect_py/grpc/models/AlgoHelper/__init__.py +2 -0
  8. architect_py/grpc/models/Auth/AuthInfoRequest.py +37 -0
  9. architect_py/grpc/models/Auth/AuthInfoResponse.py +30 -0
  10. architect_py/grpc/models/Core/ConfigResponse.py +7 -2
  11. architect_py/grpc/models/Folio/AccountHistoryRequest.py +35 -4
  12. architect_py/grpc/models/Folio/AccountSummary.py +7 -1
  13. architect_py/grpc/models/Marketdata/Candle.py +6 -0
  14. architect_py/grpc/models/Marketdata/L1BookSnapshot.py +17 -3
  15. architect_py/grpc/models/Marketdata/L2BookSnapshot.py +6 -0
  16. architect_py/grpc/models/Marketdata/Liquidation.py +6 -0
  17. architect_py/grpc/models/Marketdata/Ticker.py +6 -0
  18. architect_py/grpc/models/Marketdata/Trade.py +6 -0
  19. architect_py/grpc/models/Oms/Order.py +38 -14
  20. architect_py/grpc/models/Oms/PlaceOrderRequest.py +38 -14
  21. architect_py/grpc/models/__init__.py +4 -1
  22. architect_py/grpc/models/definitions.py +173 -0
  23. architect_py/grpc/orderflow.py +138 -0
  24. architect_py/tests/test_order_entry.py +9 -6
  25. architect_py/tests/test_orderflow.py +116 -27
  26. architect_py/tests/test_positions.py +173 -0
  27. architect_py/tests/test_rounding.py +28 -28
  28. architect_py/utils/pandas.py +50 -1
  29. {architect_py-5.1.4b1.dist-info → architect_py-5.1.5.dist-info}/METADATA +1 -1
  30. {architect_py-5.1.4b1.dist-info → architect_py-5.1.5.dist-info}/RECORD +35 -29
  31. examples/funding_rate_mean_reversion_algo.py +23 -47
  32. scripts/postprocess_grpc.py +17 -2
  33. {architect_py-5.1.4b1.dist-info → architect_py-5.1.5.dist-info}/WHEEL +0 -0
  34. {architect_py-5.1.4b1.dist-info → architect_py-5.1.5.dist-info}/licenses/LICENSE +0 -0
  35. {architect_py-5.1.4b1.dist-info → architect_py-5.1.5.dist-info}/top_level.txt +0 -0
architect_py/__init__.py CHANGED
@@ -1,12 +1,13 @@
1
1
  # ruff: noqa:I001
2
2
 
3
- __version__ = "5.1.4b1"
3
+ __version__ = "5.1.5"
4
4
 
5
5
  from .utils.nearest_tick import TickRoundMethod
6
6
  from .async_client import AsyncClient
7
7
  from .client import Client
8
8
  from .common_types import OrderDir, TradableProduct, TimeInForce, Venue
9
9
  from .grpc.models.definitions import (
10
+ AccountHistoryGranularity,
10
11
  AccountIdOrName,
11
12
  AccountPosition,
12
13
  AccountStatistics,
@@ -32,12 +33,14 @@ from .grpc.models.definitions import (
32
33
  SortTickersBy,
33
34
  Statement,
34
35
  TraderIdOrEmail,
36
+ TriggerLimitOrderType,
35
37
  UserId,
36
38
  Withdrawal,
37
39
  AccountPermissions,
38
40
  AliasKind,
39
41
  DerivativeKind,
40
42
  FillKind,
43
+ HumanDuration,
41
44
  Unit,
42
45
  MinOrderQuantityUnit,
43
46
  OptionsExerciseType,
@@ -55,6 +58,7 @@ from .grpc.models.definitions import (
55
58
  SnapshotOrUpdateForStringAndProductCatalogInfo2,
56
59
  SnapshotOrUpdateForStringAndString1,
57
60
  SnapshotOrUpdateForStringAndString2,
61
+ SpreaderPhase,
58
62
  SimpleDecimal,
59
63
  Varying1,
60
64
  Varying,
@@ -82,6 +86,8 @@ from .grpc.models.definitions import (
82
86
  SnapshotOrUpdateForStringAndOptionsSeriesInfo2,
83
87
  SnapshotOrUpdateForStringAndSnapshotOrUpdateForStringAndProductCatalogInfo1,
84
88
  SnapshotOrUpdateForStringAndSnapshotOrUpdateForStringAndProductCatalogInfo2,
89
+ SpreaderParams,
90
+ SpreaderStatus,
85
91
  Account,
86
92
  FutureSpread,
87
93
  Option,
@@ -110,6 +116,9 @@ from .grpc.models.Algo.StartAlgoRequest import StartAlgoRequest
110
116
  from .grpc.models.Algo.StartAlgoResponse import StartAlgoResponse
111
117
  from .grpc.models.Algo.StopAlgoRequest import StopAlgoRequest
112
118
  from .grpc.models.Algo.StopAlgoResponse import StopAlgoResponse
119
+ from .grpc.models.AlgoHelper.AlgoParamTypes import AlgoParamTypes
120
+ from .grpc.models.Auth.AuthInfoRequest import AuthInfoRequest
121
+ from .grpc.models.Auth.AuthInfoResponse import AuthInfoResponse
113
122
  from .grpc.models.Auth.CreateJwtRequest import CreateJwtRequest
114
123
  from .grpc.models.Auth.CreateJwtResponse import CreateJwtResponse
115
124
  from .grpc.models.Boss.DepositsRequest import DepositsRequest
@@ -236,6 +245,7 @@ from .grpc.models.Symbology.UploadSymbologyResponse import UploadSymbologyRespon
236
245
  __all__ = [
237
246
  "AberrantFill",
238
247
  "Account",
248
+ "AccountHistoryGranularity",
239
249
  "AccountHistoryRequest",
240
250
  "AccountHistoryResponse",
241
251
  "AccountIdOrName",
@@ -255,9 +265,12 @@ __all__ = [
255
265
  "AlgoOrderStatus",
256
266
  "AlgoOrdersRequest",
257
267
  "AlgoOrdersResponse",
268
+ "AlgoParamTypes",
258
269
  "AliasKind",
259
270
  "ArrayOfL1BookSnapshot",
260
271
  "AsyncClient",
272
+ "AuthInfoRequest",
273
+ "AuthInfoResponse",
261
274
  "Cancel",
262
275
  "CancelAllOrdersRequest",
263
276
  "CancelAllOrdersResponse",
@@ -315,6 +328,7 @@ __all__ = [
315
328
  "HistoricalFillsResponse",
316
329
  "HistoricalOrdersRequest",
317
330
  "HistoricalOrdersResponse",
331
+ "HumanDuration",
318
332
  "Index",
319
333
  "L1BookSnapshot",
320
334
  "L1BookSnapshotRequest",
@@ -394,6 +408,9 @@ __all__ = [
394
408
  "SnapshotOrUpdateForStringAndString2",
395
409
  "SortTickersBy",
396
410
  "SpreadLeg",
411
+ "SpreaderParams",
412
+ "SpreaderPhase",
413
+ "SpreaderStatus",
397
414
  "StartAlgoRequest",
398
415
  "StartAlgoResponse",
399
416
  "Statement",
@@ -428,6 +445,7 @@ __all__ = [
428
445
  "TradableProduct",
429
446
  "Trade",
430
447
  "TraderIdOrEmail",
448
+ "TriggerLimitOrderType",
431
449
  "Unit",
432
450
  "Unknown",
433
451
  "UploadProductCatalogRequest",
@@ -6,7 +6,6 @@ from decimal import Decimal
6
6
  from typing import (
7
7
  Any,
8
8
  AsyncGenerator,
9
- AsyncIterator,
10
9
  List,
11
10
  Literal,
12
11
  Optional,
@@ -16,7 +15,6 @@ from typing import (
16
15
  overload,
17
16
  )
18
17
 
19
- import grpc
20
18
  import pandas as pd
21
19
 
22
20
  # cannot do architect_py import * due to circular import
@@ -38,16 +36,15 @@ from architect_py.grpc.models.definitions import (
38
36
  OrderSource,
39
37
  OrderType,
40
38
  SortTickersBy,
39
+ SpreaderParams,
41
40
  TraderIdOrEmail,
41
+ TriggerLimitOrderType,
42
42
  )
43
- from architect_py.grpc.models.Orderflow.OrderflowRequest import (
44
- OrderflowRequest_route,
45
- OrderflowRequestUnannotatedResponseType,
46
- )
43
+ from architect_py.grpc.orderflow import OrderflowChannel
47
44
  from architect_py.grpc.resolve_endpoint import PAPER_GRPC_PORT, resolve_endpoint
48
45
  from architect_py.utils.nearest_tick import TickRoundMethod
49
46
  from architect_py.utils.orderbook import update_orderbook_side
50
- from architect_py.utils.pandas import candles_to_dataframe
47
+ from architect_py.utils.pandas import candles_to_dataframe, tickers_to_dataframe
51
48
  from architect_py.utils.price_bands import price_band_pairs
52
49
  from architect_py.utils.symbol_parsing import nominative_expiration
53
50
 
@@ -56,6 +53,8 @@ class AsyncClient:
56
53
  api_key: Optional[str] = None
57
54
  api_secret: Optional[str] = None
58
55
  paper_trading: bool
56
+ as_user: Optional[str] = None
57
+ as_role: Optional[str] = None
59
58
  graphql_client: GraphQLClient
60
59
  grpc_options: Sequence[Tuple[str, Any]] | None = None
61
60
  grpc_core: Optional[GrpcClient] = None
@@ -80,6 +79,8 @@ class AsyncClient:
80
79
  endpoint: str = "https://app.architect.co",
81
80
  graphql_port: Optional[int] = None,
82
81
  grpc_options: Sequence[Tuple[str, Any]] | None = None,
82
+ as_user: Optional[str] = None,
83
+ as_role: Optional[str] = None,
83
84
  **kwargs: Any,
84
85
  ) -> "AsyncClient":
85
86
  """
@@ -131,6 +132,8 @@ class AsyncClient:
131
132
  api_key=api_key,
132
133
  api_secret=api_secret,
133
134
  paper_trading=paper_trading,
135
+ as_user=as_user,
136
+ as_role=as_role,
134
137
  grpc_host=grpc_host,
135
138
  grpc_port=grpc_port,
136
139
  grpc_options=grpc_options,
@@ -153,6 +156,8 @@ class AsyncClient:
153
156
  api_key: Optional[str] = None,
154
157
  api_secret: Optional[str] = None,
155
158
  paper_trading: bool,
159
+ as_user: Optional[str] = None,
160
+ as_role: Optional[str] = None,
156
161
  grpc_host: str = "app.architect.co",
157
162
  grpc_port: int,
158
163
  grpc_options: Sequence[Tuple[str, Any]] | None = None,
@@ -184,6 +189,8 @@ class AsyncClient:
184
189
  self.api_key = api_key
185
190
  self.api_secret = api_secret
186
191
  self.paper_trading = paper_trading
192
+ self.as_user = as_user
193
+ self.as_role = as_role
187
194
  self.graphql_client = GraphQLClient(
188
195
  host=grpc_host,
189
196
  port=graphql_port,
@@ -193,7 +200,12 @@ class AsyncClient:
193
200
  )
194
201
  self.grpc_options = grpc_options
195
202
  self.grpc_core = GrpcClient(
196
- host=grpc_host, port=grpc_port, use_ssl=use_ssl, options=grpc_options
203
+ host=grpc_host,
204
+ port=grpc_port,
205
+ use_ssl=use_ssl,
206
+ options=grpc_options,
207
+ as_user=as_user,
208
+ as_role=as_role,
197
209
  )
198
210
 
199
211
  async def close(self):
@@ -238,7 +250,9 @@ class AsyncClient:
238
250
  ):
239
251
  try:
240
252
  req = CreateJwtRequest(api_key=self.api_key, api_secret=self.api_secret)
241
- res: CreateJwtResponse = await self.grpc_core.unary_unary(req)
253
+ res: CreateJwtResponse = await self.grpc_core.unary_unary(
254
+ req, no_metadata=True
255
+ )
242
256
  self.jwt = res.jwt
243
257
  # CR alee: actually read the JWT to get the expiration time;
244
258
  # for now, we just "know" that the JWTs are granted for an hour
@@ -287,6 +301,8 @@ class AsyncClient:
287
301
  port=grpc_port,
288
302
  use_ssl=use_ssl,
289
303
  options=self.grpc_options,
304
+ as_user=self.as_user,
305
+ as_role=self.as_role,
290
306
  )
291
307
  except Exception as e:
292
308
  logging.error("Failed to set marketdata endpoint: %s", e)
@@ -304,6 +320,8 @@ class AsyncClient:
304
320
  port=grpc_port,
305
321
  use_ssl=use_ssl,
306
322
  options=self.grpc_options,
323
+ as_user=self.as_user,
324
+ as_role=self.as_role,
307
325
  )
308
326
  logging.debug(
309
327
  f"Setting marketdata endpoint for {venue}: {grpc_host}:{grpc_port} use_ssl={use_ssl}"
@@ -340,6 +358,8 @@ class AsyncClient:
340
358
  port=grpc_port,
341
359
  use_ssl=use_ssl,
342
360
  options=self.grpc_options,
361
+ as_user=self.as_user,
362
+ as_role=self.as_role,
343
363
  )
344
364
  except Exception as e:
345
365
  logging.error("Failed to set hmart endpoint: %s", e)
@@ -380,6 +400,12 @@ class AsyncClient:
380
400
  res = await self.graphql_client.user_id_query()
381
401
  return res.user_id, res.user_email
382
402
 
403
+ async def auth_info(self) -> AuthInfoResponse:
404
+ grpc_client = await self.core()
405
+ req = AuthInfoRequest()
406
+ res: AuthInfoResponse = await grpc_client.unary_unary(req)
407
+ return res
408
+
383
409
  def enable_orderflow(self):
384
410
  """
385
411
  @deprecated(reason="No longer needed; orderflow is enabled by default")
@@ -864,8 +890,43 @@ class AsyncClient:
864
890
  res: Ticker = await grpc_client.unary_unary(req)
865
891
  return res
866
892
 
893
+ async def get_tickers(
894
+ self,
895
+ *,
896
+ venue: Venue,
897
+ symbols: Optional[Sequence[TradableProduct | str]] = None,
898
+ include_options: bool = False,
899
+ sort_by: Optional[SortTickersBy | str] = None,
900
+ offset: Optional[int] = None,
901
+ limit: Optional[int] = None,
902
+ as_dataframe: bool = False,
903
+ ) -> Union[Sequence[Ticker], pd.DataFrame]:
904
+ """
905
+ Gets the tickers for a list of symbols.
906
+ """
907
+ grpc_client = await self.marketdata(venue)
908
+ sort_by = SortTickersBy(sort_by) if sort_by else None
909
+ symbols = [str(symbol) for symbol in symbols] if symbols else None
910
+ req = TickersRequest.new(
911
+ offset=offset,
912
+ include_options=include_options,
913
+ sort_by=sort_by,
914
+ limit=limit,
915
+ symbols=symbols,
916
+ venue=venue,
917
+ )
918
+ res: TickersResponse = await grpc_client.unary_unary(req)
919
+ if as_dataframe:
920
+ return tickers_to_dataframe(res.tickers)
921
+ else:
922
+ return res.tickers
923
+
867
924
  async def stream_l1_book_snapshots(
868
- self, symbols: Sequence[TradableProduct | str], venue: Venue
925
+ self,
926
+ symbols: Sequence[TradableProduct | str],
927
+ venue: Venue,
928
+ *,
929
+ send_initial_snapshots: Optional[bool] = False,
869
930
  ) -> AsyncGenerator[L1BookSnapshot, None]:
870
931
  """
871
932
  Subscribe to the stream of L1BookSnapshots for a symbol.
@@ -876,7 +937,11 @@ class AsyncClient:
876
937
  venue: the venue to subscribe to
877
938
  """
878
939
  grpc_client = await self.marketdata(venue)
879
- req = SubscribeL1BookSnapshotsRequest(symbols=list(symbols), venue=venue)
940
+ req = SubscribeL1BookSnapshotsRequest(
941
+ symbols=list(symbols),
942
+ venue=venue,
943
+ send_initial_snapshots=send_initial_snapshots,
944
+ )
880
945
  async for res in grpc_client.unary_stream(req):
881
946
  yield res
882
947
 
@@ -1117,6 +1182,27 @@ class AsyncClient:
1117
1182
  res = await grpc_client.unary_unary(req)
1118
1183
  return res
1119
1184
 
1185
+ async def get_positions(
1186
+ self,
1187
+ accounts: Optional[list[str]] = None,
1188
+ trader: Optional[str] = None,
1189
+ ) -> dict[str, Decimal]:
1190
+ """
1191
+ Get positions for the specified symbols.
1192
+
1193
+ Args:
1194
+ symbols: list of symbol strings
1195
+ """
1196
+ account_summaries = await self.get_account_summaries(
1197
+ accounts=accounts, trader=trader
1198
+ )
1199
+ positions: dict[str, Decimal] = {}
1200
+ for summary in account_summaries:
1201
+ for symbol, summary in summary.positions.items():
1202
+ for pos in summary:
1203
+ positions[symbol] = positions.get(symbol, Decimal(0)) + pos.quantity
1204
+ return positions
1205
+
1120
1206
  async def get_account_summaries(
1121
1207
  self,
1122
1208
  accounts: Optional[list[str]] = None,
@@ -1381,30 +1467,17 @@ class AsyncClient:
1381
1467
 
1382
1468
  async def orderflow(
1383
1469
  self,
1384
- request_iterator: AsyncIterator[OrderflowRequest],
1385
- ) -> AsyncGenerator[Orderflow, None]:
1470
+ max_queue_size: int = 1024,
1471
+ ) -> OrderflowChannel:
1386
1472
  """
1387
1473
  A two-way channel for both order entry and listening to order updates (fills, acks, outs, etc.).
1388
1474
 
1389
1475
  This is considered the most efficient way to trade in this SDK.
1390
1476
 
1391
- Example:
1392
- See test_orderflow.py for an example.
1393
-
1394
- This WILL block the event loop until the stream is closed.
1477
+ This requires advanced knowledge of the SDK and asyncio, not recommended for beginners.
1478
+ See the OrderflowManager documentation for more details.
1395
1479
  """
1396
- grpc_client = await self.core()
1397
- decoder = grpc_client.get_decoder(OrderflowRequestUnannotatedResponseType)
1398
- stub: grpc.aio.StreamStreamMultiCallable = grpc_client.channel.stream_stream(
1399
- OrderflowRequest_route,
1400
- request_serializer=grpc_client.encoder().encode,
1401
- response_deserializer=decoder.decode,
1402
- )
1403
- call: grpc.aio._base_call.StreamStreamCall[OrderflowRequest, Orderflow] = stub(
1404
- request_iterator, metadata=(("authorization", f"Bearer {grpc_client.jwt}"),)
1405
- )
1406
- async for update in call:
1407
- yield update
1480
+ return OrderflowChannel(self, max_queue_size=max_queue_size)
1408
1481
 
1409
1482
  async def stream_orderflow(
1410
1483
  self,
@@ -1438,9 +1511,27 @@ class AsyncClient:
1438
1511
  **kwargs,
1439
1512
  ) -> Order:
1440
1513
  """
1441
- @deprecated(reason="Use place_limit_order instead")
1514
+ @deprecated(reason="Use place_order instead")
1515
+ """
1516
+ logging.warning(
1517
+ "send_limit_order is deprecated, use place_order instead. "
1518
+ "This will be removed in a future version."
1519
+ )
1520
+ return await self.place_order(*args, **kwargs)
1521
+
1522
+ async def place_limit_order(
1523
+ self,
1524
+ *args,
1525
+ **kwargs,
1526
+ ) -> Order:
1527
+ """
1528
+ @deprecated(reason="Use place_order instead")
1442
1529
  """
1443
- return await self.place_limit_order(*args, **kwargs)
1530
+ logging.warning(
1531
+ "place_limit_order is deprecated, use place_order instead. "
1532
+ "This will be removed in a future version."
1533
+ )
1534
+ return await self.place_order(*args, **kwargs)
1444
1535
 
1445
1536
  async def place_orders(
1446
1537
  self, order_requests: Sequence[PlaceOrderRequest]
@@ -1484,26 +1575,28 @@ class AsyncClient:
1484
1575
 
1485
1576
  return res
1486
1577
 
1487
- async def place_limit_order(
1578
+ async def place_order(
1488
1579
  self,
1489
1580
  *,
1490
1581
  id: Optional[OrderId] = None,
1491
1582
  symbol: TradableProduct | str,
1492
1583
  execution_venue: Optional[str] = None,
1493
- dir: Optional[OrderDir] = None,
1584
+ dir: OrderDir,
1494
1585
  quantity: Decimal,
1495
- limit_price: Decimal,
1586
+ limit_price: Optional[Decimal] = None,
1496
1587
  order_type: OrderType = OrderType.LIMIT,
1497
1588
  time_in_force: TimeInForce = TimeInForce.DAY,
1498
1589
  price_round_method: Optional[TickRoundMethod] = None,
1499
1590
  account: Optional[str] = None,
1500
1591
  trader: Optional[str] = None,
1501
- post_only: bool = False,
1592
+ post_only: Optional[bool] = None,
1502
1593
  trigger_price: Optional[Decimal] = None,
1594
+ stop_loss: Optional[TriggerLimitOrderType] = None,
1595
+ take_profit_price: Optional[Decimal] = None,
1503
1596
  **kwargs: Any,
1504
1597
  ) -> Order:
1505
1598
  """
1506
- Sends a regular limit order.
1599
+ Sends a regular order.
1507
1600
 
1508
1601
  Args:
1509
1602
  id: in case user wants to generate their own order id, otherwise it will be generated automatically
@@ -1522,8 +1615,10 @@ class AsyncClient:
1522
1615
  While technically optional, for most order types, the account is required
1523
1616
  trader: the trader to send the order for, defaults to the user's trader
1524
1617
  for when sending order for another user, not relevant for vast majority of users
1525
- post_only: whether the order should be post only, not supported by all exchanges
1618
+ post_only: whether the order should be post only, NOT SUPPORTED BY ALL EXCHANGES (e.g. CME)
1526
1619
  trigger_price: the trigger price for the order, only relevant for stop / take_profit orders
1620
+ stop_loss_price: the stop loss price for a bracket order.
1621
+ profit_price: the take profit price for a bracket order.
1527
1622
  Returns:
1528
1623
  the Order object for the order
1529
1624
  The order.status should be "PENDING" until the order is "OPEN" / "REJECTED" / "OUT" / "CANCELED" / "STALE"
@@ -1533,14 +1628,7 @@ class AsyncClient:
1533
1628
  grpc_client = await self.core()
1534
1629
  assert quantity > 0, "quantity must be positive"
1535
1630
 
1536
- if dir is None:
1537
- if "odir" in kwargs and isinstance(kwargs["odir"], OrderDir):
1538
- logging.warning("odir is deprecated, use dir instead")
1539
- dir = kwargs["odir"]
1540
- else:
1541
- raise ValueError("dir is required")
1542
-
1543
- if price_round_method is not None:
1631
+ if limit_price is not None and price_round_method is not None:
1544
1632
  if execution_venue is None:
1545
1633
  product_info = await self.get_product_info(symbol)
1546
1634
  if product_info is None:
@@ -1580,6 +1668,8 @@ class AsyncClient:
1580
1668
  execution_venue=execution_venue,
1581
1669
  post_only=post_only,
1582
1670
  trigger_price=trigger_price,
1671
+ stop_loss=stop_loss,
1672
+ take_profit_price=take_profit_price,
1583
1673
  )
1584
1674
  res = await grpc_client.unary_unary(req)
1585
1675
  return res
@@ -1724,3 +1814,26 @@ class AsyncClient:
1724
1814
  # )
1725
1815
  # res = await grpc_client.unary_unary(req)
1726
1816
  # return True
1817
+
1818
+ async def create_algo_order(
1819
+ self,
1820
+ *,
1821
+ params: SpreaderParams,
1822
+ id: Optional[str] = None,
1823
+ trader: Optional[str] = None,
1824
+ ):
1825
+ """
1826
+ Sends an advanced algo order such as the spreader.
1827
+ """
1828
+ grpc_client = await self.core()
1829
+
1830
+ if isinstance(params, SpreaderParams):
1831
+ algo = "SPREADER"
1832
+ else:
1833
+ raise ValueError(
1834
+ "Unsupported algo type. Only SpreaderParams is supported for now."
1835
+ )
1836
+
1837
+ req = CreateAlgoOrderRequest(algo=algo, params=params, id=id, trader=trader)
1838
+ res = await grpc_client.unary_unary(req)
1839
+ return res
architect_py/client.py CHANGED
@@ -56,6 +56,8 @@ class Client:
56
56
  api_key: str,
57
57
  api_secret: str,
58
58
  paper_trading: bool,
59
+ as_user: Optional[str] = None,
60
+ as_role: Optional[str] = None,
59
61
  endpoint: str = "https://app.architect.co",
60
62
  graphql_port: Optional[int] = None,
61
63
  grpc_options: Sequence[tuple[str, Any]] | None = None,
@@ -85,6 +87,8 @@ class Client:
85
87
  api_key=api_key,
86
88
  api_secret=api_secret,
87
89
  paper_trading=paper_trading,
90
+ as_user=as_user,
91
+ as_role=as_role,
88
92
  endpoint=endpoint,
89
93
  graphql_port=graphql_port,
90
94
  grpc_options=grpc_options,
architect_py/client.pyi CHANGED
@@ -9,17 +9,17 @@ from architect_py.graphql_client import GraphQLClient as GraphQLClient
9
9
  from architect_py.graphql_client.exceptions import GraphQLClientGraphQLMultiError as GraphQLClientGraphQLMultiError
10
10
  from architect_py.graphql_client.fragments import ExecutionInfoFields as ExecutionInfoFields, ProductInfoFields as ProductInfoFields
11
11
  from architect_py.grpc.client import GrpcClient as GrpcClient
12
- from architect_py.grpc.models.Orderflow.OrderflowRequest import OrderflowRequestUnannotatedResponseType as OrderflowRequestUnannotatedResponseType, OrderflowRequest_route as OrderflowRequest_route
13
- from architect_py.grpc.models.definitions import AccountIdOrName as AccountIdOrName, AccountWithPermissions as AccountWithPermissions, CandleWidth as CandleWidth, L2BookDiff as L2BookDiff, OrderId as OrderId, OrderSource as OrderSource, OrderType as OrderType, SortTickersBy as SortTickersBy, TraderIdOrEmail as TraderIdOrEmail
12
+ from architect_py.grpc.models.definitions import AccountIdOrName as AccountIdOrName, AccountWithPermissions as AccountWithPermissions, CandleWidth as CandleWidth, L2BookDiff as L2BookDiff, OrderId as OrderId, OrderSource as OrderSource, OrderType as OrderType, SortTickersBy as SortTickersBy, SpreaderParams as SpreaderParams, TraderIdOrEmail as TraderIdOrEmail, TriggerLimitOrderType as TriggerLimitOrderType
13
+ from architect_py.grpc.orderflow import OrderflowChannel as OrderflowChannel
14
14
  from architect_py.grpc.resolve_endpoint import PAPER_GRPC_PORT as PAPER_GRPC_PORT, resolve_endpoint as resolve_endpoint
15
15
  from architect_py.utils.nearest_tick import TickRoundMethod as TickRoundMethod
16
16
  from architect_py.utils.orderbook import update_orderbook_side as update_orderbook_side
17
- from architect_py.utils.pandas import candles_to_dataframe as candles_to_dataframe
17
+ from architect_py.utils.pandas import candles_to_dataframe as candles_to_dataframe, tickers_to_dataframe as tickers_to_dataframe
18
18
  from architect_py.utils.price_bands import price_band_pairs as price_band_pairs
19
19
  from architect_py.utils.symbol_parsing import nominative_expiration as nominative_expiration
20
20
  from datetime import date, datetime
21
21
  from decimal import Decimal
22
- from typing import Any, AsyncGenerator, AsyncIterator, Literal, Sequence, overload
22
+ from typing import Any, AsyncGenerator, Literal, Sequence, overload
23
23
 
24
24
  class Client:
25
25
  """
@@ -37,6 +37,8 @@ class Client:
37
37
  api_key: str | None
38
38
  api_secret: str | None
39
39
  paper_trading: bool
40
+ as_user: str | None
41
+ as_role: str | None
40
42
  graphql_client: GraphQLClient
41
43
  grpc_options: Sequence[tuple[str, Any]] | None
42
44
  grpc_core: GrpcClient | None
@@ -45,7 +47,7 @@ class Client:
45
47
  jwt: str | None
46
48
  jwt_expiration: datetime | None
47
49
  l1_books: dict[Venue, dict[TradableProduct, tuple[L1BookSnapshot, asyncio.Task]]]
48
- def __init__(self, *, api_key: str, api_secret: str, paper_trading: bool, endpoint: str = 'https://app.architect.co', graphql_port: int | None = None, grpc_options: Sequence[tuple[str, Any]] | None = None, event_loop: asyncio.events.AbstractEventLoop | None = None) -> None:
50
+ def __init__(self, *, api_key: str, api_secret: str, paper_trading: bool, as_user: str | None = None, as_role: str | None = None, endpoint: str = 'https://app.architect.co', graphql_port: int | None = None, grpc_options: Sequence[tuple[str, Any]] | None = None, event_loop: asyncio.events.AbstractEventLoop | None = None) -> None:
49
51
  """
50
52
  Create a new Client instance.
51
53
 
@@ -119,6 +121,7 @@ class Client:
119
121
  Returns:
120
122
  (user_id, user_email)
121
123
  """
124
+ def auth_info(self) -> AuthInfoResponse: ...
122
125
  def cpty_status(self, kind: str, instance: str | None = None) -> CptyStatus:
123
126
  """
124
127
  Get cpty status.
@@ -350,6 +353,10 @@ class Client:
350
353
  """
351
354
  Gets the ticker for a symbol.
352
355
  """
356
+ def get_tickers(self, *, venue: Venue, symbols: Sequence[TradableProduct | str] | None = None, include_options: bool = False, sort_by: SortTickersBy | str | None = None, offset: int | None = None, limit: int | None = None, as_dataframe: bool = False) -> Sequence[Ticker] | pd.DataFrame:
357
+ """
358
+ Gets the tickers for a list of symbols.
359
+ """
353
360
  def list_accounts(self) -> list[AccountWithPermissions]:
354
361
  """
355
362
  List accounts for the user that the API key belongs to.
@@ -367,6 +374,13 @@ class Client:
367
374
  account: account uuid or name
368
375
  Examples: "00000000-0000-0000-0000-000000000000", "STONEX:000000/JDoe"
369
376
  '''
377
+ def get_positions(self, accounts: list[str] | None = None, trader: str | None = None) -> dict[str, Decimal]:
378
+ """
379
+ Get positions for the specified symbols.
380
+
381
+ Args:
382
+ symbols: list of symbol strings
383
+ """
370
384
  def get_account_summaries(self, accounts: list[str] | None = None, trader: str | None = None) -> list[AccountSummary]:
371
385
  """
372
386
  Get account summaries for accounts matching the filters.
@@ -451,7 +465,11 @@ class Client:
451
465
  '''
452
466
  def send_limit_order(self, *args, **kwargs) -> Order:
453
467
  '''
454
- @deprecated(reason="Use place_limit_order instead")
468
+ @deprecated(reason="Use place_order instead")
469
+ '''
470
+ def place_limit_order(self, *args, **kwargs) -> Order:
471
+ '''
472
+ @deprecated(reason="Use place_order instead")
455
473
  '''
456
474
  def place_orders(self, order_requests: Sequence[PlaceOrderRequest]) -> list[Order]:
457
475
  """
@@ -482,9 +500,9 @@ class Client:
482
500
  trigger_price=trigger_price,
483
501
  )
484
502
  """
485
- def place_limit_order(self, *, id: OrderId | None = None, symbol: TradableProduct | str, execution_venue: str | None = None, dir: OrderDir | None = None, quantity: Decimal, limit_price: Decimal, order_type: OrderType = ..., time_in_force: TimeInForce = ..., price_round_method: TickRoundMethod | None = None, account: str | None = None, trader: str | None = None, post_only: bool = False, trigger_price: Decimal | None = None, **kwargs: Any) -> Order:
503
+ def place_order(self, *, id: OrderId | None = None, symbol: TradableProduct | str, execution_venue: str | None = None, dir: OrderDir, quantity: Decimal, limit_price: Decimal | None = None, order_type: OrderType = ..., time_in_force: TimeInForce = ..., price_round_method: TickRoundMethod | None = None, account: str | None = None, trader: str | None = None, post_only: bool | None = None, trigger_price: Decimal | None = None, stop_loss: TriggerLimitOrderType | None = None, take_profit_price: Decimal | None = None, **kwargs: Any) -> Order:
486
504
  '''
487
- Sends a regular limit order.
505
+ Sends a regular order.
488
506
 
489
507
  Args:
490
508
  id: in case user wants to generate their own order id, otherwise it will be generated automatically
@@ -503,8 +521,10 @@ class Client:
503
521
  While technically optional, for most order types, the account is required
504
522
  trader: the trader to send the order for, defaults to the user\'s trader
505
523
  for when sending order for another user, not relevant for vast majority of users
506
- post_only: whether the order should be post only, not supported by all exchanges
524
+ post_only: whether the order should be post only, NOT SUPPORTED BY ALL EXCHANGES (e.g. CME)
507
525
  trigger_price: the trigger price for the order, only relevant for stop / take_profit orders
526
+ stop_loss_price: the stop loss price for a bracket order.
527
+ profit_price: the take profit price for a bracket order.
508
528
  Returns:
509
529
  the Order object for the order
510
530
  The order.status should be "PENDING" until the order is "OPEN" / "REJECTED" / "OUT" / "CANCELED" / "STALE"
@@ -549,3 +569,7 @@ class Client:
549
569
  True if all orders were cancelled successfully
550
570
  False if there was an error
551
571
  """
572
+ def create_algo_order(self, *, params: SpreaderParams, id: str | None = None, trader: str | None = None):
573
+ """
574
+ Sends an advanced algo order such as the spreader.
575
+ """