architect-py 5.1.0b1__py3-none-any.whl → 5.1.1__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.0b1"
3
+ __version__ = "5.1.1"
4
4
 
5
5
  from .utils.nearest_tick import TickRoundMethod
6
6
  from .async_client import AsyncClient
@@ -102,7 +102,9 @@ class AsyncClient:
102
102
  )
103
103
  endpoint = kwargs["endpoint"]
104
104
 
105
- grpc_host, grpc_port, use_ssl = await resolve_endpoint(endpoint)
105
+ grpc_host, grpc_port, use_ssl = await resolve_endpoint(
106
+ endpoint, paper_trading=paper_trading
107
+ )
106
108
  logging.info(
107
109
  f"Resolved endpoint {endpoint}: {grpc_host}:{grpc_port} use_ssl={use_ssl}"
108
110
  )
@@ -179,6 +181,22 @@ class AsyncClient:
179
181
  )
180
182
  self.grpc_core = GrpcClient(host=grpc_host, port=grpc_port, use_ssl=use_ssl)
181
183
 
184
+ async def close(self):
185
+ """
186
+ Close the gRPC channel and GraphQL client.
187
+ """
188
+ if self.grpc_core is not None:
189
+ await self.grpc_core.close()
190
+
191
+ for grpc_client in self.grpc_marketdata.values():
192
+ await grpc_client.close()
193
+
194
+ self.grpc_marketdata.clear()
195
+ # NB: this line removes the "Error in sys.excepthook:" on close
196
+
197
+ if self.graphql_client is not None:
198
+ await self.graphql_client.close()
199
+
182
200
  async def refresh_jwt(self, force: bool = False):
183
201
  """
184
202
  Refresh the JWT for the gRPC channel if it's nearing expiration (within 1 minute).
@@ -259,6 +277,9 @@ class AsyncClient:
259
277
  self.grpc_marketdata[venue] = GrpcClient(
260
278
  host=grpc_host, port=grpc_port, use_ssl=use_ssl
261
279
  )
280
+ logging.debug(
281
+ f"Setting marketdata endpoint for {venue}: {grpc_host}:{grpc_port} use_ssl={use_ssl}"
282
+ )
262
283
  except Exception as e:
263
284
  logging.error("Failed to set marketdata endpoint: %s", e)
264
285
 
@@ -1448,7 +1469,7 @@ class AsyncClient:
1448
1469
  )
1449
1470
  if execution_info is None:
1450
1471
  raise ValueError(
1451
- "Could not find execution information for {symbol} for rounding price for limit order. Please round price manually."
1472
+ f"Could not find execution information for {symbol} for rounding price for limit order. Please round price manually."
1452
1473
  )
1453
1474
  if (tick_size := execution_info.tick_size) is not None:
1454
1475
  if tick_size:
@@ -1481,7 +1502,7 @@ class AsyncClient:
1481
1502
  id: Optional[OrderId] = None,
1482
1503
  symbol: TradableProduct | str,
1483
1504
  execution_venue: str,
1484
- odir: OrderDir,
1505
+ dir: OrderDir,
1485
1506
  quantity: Decimal,
1486
1507
  time_in_force: TimeInForce = TimeInForce.DAY,
1487
1508
  account: Optional[str] = None,
@@ -1495,7 +1516,7 @@ class AsyncClient:
1495
1516
  id: in case user wants to generate their own order id, otherwise it will be generated automatically
1496
1517
  symbol: the symbol to send the order for
1497
1518
  execution_venue: the execution venue to send the order to
1498
- odir: the direction of the order
1519
+ dir: the direction of the order
1499
1520
  quantity: the quantity of the order
1500
1521
  time_in_force: the time in force of the order
1501
1522
  account: the account to send the order for
@@ -1516,7 +1537,7 @@ class AsyncClient:
1516
1537
 
1517
1538
  price_band = price_band_pairs.get(symbol, None)
1518
1539
 
1519
- if odir == OrderDir.BUY:
1540
+ if dir == OrderDir.BUY:
1520
1541
  if ticker.ask_price is None:
1521
1542
  raise ValueError(
1522
1543
  f"Failed to send market order with reason: no ask price for {symbol}"
@@ -1539,7 +1560,7 @@ class AsyncClient:
1539
1560
 
1540
1561
  # Conservatively round price to nearest tick
1541
1562
  tick_round_method = (
1542
- TickRoundMethod.FLOOR if odir == OrderDir.BUY else TickRoundMethod.CEIL
1563
+ TickRoundMethod.FLOOR if dir == OrderDir.BUY else TickRoundMethod.CEIL
1543
1564
  )
1544
1565
 
1545
1566
  execution_info = await self.get_execution_info(
@@ -1556,7 +1577,7 @@ class AsyncClient:
1556
1577
  id=id,
1557
1578
  symbol=symbol,
1558
1579
  execution_venue=execution_venue,
1559
- odir=odir,
1580
+ dir=dir,
1560
1581
  quantity=quantity,
1561
1582
  account=account,
1562
1583
  order_type=OrderType.LIMIT,
architect_py/client.py CHANGED
@@ -2,13 +2,12 @@ import asyncio
2
2
  import sys
3
3
  import threading
4
4
  from asyncio import AbstractEventLoop
5
+ from collections.abc import Callable
5
6
  from functools import partial
6
- from typing import Any, Awaitable, Callable, Coroutine, Optional, TypeVar
7
+ from typing import Any, Concatenate, Coroutine, Optional, ParamSpec, TypeVar
7
8
 
8
9
  from .async_client import AsyncClient
9
10
 
10
- T = TypeVar("T")
11
-
12
11
 
13
12
  def is_async_function(obj):
14
13
  # can be converted to C function for faster performance
@@ -16,24 +15,30 @@ def is_async_function(obj):
16
15
  return callable(obj) and hasattr(obj, "__code__") and obj.__code__.co_flags & 0x80
17
16
 
18
17
 
18
+ P = ParamSpec("P")
19
+ T = TypeVar("T")
20
+
21
+
19
22
  class Client:
20
23
  """
