architect-py 5.1.1__py3-none-any.whl → 5.1.2__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.
architect_py/__init__.py CHANGED
@@ -1,6 +1,6 @@
1
1
  # ruff: noqa:I001
2
2
 
3
- __version__ = "5.1.1"
3
+ __version__ = "5.1.2"
4
4
 
5
5
  from .utils.nearest_tick import TickRoundMethod
6
6
  from .async_client import AsyncClient
@@ -13,6 +13,7 @@ from .grpc.models.definitions import (
13
13
  AlgoOrderStatus,
14
14
  CancelStatus,
15
15
  CandleWidth,
16
+ ConnectionStatus,
16
17
  CptyLogoutRequest,
17
18
  DateTimeOrUtc,
18
19
  Deposit,
@@ -269,6 +270,7 @@ __all__ = [
269
270
  "Commodity",
270
271
  "ConfigRequest",
271
272
  "ConfigResponse",
273
+ "ConnectionStatus",
272
274
  "CptyLoginRequest",
273
275
  "CptyLogoutRequest",
274
276
  "CptyRequest",
@@ -43,7 +43,7 @@ from architect_py.grpc.models.Orderflow.OrderflowRequest import (
43
43
  OrderflowRequest_route,
44
44
  OrderflowRequestUnannotatedResponseType,
45
45
  )
46
- from architect_py.grpc.resolve_endpoint import resolve_endpoint
46
+ from architect_py.grpc.resolve_endpoint import PAPER_GRPC_PORT, resolve_endpoint
47
47
  from architect_py.utils.nearest_tick import TickRoundMethod
48
48
  from architect_py.utils.orderbook import update_orderbook_side
49
49
  from architect_py.utils.pandas import candles_to_dataframe
@@ -112,7 +112,7 @@ class AsyncClient:
112
112
  # Sanity check paper trading on prod environments
113
113
  if paper_trading:
114
114
  if grpc_host == "app.architect.co" or grpc_host == "staging.architect.co":
115
- if grpc_port != 10081:
115
+ if grpc_port != PAPER_GRPC_PORT:
116
116
  raise ValueError("Wrong gRPC port for paper trading")
117
117
  if graphql_port is not None and graphql_port != 5678:
118
118
  raise ValueError("Wrong GraphQL port for paper trading")
@@ -184,6 +184,13 @@ class AsyncClient:
184
184
  async def close(self):
185
185
  """
186
186
  Close the gRPC channel and GraphQL client.
187
+
188
+ This fixes the:
189
+ Error in sys.excepthook:
190
+
191
+ Original exception was:
192
+
193
+ One might get when closing the client
187
194
  """
188
195
  if self.grpc_core is not None:
189
196
  await self.grpc_core.close()
@@ -524,34 +531,51 @@ class AsyncClient:
524
531
  if not series_symbol.endswith("Futures"):
525
532
  raise ValueError("series_symbol must end with 'Futures'")
526
533
  res = await self.graphql_client.get_future_series_query(series_symbol)
527
- return res.futures_series
534
+
535
+ today = date.today()
536
+
537
+ futures = [
538
+ future
539
+ for future in res.futures_series
540
+ if (exp := nominative_expiration(future)) is not None and exp > today
541
+ ]
542
+ futures.sort()
543
+
544
+ return futures
528
545
 
529
546
  async def get_front_future(
530
- self, series_symbol: str, venue: str, by_volume: bool = True
531
- ) -> str:
547
+ self, series_symbol: str, venue: Optional[str] = None
548
+ ) -> TradableProduct:
532
549
  """
533
- Gets the future with the most volume in a series.
550
+ Gets the front future.
551
+ ** If the venue is provided, it will return the future with the most volume in that venue**
552
+ Otherwise, will sort by expiration date and return the earliest future.
553
+
554
+ ** Note that this function returns a TradableProduct (ie with a base and a quote)
555
+
534
556
 
535
557
  Args:
536
558
  series_symbol: the futures series
537
559
  e.g. "ES CME Futures" would yield the lead future for the ES series
538
560
  venue: the venue to get the lead future for, e.g. "CME"
539
- by_volume: if True, sort by volume; otherwise sort by expiration date
561
+ ** If the venue is provided, it will return the future with the most volume in that venue**
540
562
 
541
563
  Returns:
542
564
  The lead future symbol
543
565
  """
544
566
  futures = await self.get_futures_series(series_symbol)
545
- if not by_volume:
567
+ if not venue:
546
568
  futures.sort()
547
- return futures[0]
569
+ return TradableProduct(futures[0], "USD")
548
570
  else:
549
571
  grpc_client = await self.marketdata(venue)
550
572
  req = TickersRequest(
551
- symbols=futures, k=SortTickersBy.VOLUME_DESC, venue=venue
573
+ symbols=[TradableProduct(f"{future}/USD") for future in futures],
574
+ k=SortTickersBy.VOLUME_DESC,
575
+ venue=venue,
552
576
  )
553
577
  res: TickersResponse = await grpc_client.unary_unary(req)
554
- return res.tickers[0].symbol
578
+ return TradableProduct(res.tickers[0].symbol)
555
579
 
556
580
  @staticmethod
557
581
  def get_expiration_from_CME_name(name: str) -> Optional[date]:
@@ -1358,8 +1382,7 @@ class AsyncClient:
1358
1382
 
1359
1383
  Example:
1360
1384
  ```python
1361
- request = SubscribeOrderflowRequest.new()
1362
- async for of in client.subscribe_orderflow_stream(request):
1385
+ async for of in client.stream_orderflow(account, execution_venue, trader):
1363
1386
  print(of)
1364
1387
  ```
1365
1388
  """
@@ -1396,6 +1419,48 @@ class AsyncClient:
1396
1419
  """
1397
1420
  return await self.place_limit_order(*args, **kwargs)
1398
1421
 
1422
+ async def place_orders(
1423
+ self, order_requests: Sequence[PlaceOrderRequest]
1424
+ ) -> list[Order]:
1425
+ """
1426
+ A low level function to place multiple orders in a single function.
1427
+
1428
+ This function does NOT check the validity of the parameters, so it is the user's responsibility
1429
+ to ensure that the orders are valid and will not be rejected by the OMS.
1430
+
1431
+ Args:
1432
+ order_request: the PlaceOrderRequest containing the orders to place
1433
+
1434
+
1435
+ Example of a PlaceOrderRequest:
1436
+ order_request: PlaceOrderRequest = PlaceOrderRequest.new(
1437
+ dir=dir,
1438
+ quantity=quantity,
1439
+ symbol=symbol,
1440
+ time_in_force=time_in_force,
1441
+ limit_price=limit_price,
1442
+ order_type=order_type,
1443
+ account=account,
1444
+ id=id,
1445
+ parent_id=None,
1446
+ source=OrderSource.API,
1447
+ trader=trader,
1448
+ execution_venue=execution_venue,
1449
+ post_only=post_only,
1450
+ trigger_price=trigger_price,
1451
+ )
1452
+ """
1453
+ grpc_client = await self.core()
1454
+
1455
+ res = await asyncio.gather(
1456
+ *[
1457
+ grpc_client.unary_unary(order_request)
1458
+ for order_request in order_requests
1459
+ ]
1460
+ )
1461
+
1462
+ return res
1463
+
1399
1464
  async def place_limit_order(
1400
1465
  self,
1401
1466
  *,
@@ -1626,11 +1691,13 @@ class AsyncClient:
1626
1691
  if cancel.reject_reason is not None:
1627
1692
  return False
1628
1693
  return True
1629
- grpc_client = await self.core()
1630
- req = CancelAllOrdersRequest(
1631
- account=account,
1632
- execution_venue=execution_venue,
1633
- trader=trader,
1634
- )
1635
- res = await grpc_client.unary_unary(req)
1636
- return res
1694
+ # grpc_client = await self.core()
1695
+
1696
+ # req = CancelAllOrdersRequest(
1697
+ # id=str(uuid.uuid4()), # Unique ID for the request
1698
+ # account=account,
1699
+ # execution_venue=execution_venue,
1700
+ # trader=trader,
1701
+ # )
1702
+ # res = await grpc_client.unary_unary(req)
1703
+ # return True
architect_py/client.pyi CHANGED
@@ -11,7 +11,7 @@ from architect_py.graphql_client.fragments import ExecutionInfoFields as Executi
11
11
  from architect_py.grpc.client import GrpcClient as GrpcClient
12
12
  from architect_py.grpc.models.Orderflow.OrderflowRequest import OrderflowRequestUnannotatedResponseType as OrderflowRequestUnannotatedResponseType, OrderflowRequest_route as OrderflowRequest_route
13
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
14
- from architect_py.grpc.resolve_endpoint import resolve_endpoint as resolve_endpoint
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
17
  from architect_py.utils.pandas import candles_to_dataframe as candles_to_dataframe
@@ -57,6 +57,13 @@ class Client:
57
57
  def close(self) -> None:
58
58
  """
59
59
  Close the gRPC channel and GraphQL client.
60
+
61
+ This fixes the:
62
+ Error in sys.excepthook:
63
+
64
+ Original exception was:
65
+
66
+ One might get when closing the client
60
67
  """
61
68
  def refresh_jwt(self, force: bool = False):
62
69
  """
@@ -210,15 +217,20 @@ class Client:
210
217
  Returns:
211
218
  List of futures products
212
219
  '''
213
- def get_front_future(self, series_symbol: str, venue: str, by_volume: bool = True) -> str:
220
+ def get_front_future(self, series_symbol: str, venue: str | None = None) -> TradableProduct:
214
221
  '''
215
- Gets the future with the most volume in a series.
222
+ Gets the front future.
223
+ ** If the venue is provided, it will return the future with the most volume in that venue**
224
+ Otherwise, will sort by expiration date and return the earliest future.
225
+
226
+ ** Note that this function returns a TradableProduct (ie with a base and a quote)
227
+
216
228
 
217
229
  Args:
218
230
  series_symbol: the futures series
219
231
  e.g. "ES CME Futures" would yield the lead future for the ES series
220
232
  venue: the venue to get the lead future for, e.g. "CME"
221
- by_volume: if True, sort by volume; otherwise sort by expiration date
233
+ ** If the venue is provided, it will return the future with the most volume in that venue**
222
234
 
223
235
  Returns:
224
236
  The lead future symbol
@@ -436,6 +448,35 @@ class Client:
436
448
  '''
437
449
  @deprecated(reason="Use place_limit_order instead")
438
450
  '''
451
+ def place_orders(self, order_requests: Sequence[PlaceOrderRequest]) -> list[Order]:
452
+ """
453
+ A low level function to place multiple orders in a single function.
454
+
455
+ This function does NOT check the validity of the parameters, so it is the user's responsibility
456
+ to ensure that the orders are valid and will not be rejected by the OMS.
457
+
458
+ Args:
459
+ order_request: the PlaceOrderRequest containing the orders to place
460
+
461
+
462
+ Example of a PlaceOrderRequest:
463
+ order_request: PlaceOrderRequest = PlaceOrderRequest.new(
464
+ dir=dir,
465
+ quantity=quantity,
466
+ symbol=symbol,
467
+ time_in_force=time_in_force,
468
+ limit_price=limit_price,
469
+ order_type=order_type,
470
+ account=account,
471
+ id=id,
472
+ parent_id=None,
473
+ source=OrderSource.API,
474
+ trader=trader,
475
+ execution_venue=execution_venue,
476
+ post_only=post_only,
477
+ trigger_price=trigger_price,
478
+ )
479
+ """
439
480
  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:
440
481
  '''
441
482
  Sends a regular limit order.
@@ -3,46 +3,45 @@
3
3
 
4
4
  from __future__ import annotations
5
5
 
6
- from typing import Annotated, Optional
6
+ from typing import Annotated, Dict, Optional
7
7
 
8
8
  from msgspec import Meta, Struct
9
9
 
10
+ from .. import definitions
11
+
10
12
 
11
13
  class CptyStatus(Struct, omit_defaults=True):
12
14
  connected: bool
15
+ connections: Dict[str, definitions.ConnectionStatus]
13
16
  kind: str
14
- last_heartbeat: Annotated[int, Meta(description="UNIX epoch time or -1 for never")]
15
- """
16
- UNIX epoch time or -1 for never
17
- """
18
- last_heartbeat_stale_threshold: Annotated[
19
- int, Meta(description="Stale threshold in seconds, or -1 for never stale")
20
- ]
17
+ stale: bool
18
+ instance: Optional[str] = None
19
+ logged_in: Optional[
20
+ Annotated[Optional[bool], Meta(description="Not applicable to cpty if None")]
21
+ ] = None
21
22
  """
22
- Stale threshold in seconds, or -1 for never stale
23
+ Not applicable to cpty if None
23
24
  """
24
- logged_in: bool
25
- instance: Optional[str] = None
26
25
 
27
26
  # Constructor that takes all field titles as arguments for convenience
28
27
  @classmethod
29
28
  def new(
30
29
  cls,
31
30
  connected: bool,
31
+ connections: Dict[str, definitions.ConnectionStatus],
32
32
  kind: str,
33
- last_heartbeat: int,
34
- last_heartbeat_stale_threshold: int,
35
- logged_in: bool,
33
+ stale: bool,
36
34
  instance: Optional[str] = None,
35
+ logged_in: Optional[bool] = None,
37
36
  ):
38
37
  return cls(
39
38
  connected,
39
+ connections,
40
40
  kind,
41
- last_heartbeat,
42
- last_heartbeat_stale_threshold,
43
- logged_in,
41
+ stale,
44
42
  instance,
43
+ logged_in,
45
44
  )
46
45
 
47
46
  def __str__(self) -> str:
48
- return f"CptyStatus(connected={self.connected},kind={self.kind},last_heartbeat={self.last_heartbeat},last_heartbeat_stale_threshold={self.last_heartbeat_stale_threshold},logged_in={self.logged_in},instance={self.instance})"
47
+ return f"CptyStatus(connected={self.connected},connections={self.connections},kind={self.kind},stale={self.stale},instance={self.instance},logged_in={self.logged_in})"
@@ -22,7 +22,18 @@ class Order(Struct, omit_defaults=True):
22
22
  src: Annotated[definitions.OrderSource, Meta(title="source")]
23
23
  tif: Annotated[TimeInForce, Meta(title="time_in_force")]
24
24
  tn: Annotated[int, Meta(ge=0, title="recv_time_ns")]
25
- ts: Annotated[int, Meta(title="recv_time")]
25
+ ts: Annotated[
26
+ int,
27
+ Meta(
28
+ description="Timestamp that the Architect OMS first received the order.\n\nFor reconciled orders, this could be very far in the future relative to the exchange order timestamp.",
29
+ title="recv_time",
30
+ ),
31
+ ]
32
+ """
33
+ Timestamp that the Architect OMS first received the order.
34
+
35
+ For reconciled orders, this could be very far in the future relative to the exchange order timestamp.
36
+ """
26
37
  u: Annotated[definitions.UserId, Meta(title="trader")]
27
38
  ve: Annotated[str, Meta(title="execution_venue")]
28
39
  xq: Annotated[Decimal, Meta(title="filled_quantity")]
@@ -182,6 +182,47 @@ class CandleWidth(int, Enum):
182
182
  OneDay = 86400
183
183
 
184
184
 
185
+ class ConnectionStatus(Struct, omit_defaults=True):
186
+ connected: bool
187
+ last_heartbeat: Annotated[int, Meta(description="UNIX epoch time or -1 for never")]
188
+ """
189
+ UNIX epoch time or -1 for never
190
+ """
191
+ last_heartbeat_stale_threshold: Annotated[
192
+ int, Meta(description="Stale threshold in seconds, or -1 for never stale")
193
+ ]
194
+ """
195
+ Stale threshold in seconds, or -1 for never stale
196
+ """
197
+ logged_in: Optional[
198
+ Annotated[
199
+ Optional[bool], Meta(description="Not applicable to connection if None")
200
+ ]
201
+ ] = None
202
+ """
203
+ Not applicable to connection if None
204
+ """
205
+
206
+ # Constructor that takes all field titles as arguments for convenience
207
+ @classmethod
208
+ def new(
209
+ cls,
210
+ connected: bool,
211
+ last_heartbeat: int,
212
+ last_heartbeat_stale_threshold: int,
213
+ logged_in: Optional[bool] = None,
214
+ ):
215
+ return cls(
216
+ connected,
217
+ last_heartbeat,
218
+ last_heartbeat_stale_threshold,
219
+ logged_in,
220
+ )
221
+
222
+ def __str__(self) -> str:
223
+ return f"ConnectionStatus(connected={self.connected},last_heartbeat={self.last_heartbeat},last_heartbeat_stale_threshold={self.last_heartbeat_stale_threshold},logged_in={self.logged_in})"
224
+
225
+
185
226
  class CptyLogoutRequest(Struct, omit_defaults=True):
186
227
  pass
187
228
 
@@ -7,6 +7,8 @@ import dns.asyncresolver
7
7
  import dns.resolver
8
8
  from dns.rdtypes.IN.SRV import SRV
9
9
 
10
+ PAPER_GRPC_PORT = 10081
11
+
10
12
 
11
13
  async def resolve_endpoint(
12
14
  endpoint: str, paper_trading: bool = True
@@ -72,6 +74,6 @@ async def resolve_endpoint(
72
74
  port = record.port
73
75
  if paper_trading:
74
76
  if "app.architect.co" in host or "staging.architect.co" in host:
75
- port = 10080
77
+ port = PAPER_GRPC_PORT
76
78
 
77
79
  return host, port, use_ssl
@@ -29,7 +29,7 @@ def is_truthy(value: str | None) -> bool:
29
29
  return value is not None and value.lower() in ("1", "true", "yes")
30
30
 
31
31
 
32
- class TestEnvironment:
32
+ class GetEnvironment:
33
33
  @classmethod
34
34
  def from_env(cls):
35
35
  endpoint = os.getenv("ARCHITECT_ENDPOINT")
@@ -80,7 +80,7 @@ class TestEnvironment:
80
80
  @pytest_asyncio.fixture
81
81
  async def async_client() -> AsyncClient:
82
82
  load_dotenv()
83
- test_env = TestEnvironment.from_env()
83
+ test_env = GetEnvironment.from_env()
84
84
  async_client = await AsyncClient.connect(
85
85
  api_key=test_env.api_key,
86
86
  api_secret=test_env.api_secret,
@@ -18,6 +18,7 @@ async def test_get_market_status(async_client: AsyncClient, venue: str, symbol:
18
18
  assert market_status is not None
19
19
  # CR alee: this is broken upstream
20
20
  # assert market_status.is_trading
21
+ await async_client.close()
21
22
 
22
23
 
23
24
  @pytest.mark.asyncio
@@ -35,6 +36,7 @@ async def test_get_historical_candles(
35
36
  symbol, venue, CandleWidth.OneHour, start - timedelta(hours=24), start
36
37
  )
37
38
  assert len(candles) > 0
39
+ await async_client.close()
38
40
 
39
41
 
40
42
  @pytest.mark.asyncio
@@ -49,6 +51,7 @@ async def test_get_l1_book_snapshot(async_client: AsyncClient, venue: str, symbo
49
51
  assert snap is not None
50
52
  assert snap.best_bid is not None
51
53
  assert snap.best_ask is not None
54
+ await async_client.close()
52
55
 
53
56
 
54
57
  @pytest.mark.asyncio
@@ -63,6 +66,7 @@ async def test_get_l2_book_snapshot(async_client: AsyncClient, venue: str, symbo
63
66
  assert snap is not None
64
67
  assert len(snap.bids) > 0
65
68
  assert len(snap.asks) > 0
69
+ await async_client.close()
66
70
 
67
71
 
68
72
  @pytest.mark.asyncio
@@ -76,6 +80,7 @@ async def test_get_ticker(async_client: AsyncClient, venue: str, symbol: str):
76
80
  ticker = await async_client.get_ticker(symbol, venue)
77
81
  assert ticker is not None
78
82
  assert ticker.last_price is not None
83
+ await async_client.close()
79
84
 
80
85
 
81
86
  @pytest.mark.asyncio
@@ -96,6 +101,7 @@ async def test_stream_l1_book_snapshots(
96
101
  i += 1
97
102
  if i > 20:
98
103
  break
104
+ await async_client.close()
99
105
 
100
106
 
101
107
  @pytest.mark.asyncio
@@ -132,6 +138,7 @@ async def test_stream_l2_book_updates(
132
138
  i += 1
133
139
  if i > 20:
134
140
  break
141
+ await async_client.close()
135
142
 
136
143
 
137
144
  @pytest.mark.asyncio
@@ -149,6 +156,8 @@ async def test_stream_trades(async_client: AsyncClient, venue: str, symbol: str)
149
156
  if i > 20:
150
157
  break
151
158
 
159
+ await async_client.close()
160
+
152
161
 
153
162
  # @pytest.mark.asyncio
154
163
  # @pytest.mark.parametrize(
@@ -34,3 +34,5 @@ async def test_place_limit_order(async_client: AsyncClient):
34
34
  assert order is not None
35
35
  await asyncio.sleep(1)
36
36
  await async_client.cancel_order(order.id)
37
+
38
+ await async_client.close()
@@ -21,3 +21,5 @@ async def test_get_account_summary(async_client: AsyncClient):
21
21
  assert summary.balances is not None
22
22
  assert len(summary.balances) > 0
23
23
  assert summary.positions is not None
24
+
25
+ await async_client.close()
@@ -67,6 +67,26 @@ async def test_get_cme_future_from_root_month_year(async_client: AsyncClient):
67
67
  "future base name does not match regex"
68
68
  )
69
69
 
70
+ await async_client.close()
71
+
72
+
73
+ @pytest.mark.asyncio
74
+ async def test_get_front_future(async_client: AsyncClient):
75
+ """
76
+ Test that we can get the front future for a given series.
77
+ """
78
+ series_name = "ES CME Futures"
79
+ front_future = await async_client.get_front_future(series_name, "CME")
80
+ assert front_future is not None, "front future is None"
81
+ assert re.match(r"ES \d* CME Future", front_future), (
82
+ "front future base name does not match regex"
83
+ )
84
+ front_future = await async_client.get_front_future(series_name)
85
+ assert front_future is not None, "front future is None"
86
+ assert re.match(r"ES \d* CME Future", front_future), (
87
+ "front future base name does not match regex"
88
+ )
89
+
70
90
 
71
91
  def add_one_month_to_datetime(dt: datetime):
72
92
  if dt.month == 12:
@@ -1,14 +1,12 @@
1
- import pytest
2
1
  from dotenv import load_dotenv
3
2
 
4
3
  from architect_py import Client
5
- from architect_py.tests.conftest import TestEnvironment
4
+ from architect_py.tests.conftest import GetEnvironment
6
5
 
7
6
 
8
- @pytest.mark.asyncio
9
- async def test_sync_client():
7
+ def test_sync_client():
10
8
  load_dotenv()
11
- test_env = TestEnvironment.from_env()
9
+ test_env = GetEnvironment.from_env()
12
10
  client = Client(
13
11
  api_key=test_env.api_key,
14
12
  api_secret=test_env.api_secret,
@@ -21,3 +19,22 @@ async def test_sync_client():
21
19
 
22
20
  assert symbols is not None
23
21
  assert len(symbols) > 20
22
+
23
+ client.close()
24
+
25
+
26
+ def test_creation():
27
+ load_dotenv()
28
+ test_env = GetEnvironment.from_env()
29
+ client = Client(
30
+ api_key=test_env.api_key,
31
+ api_secret=test_env.api_secret,
32
+ paper_trading=True,
33
+ endpoint="app.architect.co",
34
+ )
35
+
36
+ symbols = client.list_symbols(marketdata="CME")
37
+
38
+ assert symbols is not None
39
+ assert len(symbols) > 20
40
+ client.close()
@@ -1,9 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: architect-py
3
- Version: 5.1.1
3
+ Version: 5.1.2
4
4
  Summary: Python SDK for the Architect trading platform and brokerage.
5
5
  Author-email: "Architect Financial Technologies, Inc." <hello@architect.co>
6
6
  License-Expression: Apache-2.0
7
+ Project-URL: Homepage, https://www.architect.co/brokerage/overview
8
+ Project-URL: Documentation, https://docs.architect.co
9
+ Project-URL: Repository, https://github.com/architect-xyz/architect-py
7
10
  Classifier: Programming Language :: Python :: 3
8
11
  Classifier: Operating System :: OS Independent
9
12
  Classifier: Development Status :: 5 - Production/Stable
@@ -1,7 +1,7 @@
1
- architect_py/__init__.py,sha256=ZkGr9D_FPHH7OjiqyWQm14BCOLUaM_AiDAes4vX3vOk,16075
2
- architect_py/async_client.py,sha256=1EO4TGdO1C2AQg0a2I2Q42kPToclziPaWYRX2Mc2W1U,59457
1
+ architect_py/__init__.py,sha256=0AUu9lue-XJfjKuCe_CgnQXdOlwhgBSwRLFc5yeU4LI,16121
2
+ architect_py/async_client.py,sha256=0cpFvY36YquphGqD7z7hdWEb59OyuJiDYgrHzfykuC4,61646
3
3
  architect_py/client.py,sha256=yC0OVzz6uXUMdIIhqqR3GyVuBApYSm00AS51QM8xPak,4911
4
- architect_py/client.pyi,sha256=SpWAEY7Ff2FVrbOtvmp37-vanPkef6gdtu1lZ2iw3iU,23017
4
+ architect_py/client.pyi,sha256=VJrg85KaupKzWY4BvS86xREs67S_TFrItNirczYLrgc,24565
5
5
  architect_py/common_types/__init__.py,sha256=fzOdIlKGWVN9V2Onc5z1v2bpvtZ4H9RSFA9ymJcBi3k,197
6
6
  architect_py/common_types/order_dir.py,sha256=ebyWTcXzJWrotkc2D9wNGc6JXbE5I3NLLuAz3I7FTZ8,2191
7
7
  architect_py/common_types/time_in_force.py,sha256=gEDYcNp014Eeb98zJDytiV0hGxHu_QsQndeM6Hk0Wa8,3132
@@ -24,11 +24,11 @@ architect_py/graphql_client/search_symbols_query.py,sha256=hbGa6gF-gMWtRYQm2vlCT
24
24
  architect_py/graphql_client/user_id_query.py,sha256=tWKJJLgEINzd8e7rYlGklQCnwcwHzYFpCGQvhxQGX20,334
25
25
  architect_py/grpc/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
26
26
  architect_py/grpc/client.py,sha256=1_2JuxFoIVRtcuczkuI-uF7zy-AqSYehf5N3E9Gy4Jg,3406
27
- architect_py/grpc/resolve_endpoint.py,sha256=cUElbLmOerPDZJtseP9AWX1Ee3kdZ3XNb3FpgLTHZIc,2586
27
+ architect_py/grpc/resolve_endpoint.py,sha256=r_PBWANIJJ47N5uyPcnefZ21ZE1-mzgACfCBfQpekg8,2621
28
28
  architect_py/grpc/server.py,sha256=Abmdfe1eYbctVgzoJYBBBLpd7UD70FbYQLtJImSyRzs,1934
29
29
  architect_py/grpc/utils.py,sha256=5sykLExUNZbcQHcxLCCM9DdOOiJJZcpputGrDtaMifY,667
30
30
  architect_py/grpc/models/__init__.py,sha256=RrTLZvU7mNykDNp1oOm4-dekzab9ugIXd_my7Sm0Vx4,9153
31
- architect_py/grpc/models/definitions.py,sha256=R7saySbcFH_OADDO-aWZqlJmF6hjWY-TxopDX_Mu3qk,75743
31
+ architect_py/grpc/models/definitions.py,sha256=sGc8KYz4Hki6X3Kvr-Qe6iVnTaQC47wwJP3NeEPT5cQ,77014
32
32
  architect_py/grpc/models/Accounts/AccountsRequest.py,sha256=1a88cltSebOb53EdJ0hKEGR7FlmBiibrCtGzLTKqDBY,1524
33
33
  architect_py/grpc/models/Accounts/AccountsResponse.py,sha256=DlXbkd3UbRybblBAfokw-K6nRvLNZgqz7cc0EKiW1zI,636
34
34
  architect_py/grpc/models/Accounts/__init__.py,sha256=sIyaEvJdP-VmGTGPPqZuRjKn4bc7NUClJ76Gd5uq-5s,57
@@ -65,7 +65,7 @@ architect_py/grpc/models/Core/RestartCptyResponse.py,sha256=aCyJfucfFGHieGURjEej
65
65
  architect_py/grpc/models/Core/__init__.py,sha256=sIyaEvJdP-VmGTGPPqZuRjKn4bc7NUClJ76Gd5uq-5s,57
66
66
  architect_py/grpc/models/Cpty/CptyRequest.py,sha256=eWBayFNnhAZgszneP7f6hDvzQ82JQrFLDSeOPAyiRlg,1711
67
67
  architect_py/grpc/models/Cpty/CptyResponse.py,sha256=UObbkmbtD3sWWklml4hx-Ggc1RaI1v1-J5QW9doWpe4,2865
68
- architect_py/grpc/models/Cpty/CptyStatus.py,sha256=QArnE_viYbiKLW5gctN2Pdy-R0lmKhC3bDiGPPKPjgw,1423
68
+ architect_py/grpc/models/Cpty/CptyStatus.py,sha256=UG8LkBWyQ8oJfxU22iJD5ieZRqmuyIqJxToO6Li5RTc,1281
69
69
  architect_py/grpc/models/Cpty/CptyStatusRequest.py,sha256=5eYYL2L5TmFKgoUuAInvFS0qlSh7YKw4j05Z9ftpxaA,1027
70
70
  architect_py/grpc/models/Cpty/CptysRequest.py,sha256=th1vto4vclExgZD4HwXaNy87amRP2oM1ix4WLetnIW8,801
71
71
  architect_py/grpc/models/Cpty/CptysResponse.py,sha256=cQiRp3VEewfcCKRxqdXpMT2EEZujO3h9LZtHATBDPtk,568
@@ -118,7 +118,7 @@ architect_py/grpc/models/Oms/CancelAllOrdersResponse.py,sha256=YM1H_nrV4RhpLMEwS
118
118
  architect_py/grpc/models/Oms/CancelOrderRequest.py,sha256=0yJysCf0IcwUevEqVnAkIm-OkOAbp_vOwh1p1ljSsp8,1939
119
119
  architect_py/grpc/models/Oms/OpenOrdersRequest.py,sha256=5Uv9ndI2WqRJgOWNLeKcIV8czb0ff6wHUW0gokeBktg,1804
120
120
  architect_py/grpc/models/Oms/OpenOrdersResponse.py,sha256=HT4YXjbbwdp2rLLXxoetF33DGe2j403soMLme2p18ts,592
121
- architect_py/grpc/models/Oms/Order.py,sha256=wcLx5lK_xF3z99Ix1Q1j43Mjk4zvoyBGwgFuYmOB-5E,10257
121
+ architect_py/grpc/models/Oms/Order.py,sha256=jjNSxHO8kdc67xhUdiRmsOdvISQPgtYeQ_pm4H3E5dg,10680
122
122
  architect_py/grpc/models/Oms/PendingCancelsRequest.py,sha256=jdbBOpCHBlZFAZfF6urJZXXa5Dr5cTRR3AJ9ss4rY6E,1620
123
123
  architect_py/grpc/models/Oms/PendingCancelsResponse.py,sha256=mWRNRDa489Vdg-r7dJMOmfOO8l57yg8lBMynBDcY60A,628
124
124
  architect_py/grpc/models/Oms/PlaceOrderRequest.py,sha256=se2iSEC_TrL5y-m_CRJkk9jsYLFOGnE57QFwz4Dqtck,8317
@@ -154,35 +154,35 @@ architect_py/grpc/models/Symbology/UploadSymbologyRequest.py,sha256=XRMC6W6LLG0d
154
154
  architect_py/grpc/models/Symbology/UploadSymbologyResponse.py,sha256=LM6iHjta4yZY784qMR5etG9gKjiBsBCntZqAcmaOHac,444
155
155
  architect_py/grpc/models/Symbology/__init__.py,sha256=sIyaEvJdP-VmGTGPPqZuRjKn4bc7NUClJ76Gd5uq-5s,57
156
156
  architect_py/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
157
- architect_py/tests/conftest.py,sha256=qZYHMQtmPuHKJ9TiYng6Z3RfwFI0v2KKVmoJRes1j8c,3517
157
+ architect_py/tests/conftest.py,sha256=XCeq44muZi6i9CxOe9lH7rCmt5pQViWJ_25gSowrLP0,3515
158
158
  architect_py/tests/test_book_building.py,sha256=biqs8X9bw1YSb6mrCDS-ELesdD-P5F6bE3MYXP0BeQ4,1236
159
159
  architect_py/tests/test_encoding.py,sha256=J61Lk2CDIqgdcsj0-KYBZweYh5EQ4XAXsRyM0fdMqfU,1085
160
- architect_py/tests/test_marketdata.py,sha256=mhYIiNhmsY706PQD3Nvto0l4uDl5dPHn8BcqGPgFCA8,4714
161
- architect_py/tests/test_order_entry.py,sha256=7YKy1tT6XbNbEQFHcrhPelrD8MQnhqA2IvCIK23lAtk,1147
160
+ architect_py/tests/test_marketdata.py,sha256=W26OrL51ONAclBjBcm7trS1QPXtLLjdgnsbDR2kqtIw,4963
161
+ architect_py/tests/test_order_entry.py,sha256=eD3YrRKTpsJqAI5ahzk4J8uPKXby_XLYIcv_ZaqDKRA,1179
162
162
  architect_py/tests/test_orderflow.py,sha256=PRCr4Yzaif9OG0Eq1zxCUpTfFsHA3WEWSBiETRDvnIE,1038
163
- architect_py/tests/test_portfolio_management.py,sha256=LPlkLP2SllLPm0Un7OptfVo96uqiDI7-osTaHxH5m54,677
163
+ architect_py/tests/test_portfolio_management.py,sha256=Q4pburTDJ53hrq2_aRbNAOG3nwbCEsgZQGbI_AMHLxE,709
164
164
  architect_py/tests/test_rounding.py,sha256=cAQ1-tWOVgxENt0Fzs9YcFDdDDYmCtOHvAA_w76wy9g,1417
165
- architect_py/tests/test_symbology.py,sha256=892FN_FGwE8t4lVQtUMGKav69MGzHACeF5RAYrAEdBw,2707
166
- architect_py/tests/test_sync_client.py,sha256=pQYa4arVako7TqNIRL4M9YLMgMBGDMx9mqgHyZMU-lI,588
165
+ architect_py/tests/test_symbology.py,sha256=74fbUgoycuViMHHnurE2Dnfao75wWu_cmQMyU5XQcdY,3436
166
+ architect_py/tests/test_sync_client.py,sha256=teaHrp-CMpKIDsGPdnyxvmuW_a3hgFftnsnPsFHz9Tw,946
167
167
  architect_py/utils/nearest_tick.py,sha256=i1cCGMSi-sP4Grbp0RCwEsoLzMWN9iC6gPMBm2daDWM,4810
168
168
  architect_py/utils/nearest_tick_2.py,sha256=f-o6b73Mo8epCIaOYBS9F0k_6UHUDSVG1N_VWg7iFBU,3641
169
169
  architect_py/utils/orderbook.py,sha256=JM02NhHbmK3sNaS2Ara8FBY4TvKvtMIzJW1oVd8KC3s,1004
170
170
  architect_py/utils/pandas.py,sha256=QHz2ynj4T92FobuzRaNoH3ypArHoSDCiGtZ3PVXJ2vo,1017
171
171
  architect_py/utils/price_bands.py,sha256=j7ioSA3dx025CD5E2Vg7XQvmjPvxQb-gzQBfQTovpTw,21874
172
172
  architect_py/utils/symbol_parsing.py,sha256=OjJzk2c6QU2s0aJMSyVEzlWD5Vy-RlakJVW7jNHVDJk,845
173
- architect_py-5.1.1.dist-info/licenses/LICENSE,sha256=6P0_5gYN8iPWPZeqA9nxiO3tRQmcSA1ijAVR7C8j1SI,11362
173
+ architect_py-5.1.2.dist-info/licenses/LICENSE,sha256=6P0_5gYN8iPWPZeqA9nxiO3tRQmcSA1ijAVR7C8j1SI,11362
174
174
  examples/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
175
- examples/book_subscription.py,sha256=eyXEt23dTyyArXt7N7C-XdRmLFSdZSntOYZaL0W-PBU,1440
176
- examples/candles.py,sha256=AicLj6d-yUVbZVX-mQUBJkI6KEoJeC-BErvxD9-8Z8Y,735
175
+ examples/book_subscription.py,sha256=W7pldoHIPOclhxhxFvVmsBTB1wx1aS4OgAg0EFXeAzA,1461
176
+ examples/candles.py,sha256=Vc1IgwgFPmpAtURdNXGhgdL82y_JNcryADE30r3vVY0,756
177
177
  examples/common.py,sha256=K2ppu4vdlTNsL1oX5RDrNKczljfPVOTPzDia1Abrppg,2987
178
178
  examples/external_cpty.py,sha256=xxGXONXwoWIS8ys0SgxHLSmntAi1BlwV2NR9WD1kvpc,2527
179
- examples/funding_rate_mean_reversion_algo.py,sha256=iP3bghdYMzERfaJMs_QJZ-e3SUVhLgHLNPvOf2sDkIQ,6482
180
- examples/order_sending.py,sha256=NAde_HOHPKz79M1UJQm2ABHwrdA2CLGWxqz_JF7d4mQ,3287
181
- examples/stream_l1_marketdata.py,sha256=Oi7ovb0i4hTdWfkhcRYUJLW5Ou64JxWzZMGhqSL8f_I,707
182
- examples/stream_l2_marketdata.py,sha256=e5d0z4Hft3X51oZsPwJbb_5VG2iDfCMlbbFv3qOkma4,1070
183
- examples/trades.py,sha256=AGKX8g7Xaf5r-KPWEeeAfL7XxoVeh8WsZvaWW5kBuMg,540
184
- examples/tutorial_async.py,sha256=mNwqD3OQnNZ97YnVJWBsp2IimCB8fRrrSSDTPVxNLwo,2625
185
- examples/tutorial_sync.py,sha256=tnUJNOTGeR4UdtLBtH3vYjgFU9sX_-bVucgXIDJ02ek,2812
179
+ examples/funding_rate_mean_reversion_algo.py,sha256=3wy9SsQ5brYRRyzEAl6IV5g-MGFSfYInmHN0T50jzOg,6502
180
+ examples/order_sending.py,sha256=1OV5YSmEVXqeuzLsUWmwnjsdsLFNUdnmwdmdwnKuIYI,3313
181
+ examples/stream_l1_marketdata.py,sha256=6Hkfacbexmgj7n-zT7bIyGksghSl5asrDErtRcjAn2E,728
182
+ examples/stream_l2_marketdata.py,sha256=ZeQlap7YeLg19IdAHnSc95IFFaQ7PgCY2S0MZY4RIvY,1091
183
+ examples/trades.py,sha256=ikMvpLy_b5Y4_2URhkAgdOTzAEC-DAAKilUZenw_J_c,561
184
+ examples/tutorial_async.py,sha256=paanYeCyydHVE_srO-lxj9tpmf4r535dhYaXUV95SN8,2646
185
+ examples/tutorial_sync.py,sha256=G6jsxlpxnaewll4XCER6AYMCAK-1Z_A0Zef4D9CKGvw,2824
186
186
  scripts/add_imports_to_inits.py,sha256=bryhz6RpKAJsSieVMnXnRyLp8evNkpOsNUkBUPkk1WQ,4518
187
187
  scripts/correct_sync_interface.py,sha256=O8qxSqNSNIL8KrgZ4C8rjs_pUCdcA1WeqKAggM2DINw,4056
188
188
  scripts/generate_functions_md.py,sha256=-rVRhbHlDodGH2a32UCsMLIpgXtDvOhBmkHa0RqDpCA,6232
@@ -191,7 +191,7 @@ scripts/preprocess_grpc_schema.py,sha256=p9LdoMZzixBSsVx7Dy3_8uJzOy_QwCoVMkAABQK
191
191
  scripts/prune_graphql_schema.py,sha256=hmfw5FD_iKGKMFkq6H1neZiXXtljFFrOwi2fiusTWE4,6210
192
192
  templates/exceptions.py,sha256=tIHbiO5Q114h9nPwJXsgHvW_bERLwxuNp9Oj41p6t3A,2379
193
193
  templates/juniper_base_client.py,sha256=B8QF4IFSwqBK5UY2aFPbSdYnX9bcwnlxLK4ojPRaW0E,12705
194
- architect_py-5.1.1.dist-info/METADATA,sha256=eOucwfQ94znxJ8098B2UPOVd5FCu85J6DKqbXRvxbDc,2367
195
- architect_py-5.1.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
196
- architect_py-5.1.1.dist-info/top_level.txt,sha256=UjtO97OACFQ9z5MzS-X2wBlt5Ovk1vxakQPKfokI454,40
197
- architect_py-5.1.1.dist-info/RECORD,,
194
+ architect_py-5.1.2.dist-info/METADATA,sha256=9Kut96FHWLTstvnSxNhaf5bvSCdI1JzzY4BettvbdBk,2559
195
+ architect_py-5.1.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
196
+ architect_py-5.1.2.dist-info/top_level.txt,sha256=UjtO97OACFQ9z5MzS-X2wBlt5Ovk1vxakQPKfokI454,40
197
+ architect_py-5.1.2.dist-info/RECORD,,
@@ -46,6 +46,8 @@ async def main():
46
46
  except ValidationError as e:
47
47
  print(e)
48
48
 
49
+ await c.close()
50
+
49
51
 
50
52
  if __name__ == "__main__":
51
53
  asyncio.run(main())
examples/candles.py CHANGED
@@ -24,5 +24,7 @@ async def main():
24
24
  print(e.status_code)
25
25
  print(e.response.json())
26
26
 
27
+ await c.close()
28
+
27
29
 
28
30
  asyncio.run(main())
@@ -184,6 +184,7 @@ async def main():
184
184
  print_info(c),
185
185
  subscribe_and_print_orderflow(c, orderflow_requester),
186
186
  )
187
+ await c.close()
187
188
 
188
189
 
189
190
  asyncio.run(main())
examples/order_sending.py CHANGED
@@ -104,6 +104,8 @@ async def main():
104
104
  await test_send_order(client, account)
105
105
  await test_send_market_pro_order(client, account)
106
106
 
107
+ await client.close()
108
+
107
109
 
108
110
  if __name__ == "__main__":
109
111
  loop = asyncio.new_event_loop()
@@ -20,5 +20,7 @@ async def main():
20
20
  best_ask_s = f"{snap.best_ask[0]} x {snap.best_ask[1]}" # price x size
21
21
  print(f"{snap.symbol} {snap.timestamp} {best_bid_s} {best_ask_s}")
22
22
 
23
+ await c.close()
24
+
23
25
 
24
26
  asyncio.run(main())
@@ -33,5 +33,7 @@ async def main():
33
33
  venue = "CME"
34
34
  await print_l2_book(c, market_symbol, venue=venue)
35
35
 
36
+ await c.close()
37
+
36
38
 
37
39
  asyncio.run(main())
examples/trades.py CHANGED
@@ -16,5 +16,7 @@ async def main():
16
16
  print(e.status_code)
17
17
  print(e.response.json())
18
18
 
19
+ await c.close()
20
+
19
21
 
20
22
  asyncio.run(main())
@@ -79,6 +79,8 @@ async def main():
79
79
  print(f"Order was filled for qty: {order.filled_quantity}")
80
80
  print(f"Average execution price: {order.average_fill_price}")
81
81
 
82
+ await c.close()
83
+
82
84
 
83
85
  if __name__ == "__main__":
84
86
  asyncio.run(main())
examples/tutorial_sync.py CHANGED
@@ -92,3 +92,6 @@ elif order.status is OrderStatus.Canceled:
92
92
  elif order.status is OrderStatus.Out:
93
93
  print(f"Order was filled for qty: {order.filled_quantity}")
94
94
  print(f"Average execution price: {order.average_fill_price}")
95
+
96
+
97
+ c.close()