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 +1 -1
- architect_py/async_client.py +28 -7
- architect_py/client.py +56 -70
- architect_py/client.pyi +9 -8
- architect_py/common_types/time_in_force.py +1 -0
- architect_py/common_types/tradable_product.py +1 -1
- architect_py/graphql_client/juniper_base_client.py +4 -0
- architect_py/grpc/client.py +3 -0
- architect_py/grpc/models/Marketdata/Ticker.py +14 -1
- architect_py/grpc/resolve_endpoint.py +9 -2
- architect_py/tests/conftest.py +0 -3
- architect_py/tests/test_encoding.py +37 -0
- architect_py/tests/test_order_entry.py +1 -1
- architect_py/tests/test_sync_client.py +23 -0
- {architect_py-5.1.0b1.dist-info → architect_py-5.1.1.dist-info}/METADATA +1 -1
- {architect_py-5.1.0b1.dist-info → architect_py-5.1.1.dist-info}/RECORD +23 -21
- {architect_py-5.1.0b1.dist-info → architect_py-5.1.1.dist-info}/WHEEL +1 -1
- examples/order_sending.py +3 -3
- examples/tutorial_async.py +1 -1
- examples/tutorial_sync.py +1 -1
- templates/juniper_base_client.py +4 -0
- {architect_py-5.1.0b1.dist-info → architect_py-5.1.1.dist-info}/licenses/LICENSE +0 -0
- {architect_py-5.1.0b1.dist-info → architect_py-5.1.1.dist-info}/top_level.txt +0 -0
architect_py/__init__.py
CHANGED
architect_py/async_client.py
CHANGED
@@ -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(
|
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
|
-
|
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
|
-
|
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
|
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
|
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
|
-
|
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,
|
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__ = (
|
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
|
-
|
66
|
-
|
67
|
-
async_client = self.
|
68
|
-
AsyncClient.connect
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
107
|
+
def _promote_async_client_methods(self) -> None:
|
108
|
+
for name in dir(self.client):
|
109
|
+
if name.startswith("_"):
|
110
|
+
continue
|
100
111
|
|
101
|
-
|
102
|
-
|
103
|
-
|
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
|
-
|
108
|
-
|
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
|
-
|
112
|
-
but otherwise pass through the other attributes normally
|
119
|
+
object.__setattr__(self, name, attr)
|
113
120
|
|
114
|
-
|
115
|
-
|
116
|
-
in
|
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
|
-
|
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,
|
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
|
-
|
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
|
@@ -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(
|
57
|
+
return msgspec.Raw(msgspec.json.encode(str(self)))
|
58
58
|
|
59
59
|
@staticmethod
|
60
60
|
def deserialize(s: str) -> "TradableProduct":
|
architect_py/grpc/client.py
CHANGED
@@ -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(
|
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
|
-
|
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
|
architect_py/tests/conftest.py
CHANGED
@@ -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
|
-
|
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,11 +1,11 @@
|
|
1
|
-
architect_py/__init__.py,sha256=
|
2
|
-
architect_py/async_client.py,sha256=
|
3
|
-
architect_py/client.py,sha256=
|
4
|
-
architect_py/client.pyi,sha256=
|
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=
|
8
|
-
architect_py/common_types/tradable_product.py,sha256=
|
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=
|
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=
|
27
|
-
architect_py/grpc/resolve_endpoint.py,sha256=
|
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=
|
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=
|
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=
|
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.
|
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=
|
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=
|
183
|
-
examples/tutorial_sync.py,sha256=
|
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=
|
192
|
-
architect_py-5.1.
|
193
|
-
architect_py-5.1.
|
194
|
-
architect_py-5.1.
|
195
|
-
architect_py-5.1.
|
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,,
|
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
|
-
|
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
|
-
|
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
|
-
|
87
|
+
dir=OrderDir.BUY,
|
88
88
|
quantity=Decimal(1),
|
89
89
|
order_type=OrderType.LIMIT,
|
90
90
|
execution_venue=CME,
|
examples/tutorial_async.py
CHANGED
examples/tutorial_sync.py
CHANGED
templates/juniper_base_client.py
CHANGED
File without changes
|
File without changes
|