24
+ One can find the function definition in the AsyncClient class and in the pyi file.
25
+
21
26
  This class is a wrapper around the AsyncClient class that allows you to call async methods synchronously.
22
27
  This does not work for subscription based methods.
23
28
 
24
29
  This Client takes control of the event loop, which you can pass in.
25
30
 
26
- One can find the function definition in the AsyncClient class.
27
31
 
28
32
  The AsyncClient is more performant and powerful, so it is recommended to use that class if possible.
29
-
30
- Avoid adding functions or other attributes to this class unless you know what you are doing, because
31
- the __getattribute__ method changes the behavior of the class in a way that is not intuitive.
32
-
33
- Instead, add them to the AsyncClient class.
33
+ Avoid adding functions or other attributes to this class unless you know what you are doing.
34
34
  """
35
35
 
36
- __slots__ = ("client", "_event_loop")
36
+ __slots__ = (
37
+ "client",
38
+ "_event_loop",
39
+ "_sync_call",
40
+ "__dict__",
41
+ )
37
42
  client: AsyncClient
38
43
  _event_loop: AbstractEventLoop
39
44
 
@@ -56,88 +61,69 @@ class Client:
56
61
  Pass in an `event_loop` if you want to use your own; otherwise, this class
57
62
  will use the default asyncio event loop.
58
63
  """
64
+ self._sync_call = self._pick_executor()
65
+
59
66
  if event_loop is None:
60
67
  try:
61
68
  event_loop = asyncio.get_running_loop()
62
69
  except RuntimeError:
63
70
  event_loop = asyncio.new_event_loop()
64
71
  asyncio.set_event_loop(event_loop)
