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 +3 -1
- architect_py/async_client.py +88 -21
- architect_py/client.pyi +45 -4
- architect_py/grpc/models/Cpty/CptyStatus.py +17 -18
- architect_py/grpc/models/Oms/Order.py +12 -1
- architect_py/grpc/models/definitions.py +41 -0
- architect_py/grpc/resolve_endpoint.py +3 -1
- architect_py/tests/conftest.py +2 -2
- architect_py/tests/test_marketdata.py +9 -0
- architect_py/tests/test_order_entry.py +2 -0
- architect_py/tests/test_portfolio_management.py +2 -0
- architect_py/tests/test_symbology.py +20 -0
- architect_py/tests/test_sync_client.py +22 -5
- {architect_py-5.1.1.dist-info → architect_py-5.1.2.dist-info}/METADATA +4 -1
- {architect_py-5.1.1.dist-info → architect_py-5.1.2.dist-info}/RECORD +27 -27
- examples/book_subscription.py +2 -0
- examples/candles.py +2 -0
- examples/funding_rate_mean_reversion_algo.py +1 -0
- examples/order_sending.py +2 -0
- examples/stream_l1_marketdata.py +2 -0
- examples/stream_l2_marketdata.py +2 -0
- examples/trades.py +2 -0
- examples/tutorial_async.py +2 -0
- examples/tutorial_sync.py +3 -0
- {architect_py-5.1.1.dist-info → architect_py-5.1.2.dist-info}/WHEEL +0 -0
- {architect_py-5.1.1.dist-info → architect_py-5.1.2.dist-info}/licenses/LICENSE +0 -0
- {architect_py-5.1.1.dist-info → architect_py-5.1.2.dist-info}/top_level.txt +0 -0
architect_py/__init__.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# ruff: noqa:I001
|
2
2
|
|
3
|
-
__version__ = "5.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",
|
architect_py/async_client.py
CHANGED
@@ -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 !=
|
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
|
-
|
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
|
531
|
-
) ->
|
547
|
+
self, series_symbol: str, venue: Optional[str] = None
|
548
|
+
) -> TradableProduct:
|
532
549
|
"""
|
533
|
-
Gets the future
|
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
|
-
|
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
|
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,
|
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
|
-
|
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
|
-
|
1631
|
-
|
1632
|
-
|
1633
|
-
|
1634
|
-
|
1635
|
-
|
1636
|
-
|
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
|
220
|
+
def get_front_future(self, series_symbol: str, venue: str | None = None) -> TradableProduct:
|
214
221
|
'''
|
215
|
-
Gets the future
|
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
|
-
|
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
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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},
|
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[
|
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 =
|
77
|
+
port = PAPER_GRPC_PORT
|
76
78
|
|
77
79
|
return host, port, use_ssl
|
architect_py/tests/conftest.py
CHANGED
@@ -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
|
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 =
|
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(
|
@@ -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
|
4
|
+
from architect_py.tests.conftest import GetEnvironment
|
6
5
|
|
7
6
|
|
8
|
-
|
9
|
-
async def test_sync_client():
|
7
|
+
def test_sync_client():
|
10
8
|
load_dotenv()
|
11
|
-
test_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.
|
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=
|
2
|
-
architect_py/async_client.py,sha256=
|
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=
|
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=
|
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=
|
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=
|
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=
|
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=
|
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=
|
161
|
-
architect_py/tests/test_order_entry.py,sha256=
|
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=
|
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=
|
166
|
-
architect_py/tests/test_sync_client.py,sha256=
|
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.
|
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=
|
176
|
-
examples/candles.py,sha256=
|
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=
|
180
|
-
examples/order_sending.py,sha256=
|
181
|
-
examples/stream_l1_marketdata.py,sha256=
|
182
|
-
examples/stream_l2_marketdata.py,sha256=
|
183
|
-
examples/trades.py,sha256=
|
184
|
-
examples/tutorial_async.py,sha256=
|
185
|
-
examples/tutorial_sync.py,sha256=
|
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.
|
195
|
-
architect_py-5.1.
|
196
|
-
architect_py-5.1.
|
197
|
-
architect_py-5.1.
|
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,,
|
examples/book_subscription.py
CHANGED
examples/candles.py
CHANGED
examples/order_sending.py
CHANGED
examples/stream_l1_marketdata.py
CHANGED
examples/stream_l2_marketdata.py
CHANGED
examples/trades.py
CHANGED
examples/tutorial_async.py
CHANGED
examples/tutorial_sync.py
CHANGED
File without changes
|
File without changes
|
File without changes
|