65
- super().__setattr__("_event_loop", event_loop)
66
-
67
- async_client = self._event_loop.run_until_complete(
68
- AsyncClient.connect(
69
- api_key=api_key,
70
- api_secret=api_secret,
71
- paper_trading=paper_trading,
72
- endpoint=endpoint,
73
- graphql_port=graphql_port,
74
- **kwargs,
75
- )
72
+ object.__setattr__(self, "_event_loop", event_loop)
73
+
74
+ async_client: AsyncClient = self._sync_call(
75
+ AsyncClient.connect,
76
+ api_key=api_key,
77
+ api_secret=api_secret,
78
+ paper_trading=paper_trading,
79
+ endpoint=endpoint,
80
+ graphql_port=graphql_port,
81
+ **kwargs,
76
82
  )
77
- super().__setattr__(
83
+
84
+ object.__setattr__(
85
+ self,
78
86
  "client",
79
87
  async_client,
80
88
  )
89
+ self._promote_async_client_methods()
81
90
 
91
+ def _pick_executor(
92
+ self,
93
+ ) -> Callable[
94
+ Concatenate[Callable[P, Coroutine[Any, Any, T]], P],
95
+ T,
96
+ ]:
97
+ """Return a function that runs a coroutine and blocks."""
82
98
  if "ipykernel" in sys.modules:
83
- # for jupyter notebooks
99
+ executor = AsyncExecutor()
84
100
  import atexit
85
101
 
86
- executor = AsyncExecutor()
87
102
  atexit.register(executor.shutdown)
103
+ return lambda fn, *a, **kw: executor.submit(fn(*a, **kw))
88
104
 
89
- def _sync_call_create_task(
90
- async_method: Callable[..., Coroutine[Any, Any, T]],
91
- *args,
92
- **kwargs,
93
- ) -> T:
94
- """
95
- Executes the given coroutine synchronously using the executor.
96
- """
97
- return executor.submit(async_method(*args, **kwargs))
105
+ return lambda fn, *a, **kw: self._event_loop.run_until_complete(fn(*a, **kw))
98
106
 
99
- super().__setattr__("_sync_call", _sync_call_create_task)
107
+ def _promote_async_client_methods(self) -> None:
108
+ for name in dir(self.client):
109
+ if name.startswith("_"):
110
+ continue
100
111
 
101
- def __getattribute__(self, name: str):
102
- """
103
- You may have been lead here looking for the definition of a method of the Client
104
- It can be found if you look in the AsyncClient class, which this class is a wrapper for,
105
- or GraphQLClient, which is a parent class of AsyncClient
112
+ if any(x in name for x in ("stream", "subscribe", "connect")):
113
+ continue
114
+ attr = getattr(self.client, name)
106
115
 
107
- Explanation:
108
- __getattribute__ is a magic method that is called when searching for any attribute
109
- In this case, will look through self.client, which is an instance of the Client class
116
+ if is_async_function(attr):
117
+ attr = partial(self._sync_call, attr)
110
118
 
111
- We do this because we want to be able to call the async methods of the Client in a synchronous way,
112
- but otherwise pass through the other attributes normally
119
+ object.__setattr__(self, name, attr)
113
120
 
114
- It must be getattribute and not getattr because of the AsyncClientProtocol class inheritance
115
- We gain type hinting but lose the ability to call the methods of the Client class itself
116
- in a normal way
117
- """
118
- attr = getattr(super().__getattribute__("client"), name)
119
- if is_async_function(attr):
120
- if "subscribe" in name:
121
- raise AttributeError(
122
- f"Method {name} is an subscription based async method and cannot be called synchronously"
123
- )
124
- return partial(super().__getattribute__("_sync_call"), attr)
121
+ def __setattr__(self, name: str, value: Any) -> None:
122
+ # protect wrapper internals
123
+ if name in ("client", "_event_loop", "_sync_call"):
124
+ object.__setattr__(self, name, value)
125
125
  else:
126
- return attr
127
-
128
- def __setattr__(self, name: str, value: Any):
129
- """primarily to prevent unintended shadowing"""
130
- client = super().__getattribute__("client")
131
- setattr(client, name, value)
132
-
133
- def _sync_call(
134
- self, async_method: Callable[..., Awaitable[T]], *args, **kwargs
135
- ) -> T:
136
- return (
137
- super()
138
- .__getattribute__("_event_loop")
139
- .run_until_complete(async_method(*args, **kwargs))
140
- )
126
+ setattr(self.client, name, value)
141
127
 
142
128
 
143
129
  class AsyncExecutor:
architect_py/client.pyi CHANGED
@@ -23,19 +23,16 @@ from typing import Any, AsyncGenerator, AsyncIterator, Literal, Sequence, overlo
23
23
 
24
24
  class Client:
25
25
  """
26
+ One can find the function definition in the AsyncClient class and in the pyi file.
27
+
26
28
  This class is a wrapper around the AsyncClient class that allows you to call async methods synchronously.
27
29
  This does not work for subscription based methods.
28
30
 
29
31
  This Client takes control of the event loop, which you can pass in.
30
32
 
31
- One can find the function definition in the AsyncClient class.
32
33
 
33
34
  The AsyncClient is more performant and powerful, so it is recommended to use that class if possible.
34
-
35
- Avoid adding functions or other attributes to this class unless you know what you are doing, because
36
- the __getattribute__ method changes the behavior of the class in a way that is not intuitive.
37
-
38
- Instead, add them to the AsyncClient class.
35
+ Avoid adding functions or other attributes to this class unless you know what you are doing.
39
36
  """
40
37
  api_key: str | None
41
38
  api_secret: str | None
@@ -57,6 +54,10 @@ class Client:
57
54
  will use the default asyncio event loop.
58
55
  """
59
56
  l2_books: dict[Venue, dict[TradableProduct, tuple[L2BookSnapshot, asyncio.Task]]]
57
+ def close(self) -> None:
58
+ """
59
+ Close the gRPC channel and GraphQL client.
60
+ """
60
61
  def refresh_jwt(self, force: bool = False):
61
62
  """
62
63
  Refresh the JWT for the gRPC channel if it's nearing expiration (within 1 minute).
@@ -464,7 +465,7 @@ class Client:
464
465
 
465
466
  If the order is rejected, the order.reject_reason and order.reject_message will be set
466
467
  '''
467
- def send_market_pro_order(self, *, id: OrderId | None = None, symbol: TradableProduct | str, execution_venue: str, odir: OrderDir, quantity: Decimal, time_in_force: TimeInForce = ..., account: str | None = None, fraction_through_market: Decimal = ...) -> Order:
468
+ def send_market_pro_order(self, *, id: OrderId | None = None, symbol: TradableProduct | str, execution_venue: str, dir: OrderDir, quantity: Decimal, time_in_force: TimeInForce = ..., account: str | None = None, fraction_through_market: Decimal = ...) -> Order:
468
469
  '''
469
470
  Sends a market-order like limit price based on the BBO.
470
471
  Meant to behave as a market order but with more protections.
@@ -473,7 +474,7 @@ class Client:
473
474
  id: in case user wants to generate their own order id, otherwise it will be generated automatically
474
475
  symbol: the symbol to send the order for
475
476
  execution_venue: the execution venue to send the order to
476
- odir: the direction of the order
477
+ dir: the direction of the order
477
478
  quantity: the quantity of the order
478
479
  time_in_force: the time in force of the order
479
480
  account: the account to send the order for
@@ -65,6 +65,7 @@ class TimeInForce:
65
65
 
66
66
  @classmethod
67
67
  def GTD(cls, when: datetime) -> "TimeInForce":
68
+ assert when.tzinfo is not None, "GTD requires a timezone-aware datetime"
68
69
  return cls("GTD", when)
69
70
 
70
71
  def serialize(self) -> msgspec.Raw:
@@ -54,7 +54,7 @@ class TradableProduct(str):
54
54
  return self.split("/", 1)[1]
55
55
 
56
56
  def serialize(self) -> msgspec.Raw:
57
- return msgspec.Raw(self.encode())
57
+ return msgspec.Raw(msgspec.json.encode(str(self)))
58
58
 
59
59
  @staticmethod
60
60
  def deserialize(s: str) -> "TradableProduct":
@@ -91,6 +91,10 @@ class JuniperBaseClient:
91
91
  exc_tb: object,
92
92
  ) -> None:
93
93
  await self.http_client.aclose()
94
+
95
+ async def close(self) -> None:
96
+ """Close the HTTP client connection."""
97
+ await self.http_client.aclose()
94
98
 
95
99
  async def execute(
96
100
  self,
@@ -30,6 +30,9 @@ class GrpcClient:
30
30
  def set_jwt(self, jwt: str | None):
31
31
  self.jwt = jwt
32
32
 
33
+ async def close(self):
34
+ await self.channel.close()
35
+
33
36
  @staticmethod
34
37
  def encoder() -> msgspec.json.Encoder:
35
38
  return encoder
@@ -29,6 +29,9 @@ class Ticker(Struct, omit_defaults=True):
29
29
  ft: Optional[Annotated[Optional[datetime], Meta(title="next_funding_time")]] = None
30
30
  h: Optional[Annotated[Optional[Decimal], Meta(title="high_24h")]] = None
31
31
  ip: Optional[Annotated[Optional[Decimal], Meta(title="index_price")]] = None
32
+ isp: Optional[
33
+ Annotated[Optional[Decimal], Meta(title="indicative_settlement_price")]
34
+ ] = None
32
35
  l: Optional[Annotated[Optional[Decimal], Meta(title="low_24h")]] = None
33
36
  market_cap: Optional[Decimal] = None
34
37
  mp: Optional[Annotated[Optional[Decimal], Meta(title="mark_price")]] = None
@@ -68,6 +71,7 @@ class Ticker(Struct, omit_defaults=True):
68
71
  next_funding_time: Optional[datetime] = None,
69
72
  high_24h: Optional[Decimal] = None,
70
73
  index_price: Optional[Decimal] = None,
74
+ indicative_settlement_price: Optional[Decimal] = None,
71
75
  low_24h: Optional[Decimal] = None,
72
76
  market_cap: Optional[Decimal] = None,
73
77
  mark_price: Optional[Decimal] = None,
@@ -102,6 +106,7 @@ class Ticker(Struct, omit_defaults=True):
102
106
  next_funding_time,
103
107
  high_24h,
104
108
  index_price,
109
+ indicative_settlement_price,
105
110
  low_24h,
106
111
  market_cap,
107
112
  mark_price,
@@ -122,7 +127,7 @@ class Ticker(Struct, omit_defaults=True):
122
127
  )
123
128
 
124
129
  def __str__(self) -> str:
125
- return f"Ticker(symbol={self.s},timestamp_ns={self.tn},timestamp={self.ts},venue={self.ve},ask_price={self.ap},ask_size={self.as_},bid_price={self.bp},bid_size={self.bs},dividend={self.dividend},dividend_yield={self.dividend_yield},eps_adj={self.eps_adj},funding_rate={self.fr},next_funding_time={self.ft},high_24h={self.h},index_price={self.ip},low_24h={self.l},market_cap={self.market_cap},mark_price={self.mp},open_24h={self.o},open_interest={self.oi},last_price={self.p},price_to_earnings={self.price_to_earnings},last_size={self.q},last_settlement_date={self.sd},shares_outstanding_weighted_adj={self.shares_outstanding_weighted_adj},last_settlement_price={self.sp},volume_24h={self.v},volume_30d={self.vm},session_high={self.xh},session_low={self.xl},session_open={self.xo},session_volume={self.xv})"
130
+ return f"Ticker(symbol={self.s},timestamp_ns={self.tn},timestamp={self.ts},venue={self.ve},ask_price={self.ap},ask_size={self.as_},bid_price={self.bp},bid_size={self.bs},dividend={self.dividend},dividend_yield={self.dividend_yield},eps_adj={self.eps_adj},funding_rate={self.fr},next_funding_time={self.ft},high_24h={self.h},index_price={self.ip},indicative_settlement_price={self.isp},low_24h={self.l},market_cap={self.market_cap},mark_price={self.mp},open_24h={self.o},open_interest={self.oi},last_price={self.p},price_to_earnings={self.price_to_earnings},last_size={self.q},last_settlement_date={self.sd},shares_outstanding_weighted_adj={self.shares_outstanding_weighted_adj},last_settlement_price={self.sp},volume_24h={self.v},volume_30d={self.vm},session_high={self.xh},session_low={self.xl},session_open={self.xo},session_volume={self.xv})"
126
131
 
127
132
  @property
128
133
  def symbol(self) -> str:
@@ -228,6 +233,14 @@ class Ticker(Struct, omit_defaults=True):
228
233
  def index_price(self, value: Optional[Decimal]) -> None:
229
234
  self.ip = value
230
235
 
236
+ @property
237
+ def indicative_settlement_price(self) -> Optional[Decimal]:
238
+ return self.isp
239
+
240
+ @indicative_settlement_price.setter
241
+ def indicative_settlement_price(self, value: Optional[Decimal]) -> None:
242
+ self.isp = value
243
+
231
244
  @property
232
245
  def low_24h(self) -> Optional[Decimal]:
233
246
  return self.l
@@ -8,7 +8,9 @@ import dns.resolver
8
8
  from dns.rdtypes.IN.SRV import SRV
9
9
 
10
10
 
11
- async def resolve_endpoint(endpoint: str) -> Tuple[str, int, bool]:
11
+ async def resolve_endpoint(
12
+ endpoint: str, paper_trading: bool = True
13
+ ) -> Tuple[str, int, bool]:
12
14
  """
13
15
  From a gRPC endpoint, resolve the host, port and whether or not the endpoint
14
16
  should use SSL. If the port is specified explicitly, it will be used. Otherwise,
@@ -67,4 +69,9 @@ async def resolve_endpoint(endpoint: str) -> Tuple[str, int, bool]:
67
69
 
68
70
  host = str(record.target).rstrip(".") # strips the period off of FQDNs
69
71
 
70
- return host, record.port, use_ssl
72
+ port = record.port
73
+ if paper_trading:
74
+ if "app.architect.co" in host or "staging.architect.co" in host:
75
+ port = 10080
76
+
77
+ return host, port, use_ssl
@@ -119,6 +119,3 @@ async def front_ES_future_usd(async_client: AsyncClient) -> str:
119
119
  """
120
120
  future = await get_front_ES_future(async_client)
121
121
  return f"{future}/USD"
122
-
123
-
124
- # CR alee: add sync Client tests
@@ -0,0 +1,37 @@
1
+ from datetime import datetime, timezone
2
+
3
+ from architect_py.common_types.time_in_force import TimeInForce
4
+ from architect_py.common_types.tradable_product import TradableProduct
5
+ from architect_py.grpc.utils import encoder
6
+
7
+
8
+ def test_encoding():
9
+ product = TradableProduct("ES 20250321 CME Future", "USD")
10
+ encoded = encoder.encode(product)
11
+
12
+ assert encoded == b'"ES 20250321 CME Future/USD"', (
13
+ 'Encoding of TradableProduct failed, expected "ES 20250321 CME Future/USD" but got '
14
+ + str(encoded)
15
+ )
16
+
17
+ now = datetime.now(timezone.utc)
18
+
19
+ encoded = encoder.encode(TimeInForce.GTD(now))
20
+
21
+ assert encoded == b'{"GTD": "' + now.isoformat().encode() + b'"}', (
22
+ 'Encoding of TimeInForce.GTD failed, expected {"GTD": "'
23
+ + now.isoformat()
24
+ + '"} but got '
25
+ + str(encoded)
26
+ )
27
+
28
+ encoded = encoder.encode(TimeInForce.GTC)
29
+
30
+ assert encoded == b'"GTC"', (
31
+ 'Encoding of TimeInForce.GTC failed, expected "GTC" but got ' + str(encoded)
32
+ )
33
+
34
+
35
+ if __name__ == "__main__":
36
+ test_encoding()
37
+ print("All tests passed.")
@@ -26,7 +26,7 @@ async def test_place_limit_order(async_client: AsyncClient):
26
26
  order = await async_client.place_limit_order(
27
27
  symbol=symbol,
28
28
  execution_venue=venue,
29
- odir=OrderDir.BUY,
29
+ dir=OrderDir.BUY,
30
30
  quantity=Decimal(1),
31
31
  limit_price=limit_price,
32
32
  account=str(account.account.id),
@@ -0,0 +1,23 @@
1
+ import pytest
2
+ from dotenv import load_dotenv
3
+
4
+ from architect_py import Client
5
+ from architect_py.tests.conftest import TestEnvironment
6
+
7
+
8
+ @pytest.mark.asyncio
9
+ async def test_sync_client():
10
+ load_dotenv()
11
+ test_env = TestEnvironment.from_env()
12
+ client = Client(
13
+ api_key=test_env.api_key,
14
+ api_secret=test_env.api_secret,
15
+ paper_trading=test_env.paper_trading,
16
+ endpoint=test_env.endpoint,
17
+ graphql_port=test_env.graphql_port,
18
+ )
19
+
20
+ symbols = client.list_symbols(marketdata="CME")
21
+
22
+ assert symbols is not None
23
+ assert len(symbols) > 20
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: architect-py
3
- Version: 5.1.0b1
3
+ Version: 5.1.1
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
@@ -1,11 +1,11 @@
1
- architect_py/__init__.py,sha256=18bhau9o35uBVHpoQlkCgF0aT-qNHYCO3JJpWkDykjM,16077
2
- architect_py/async_client.py,sha256=Re743h6J6Qax5TAwf8Rv3A1AYTaHTYmcI5y1x5q7Eh8,58787
3
- architect_py/client.py,sha256=fpgE23nqB9GGsOyGdf0WRhVektOjso-qRm-XiQVF690,6048
4
- architect_py/client.pyi,sha256=OTlM_F_zANNHKuWV0XNGvcM9vIeENaZjBLSa8VMQXiI,23050
1
+ architect_py/__init__.py,sha256=ZkGr9D_FPHH7OjiqyWQm14BCOLUaM_AiDAes4vX3vOk,16075
2
+ architect_py/async_client.py,sha256=1EO4TGdO1C2AQg0a2I2Q42kPToclziPaWYRX2Mc2W1U,59457
3
+ architect_py/client.py,sha256=yC0OVzz6uXUMdIIhqqR3GyVuBApYSm00AS51QM8xPak,4911
4
+ architect_py/client.pyi,sha256=SpWAEY7Ff2FVrbOtvmp37-vanPkef6gdtu1lZ2iw3iU,23017
5
5
  architect_py/common_types/__init__.py,sha256=fzOdIlKGWVN9V2Onc5z1v2bpvtZ4H9RSFA9ymJcBi3k,197
6
6
  architect_py/common_types/order_dir.py,sha256=ebyWTcXzJWrotkc2D9wNGc6JXbE5I3NLLuAz3I7FTZ8,2191
7
- architect_py/common_types/time_in_force.py,sha256=lzGAQCm_BqJ0mlNdZfhHYcybbHhP-Uc7dlcxrrCwQRo,3051
8
- architect_py/common_types/tradable_product.py,sha256=S4sFIvG13KA4SVqUI9w2smWwomqL6Qh0xIkvKAIxKH0,2131
7
+ architect_py/common_types/time_in_force.py,sha256=gEDYcNp014Eeb98zJDytiV0hGxHu_QsQndeM6Hk0Wa8,3132
8
+ architect_py/common_types/tradable_product.py,sha256=4IIyrkPsMXjS_LrPDIgRWZY03FAsOaALdGTrPHtg6Rs,2148
9
9
  architect_py/graphql_client/__init__.py,sha256=fTZjb1MbdYM80p9dCklWwu5T0c7C3KYMLLGGsF7ZGAA,2306
10
10
  architect_py/graphql_client/base_model.py,sha256=o2d-DixASFCGztr3rTiGX0AwgFu7Awr7EgD70FI8a-I,620
11
11
  architect_py/graphql_client/client.py,sha256=qsjGZG7M9KwGLL1f6dVLV8-r27vUIYA9al0IDJw4ugo,9976
@@ -19,12 +19,12 @@ architect_py/graphql_client/get_future_series_query.py,sha256=pRfdJ31gW2bxYotmXv
19
19
  architect_py/graphql_client/get_product_info_query.py,sha256=oxDDx3J2jGSnNXQZw-lDHdzZO33XCOC0hpNSofjqKQ0,598
20
20
  architect_py/graphql_client/get_product_infos_query.py,sha256=vZImiKh19LCG0yKiw9aP9y1lnUgxgywW7whj1FeSnGk,601
21
21
  architect_py/graphql_client/input_types.py,sha256=6Obe-vvDm4TDgH3oRZUzbEvkbquaQOHYRK_62B1_0FA,57
22
- architect_py/graphql_client/juniper_base_client.py,sha256=njikUx3G2PLqYmx6PGhT_EHALUwD9EHlI2o4W1XMnMk,12609
22
+ architect_py/graphql_client/juniper_base_client.py,sha256=0kbAihyRgEP3n28zRumoSTvpOV695bd8bQPlVE3tRTY,12737
23
23
  architect_py/graphql_client/search_symbols_query.py,sha256=hbGa6gF-gMWtRYQm2vlCTPDex8RWrJ4Yn4nT0VRQnCQ,614
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
- architect_py/grpc/client.py,sha256=6NolGeMdIxEcx1R0ij_wWW-o1MspS_XqYE2TtQqaxsk,3343
27
- architect_py/grpc/resolve_endpoint.py,sha256=HnOYC7n7taeSdnGY5E4TCSArCuY2LTwsGOnt_YLh9vQ,2415
26
+ architect_py/grpc/client.py,sha256=1_2JuxFoIVRtcuczkuI-uF7zy-AqSYehf5N3E9Gy4Jg,3406
27
+ architect_py/grpc/resolve_endpoint.py,sha256=cUElbLmOerPDZJtseP9AWX1Ee3kdZ3XNb3FpgLTHZIc,2586
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
@@ -105,7 +105,7 @@ architect_py/grpc/models/Marketdata/SubscribeLiquidationsRequest.py,sha256=6BhC4
105
105
  architect_py/grpc/models/Marketdata/SubscribeManyCandlesRequest.py,sha256=pel2GGysDsJXjPY7rkyqqyGS3MPl13YezJS7apihiFc,1512
106
106
  architect_py/grpc/models/Marketdata/SubscribeTickersRequest.py,sha256=7g2LBAYd97OJ9FrxpUvZKO7hSMng-K4KfnsN08O4XSM,1437
107
107
  architect_py/grpc/models/Marketdata/SubscribeTradesRequest.py,sha256=7P8FyNx6wijNUBGry0vaMhaEKuG1ik8kTibJBvdol2k,1299
108
- architect_py/grpc/models/Marketdata/Ticker.py,sha256=XOZbaSwCOyE8sDQi4aKA7jc39igXay63JCFOpeQN1Fo,11091
108
+ architect_py/grpc/models/Marketdata/Ticker.py,sha256=O4kJK1RyThYgfpvIr9mgRWAAkYgwwAKgOhEhbfDo9b4,11592
109
109
  architect_py/grpc/models/Marketdata/TickerRequest.py,sha256=Ay--5JKgCfdvlVWD2H6YSa_66NC3Dt6c-XK8JkbWhus,1008
110
110
  architect_py/grpc/models/Marketdata/TickerUpdate.py,sha256=sJ4wvCeGckMV30HwAcAsEMQbCzjN31OxF19q70jdxok,437
111
111
  architect_py/grpc/models/Marketdata/TickersRequest.py,sha256=_BYkOO2pk-terLNwyxN8gtHQxIrfPA7klodDeTS5ouM,2200
@@ -154,33 +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=5CcEuqakcU-ZapywYZRcLI5y1GicrTeOxiEixt--R0w,3552
157
+ architect_py/tests/conftest.py,sha256=qZYHMQtmPuHKJ9TiYng6Z3RfwFI0v2KKVmoJRes1j8c,3517
158
158
  architect_py/tests/test_book_building.py,sha256=biqs8X9bw1YSb6mrCDS-ELesdD-P5F6bE3MYXP0BeQ4,1236
159
+ architect_py/tests/test_encoding.py,sha256=J61Lk2CDIqgdcsj0-KYBZweYh5EQ4XAXsRyM0fdMqfU,1085
159
160
  architect_py/tests/test_marketdata.py,sha256=mhYIiNhmsY706PQD3Nvto0l4uDl5dPHn8BcqGPgFCA8,4714
160
- architect_py/tests/test_order_entry.py,sha256=qRKWa4QvIlurZG96AK2CMF9dpRAKep9RquOuEAdAuRY,1148
161
+ architect_py/tests/test_order_entry.py,sha256=7YKy1tT6XbNbEQFHcrhPelrD8MQnhqA2IvCIK23lAtk,1147
161
162
  architect_py/tests/test_orderflow.py,sha256=PRCr4Yzaif9OG0Eq1zxCUpTfFsHA3WEWSBiETRDvnIE,1038
162
163
  architect_py/tests/test_portfolio_management.py,sha256=LPlkLP2SllLPm0Un7OptfVo96uqiDI7-osTaHxH5m54,677
163
164
  architect_py/tests/test_rounding.py,sha256=cAQ1-tWOVgxENt0Fzs9YcFDdDDYmCtOHvAA_w76wy9g,1417
164
165
  architect_py/tests/test_symbology.py,sha256=892FN_FGwE8t4lVQtUMGKav69MGzHACeF5RAYrAEdBw,2707
166
+ architect_py/tests/test_sync_client.py,sha256=pQYa4arVako7TqNIRL4M9YLMgMBGDMx9mqgHyZMU-lI,588
165
167
  architect_py/utils/nearest_tick.py,sha256=i1cCGMSi-sP4Grbp0RCwEsoLzMWN9iC6gPMBm2daDWM,4810
166
168
  architect_py/utils/nearest_tick_2.py,sha256=f-o6b73Mo8epCIaOYBS9F0k_6UHUDSVG1N_VWg7iFBU,3641
167
169
  architect_py/utils/orderbook.py,sha256=JM02NhHbmK3sNaS2Ara8FBY4TvKvtMIzJW1oVd8KC3s,1004
168
170
  architect_py/utils/pandas.py,sha256=QHz2ynj4T92FobuzRaNoH3ypArHoSDCiGtZ3PVXJ2vo,1017
169
171
  architect_py/utils/price_bands.py,sha256=j7ioSA3dx025CD5E2Vg7XQvmjPvxQb-gzQBfQTovpTw,21874
170
172
  architect_py/utils/symbol_parsing.py,sha256=OjJzk2c6QU2s0aJMSyVEzlWD5Vy-RlakJVW7jNHVDJk,845
171
- architect_py-5.1.0b1.dist-info/licenses/LICENSE,sha256=6P0_5gYN8iPWPZeqA9nxiO3tRQmcSA1ijAVR7C8j1SI,11362
173
+ architect_py-5.1.1.dist-info/licenses/LICENSE,sha256=6P0_5gYN8iPWPZeqA9nxiO3tRQmcSA1ijAVR7C8j1SI,11362
172
174
  examples/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
173
175
  examples/book_subscription.py,sha256=eyXEt23dTyyArXt7N7C-XdRmLFSdZSntOYZaL0W-PBU,1440
174
176
  examples/candles.py,sha256=AicLj6d-yUVbZVX-mQUBJkI6KEoJeC-BErvxD9-8Z8Y,735
175
177
  examples/common.py,sha256=K2ppu4vdlTNsL1oX5RDrNKczljfPVOTPzDia1Abrppg,2987
176
178
  examples/external_cpty.py,sha256=xxGXONXwoWIS8ys0SgxHLSmntAi1BlwV2NR9WD1kvpc,2527
177
179
  examples/funding_rate_mean_reversion_algo.py,sha256=iP3bghdYMzERfaJMs_QJZ-e3SUVhLgHLNPvOf2sDkIQ,6482
178
- examples/order_sending.py,sha256=2Wple0qOe1I-9c8nTX9xtOuMjIvPydIezUfn6LQnWlE,3290
180
+ examples/order_sending.py,sha256=NAde_HOHPKz79M1UJQm2ABHwrdA2CLGWxqz_JF7d4mQ,3287
179
181
  examples/stream_l1_marketdata.py,sha256=Oi7ovb0i4hTdWfkhcRYUJLW5Ou64JxWzZMGhqSL8f_I,707
180
182
  examples/stream_l2_marketdata.py,sha256=e5d0z4Hft3X51oZsPwJbb_5VG2iDfCMlbbFv3qOkma4,1070
181
183
  examples/trades.py,sha256=AGKX8g7Xaf5r-KPWEeeAfL7XxoVeh8WsZvaWW5kBuMg,540
182
- examples/tutorial_async.py,sha256=HTh2iVJOlXckenWRJxYxp8awhNGBiFPDgtIe4f8mBYk,2626
183
- examples/tutorial_sync.py,sha256=egXb184Q-HuSK7G-NHcnOFM8ywmv08tlKr4GUrk75sY,2813
184
+ examples/tutorial_async.py,sha256=mNwqD3OQnNZ97YnVJWBsp2IimCB8fRrrSSDTPVxNLwo,2625
185
+ examples/tutorial_sync.py,sha256=tnUJNOTGeR4UdtLBtH3vYjgFU9sX_-bVucgXIDJ02ek,2812
184
186
  scripts/add_imports_to_inits.py,sha256=bryhz6RpKAJsSieVMnXnRyLp8evNkpOsNUkBUPkk1WQ,4518
185
187
  scripts/correct_sync_interface.py,sha256=O8qxSqNSNIL8KrgZ4C8rjs_pUCdcA1WeqKAggM2DINw,4056
186
188
  scripts/generate_functions_md.py,sha256=-rVRhbHlDodGH2a32UCsMLIpgXtDvOhBmkHa0RqDpCA,6232
@@ -188,8 +190,8 @@ scripts/postprocess_grpc.py,sha256=QqFZdOLH6hLPRCLUkf7qvuGooLsXulcpMghCpleHc-A,2
188
190
  scripts/preprocess_grpc_schema.py,sha256=p9LdoMZzixBSsVx7Dy3_8uJzOy_QwCoVMkAABQKUsBA,22894
189
191
  scripts/prune_graphql_schema.py,sha256=hmfw5FD_iKGKMFkq6H1neZiXXtljFFrOwi2fiusTWE4,6210
190
192
  templates/exceptions.py,sha256=tIHbiO5Q114h9nPwJXsgHvW_bERLwxuNp9Oj41p6t3A,2379
191
- templates/juniper_base_client.py,sha256=x3W5bRmeAK-oznsjJm_4TvrRJJICW23jKHcCKUIj7Vg,12577
192
- architect_py-5.1.0b1.dist-info/METADATA,sha256=gdF0ZucoSHYWHr8320fPwGYKbfaPeFghMQmfLzFX5_I,2369
193
- architect_py-5.1.0b1.dist-info/WHEEL,sha256=zaaOINJESkSfm_4HQVc5ssNzHCPXhJm0kEUakpsEHaU,91
194
- architect_py-5.1.0b1.dist-info/top_level.txt,sha256=UjtO97OACFQ9z5MzS-X2wBlt5Ovk1vxakQPKfokI454,40
195
- architect_py-5.1.0b1.dist-info/RECORD,,
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,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.8.0)
2
+ Generator: setuptools (80.9.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
examples/order_sending.py CHANGED
@@ -36,7 +36,7 @@ async def test_send_order(client: AsyncClient, account: str):
36
36
 
37
37
  order = await client.place_limit_order(
38
38
  symbol=symbol,
39
- odir=OrderDir.BUY,
39
+ dir=OrderDir.BUY,
40
40
  quantity=best_bid_quantity,
41
41
  order_type=OrderType.LIMIT,
42
42
  execution_venue="CME",
@@ -67,7 +67,7 @@ async def test_send_market_pro_order(client: AsyncClient, account: str):
67
67
  await client.send_market_pro_order(
68
68
  symbol=symbol,
69
69
  execution_venue=venue,
70
- odir=OrderDir.BUY,
70
+ dir=OrderDir.BUY,
71
71
  quantity=Decimal(1),
72
72
  account=account,
73
73
  time_in_force=TimeInForce.IOC,
@@ -84,7 +84,7 @@ async def send_NQ_buy_for_mid(client: AsyncClient, account: str):
84
84
 
85
85
  order = await client.place_limit_order(
86
86
  symbol=NQ_lead_future,
87
- odir=OrderDir.BUY,
87
+ dir=OrderDir.BUY,
88
88
  quantity=Decimal(1),
89
89
  order_type=OrderType.LIMIT,
90
90
  execution_venue=CME,
@@ -49,7 +49,7 @@ async def main():
49
49
  order = await c.place_limit_order(
50
50
  symbol=market,
51
51
  execution_venue=execution_venue,
52
- odir=OrderDir.BUY,
52
+ dir=OrderDir.BUY,
53
53
  quantity=quantity,
54
54
  limit_price=limit_price,
55
55
  account=str(account_id),
examples/tutorial_sync.py CHANGED
@@ -68,7 +68,7 @@ if confirm(
68
68
  order = c.send_limit_order(
69
69
  symbol=symbol,
70
70
  execution_venue=venue,
71
- odir=OrderDir.BUY,
71
+ dir=OrderDir.BUY,
72
72
  quantity=best_bid_quantity,
73
73
  limit_price=limit_price,
74
74
  account=account.account.name,
@@ -89,6 +89,10 @@ class JuniperBaseClient:
89
89
  exc_tb: object,
90
90
  ) -> None:
91
91
  await self.http_client.aclose()
92
+
93
+ async def close(self) -> None:
94
+ """Close the HTTP client connection."""
95
+ await self.http_client.aclose()
92
96
 
93
97
  async def execute(
94
98
  self,