architect-py 5.1.3__py3-none-any.whl → 5.1.4rc1__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 +19 -1
- architect_py/async_client.py +133 -39
- architect_py/client.py +15 -1
- architect_py/client.pyi +25 -7
- architect_py/grpc/client.py +37 -13
- architect_py/grpc/models/AlgoHelper/AlgoParamTypes.py +31 -0
- architect_py/grpc/models/AlgoHelper/__init__.py +2 -0
- architect_py/grpc/models/Auth/AuthInfoRequest.py +37 -0
- architect_py/grpc/models/Auth/AuthInfoResponse.py +30 -0
- architect_py/grpc/models/Folio/AccountHistoryRequest.py +35 -4
- architect_py/grpc/models/Marketdata/L1BookSnapshot.py +11 -3
- architect_py/grpc/models/Marketdata/TickersRequest.py +4 -1
- architect_py/grpc/models/Oms/Order.py +38 -14
- architect_py/grpc/models/Oms/PlaceOrderRequest.py +38 -14
- architect_py/grpc/models/__init__.py +4 -1
- architect_py/grpc/models/definitions.py +154 -0
- architect_py/grpc/orderflow.py +138 -0
- architect_py/tests/test_order_entry.py +9 -6
- architect_py/tests/test_orderflow.py +116 -27
- {architect_py-5.1.3.dist-info → architect_py-5.1.4rc1.dist-info}/METADATA +1 -1
- {architect_py-5.1.3.dist-info → architect_py-5.1.4rc1.dist-info}/RECORD +27 -22
- examples/funding_rate_mean_reversion_algo.py +23 -47
- scripts/correct_sync_interface.py +5 -2
- scripts/postprocess_grpc.py +17 -2
- {architect_py-5.1.3.dist-info → architect_py-5.1.4rc1.dist-info}/WHEEL +0 -0
- {architect_py-5.1.3.dist-info → architect_py-5.1.4rc1.dist-info}/licenses/LICENSE +0 -0
- {architect_py-5.1.3.dist-info → architect_py-5.1.4rc1.dist-info}/top_level.txt +0 -0
@@ -15,6 +15,12 @@ from msgspec import Meta, Struct
|
|
15
15
|
from .Marketdata.Ticker import Ticker
|
16
16
|
|
17
17
|
|
18
|
+
class AccountHistoryGranularity(str, Enum):
|
19
|
+
FiveMinutes = "FiveMinutes"
|
20
|
+
Hourly = "Hourly"
|
21
|
+
Daily = "Daily"
|
22
|
+
|
23
|
+
|
18
24
|
AccountIdOrName = str
|
19
25
|
|
20
26
|
|
@@ -177,6 +183,8 @@ class CandleWidth(int, Enum):
|
|
177
183
|
OneSecond = 1
|
178
184
|
FiveSecond = 5
|
179
185
|
OneMinute = 60
|
186
|
+
TwoMinute = 120
|
187
|
+
ThreeMinute = 180
|
180
188
|
FifteenMinute = 900
|
181
189
|
OneHour = 3600
|
182
190
|
OneDay = 86400
|
@@ -454,6 +462,7 @@ class OrderRejectReason(str, Enum):
|
|
454
462
|
InsufficientCash = "InsufficientCash"
|
455
463
|
InsufficientMargin = "InsufficientMargin"
|
456
464
|
NotEasyToBorrow = "NotEasyToBorrow"
|
465
|
+
InvalidOrder = "InvalidOrder"
|
457
466
|
Unknown = "Unknown"
|
458
467
|
|
459
468
|
|
@@ -501,6 +510,7 @@ class OrderType(str, Enum):
|
|
501
510
|
LIMIT = "LIMIT"
|
502
511
|
STOP_LOSS_LIMIT = "STOP_LOSS_LIMIT"
|
503
512
|
TAKE_PROFIT_LIMIT = "TAKE_PROFIT_LIMIT"
|
513
|
+
BRACKET = "BRACKET"
|
504
514
|
|
505
515
|
|
506
516
|
class ProductCatalogInfo(Struct, omit_defaults=True):
|
@@ -789,6 +799,42 @@ class Statement(Struct, omit_defaults=True):
|
|
789
799
|
TraderIdOrEmail = str
|
790
800
|
|
791
801
|
|
802
|
+
class TriggerLimitOrderType(Struct, omit_defaults=True):
|
803
|
+
p: Annotated[Decimal, Meta(title="limit_price")]
|
804
|
+
tp: Annotated[Decimal, Meta(title="trigger_price")]
|
805
|
+
|
806
|
+
# Constructor that takes all field titles as arguments for convenience
|
807
|
+
@classmethod
|
808
|
+
def new(
|
809
|
+
cls,
|
810
|
+
limit_price: Decimal,
|
811
|
+
trigger_price: Decimal,
|
812
|
+
):
|
813
|
+
return cls(
|
814
|
+
limit_price,
|
815
|
+
trigger_price,
|
816
|
+
)
|
817
|
+
|
818
|
+
def __str__(self) -> str:
|
819
|
+
return f"TriggerLimitOrderType(limit_price={self.p},trigger_price={self.tp})"
|
820
|
+
|
821
|
+
@property
|
822
|
+
def limit_price(self) -> Decimal:
|
823
|
+
return self.p
|
824
|
+
|
825
|
+
@limit_price.setter
|
826
|
+
def limit_price(self, value: Decimal) -> None:
|
827
|
+
self.p = value
|
828
|
+
|
829
|
+
@property
|
830
|
+
def trigger_price(self) -> Decimal:
|
831
|
+
return self.tp
|
832
|
+
|
833
|
+
@trigger_price.setter
|
834
|
+
def trigger_price(self, value: Decimal) -> None:
|
835
|
+
self.tp = value
|
836
|
+
|
837
|
+
|
792
838
|
UserId = str
|
793
839
|
|
794
840
|
|
@@ -868,6 +914,9 @@ class FillKind(int, Enum):
|
|
868
914
|
Correction = 2
|
869
915
|
|
870
916
|
|
917
|
+
HumanDuration = str
|
918
|
+
|
919
|
+
|
871
920
|
class Unit(str, Enum):
|
872
921
|
base = "base"
|
873
922
|
quote = "quote"
|
@@ -1159,6 +1208,17 @@ SnapshotOrUpdateForStringAndString = Union[
|
|
1159
1208
|
]
|
1160
1209
|
|
1161
1210
|
|
1211
|
+
class SpreaderPhase(str, Enum):
|
1212
|
+
ScanningForTakes = "ScanningForTakes"
|
1213
|
+
AwaitingOrderResults = "AwaitingOrderResults"
|
1214
|
+
OrderLockout = "OrderLockout"
|
1215
|
+
NoBbo = "NoBbo"
|
1216
|
+
NotEnoughBboSize = "NotEnoughBboSize"
|
1217
|
+
DoneOverfilled = "DoneOverfilled"
|
1218
|
+
DoneAndFullyHedged = "DoneAndFullyHedged"
|
1219
|
+
DoneAndGivingUp = "DoneAndGivingUp"
|
1220
|
+
|
1221
|
+
|
1162
1222
|
class SimpleDecimal(Struct, omit_defaults=True):
|
1163
1223
|
simple: Decimal
|
1164
1224
|
|
@@ -2255,6 +2315,100 @@ SnapshotOrUpdateForStringAndSnapshotOrUpdateForStringAndProductCatalogInfo = Uni
|
|
2255
2315
|
]
|
2256
2316
|
|
2257
2317
|
|
2318
|
+
class SpreaderParams(Struct, omit_defaults=True):
|
2319
|
+
dir: OrderDir
|
2320
|
+
leg1_marketdata_venue: str
|
2321
|
+
leg1_price_offset: Decimal
|
2322
|
+
leg1_price_ratio: Decimal
|
2323
|
+
leg1_quantity_ratio: Decimal
|
2324
|
+
leg1_symbol: str
|
2325
|
+
leg2_marketdata_venue: str
|
2326
|
+
leg2_price_offset: Decimal
|
2327
|
+
leg2_price_ratio: Decimal
|
2328
|
+
leg2_quantity_ratio: Decimal
|
2329
|
+
leg2_symbol: str
|
2330
|
+
limit_price: Decimal
|
2331
|
+
order_lockout: HumanDuration
|
2332
|
+
quantity: Decimal
|
2333
|
+
leg1_account: Optional[AccountIdOrName] = None
|
2334
|
+
leg1_execution_venue: Optional[str] = None
|
2335
|
+
leg2_account: Optional[AccountIdOrName] = None
|
2336
|
+
leg2_execution_venue: Optional[str] = None
|
2337
|
+
|
2338
|
+
# Constructor that takes all field titles as arguments for convenience
|
2339
|
+
@classmethod
|
2340
|
+
def new(
|
2341
|
+
cls,
|
2342
|
+
dir: OrderDir,
|
2343
|
+
leg1_marketdata_venue: str,
|
2344
|
+
leg1_price_offset: Decimal,
|
2345
|
+
leg1_price_ratio: Decimal,
|
2346
|
+
leg1_quantity_ratio: Decimal,
|
2347
|
+
leg1_symbol: str,
|
2348
|
+
leg2_marketdata_venue: str,
|
2349
|
+
leg2_price_offset: Decimal,
|
2350
|
+
leg2_price_ratio: Decimal,
|
2351
|
+
leg2_quantity_ratio: Decimal,
|
2352
|
+
leg2_symbol: str,
|
2353
|
+
limit_price: Decimal,
|
2354
|
+
order_lockout: HumanDuration,
|
2355
|
+
quantity: Decimal,
|
2356
|
+
leg1_account: Optional[AccountIdOrName] = None,
|
2357
|
+
leg1_execution_venue: Optional[str] = None,
|
2358
|
+
leg2_account: Optional[AccountIdOrName] = None,
|
2359
|
+
leg2_execution_venue: Optional[str] = None,
|
2360
|
+
):
|
2361
|
+
return cls(
|
2362
|
+
dir,
|
2363
|
+
leg1_marketdata_venue,
|
2364
|
+
leg1_price_offset,
|
2365
|
+
leg1_price_ratio,
|
2366
|
+
leg1_quantity_ratio,
|
2367
|
+
leg1_symbol,
|
2368
|
+
leg2_marketdata_venue,
|
2369
|
+
leg2_price_offset,
|
2370
|
+
leg2_price_ratio,
|
2371
|
+
leg2_quantity_ratio,
|
2372
|
+
leg2_symbol,
|
2373
|
+
limit_price,
|
2374
|
+
order_lockout,
|
2375
|
+
quantity,
|
2376
|
+
leg1_account,
|
2377
|
+
leg1_execution_venue,
|
2378
|
+
leg2_account,
|
2379
|
+
leg2_execution_venue,
|
2380
|
+
)
|
2381
|
+
|
2382
|
+
def __str__(self) -> str:
|
2383
|
+
return f"SpreaderParams(dir={self.dir},leg1_marketdata_venue={self.leg1_marketdata_venue},leg1_price_offset={self.leg1_price_offset},leg1_price_ratio={self.leg1_price_ratio},leg1_quantity_ratio={self.leg1_quantity_ratio},leg1_symbol={self.leg1_symbol},leg2_marketdata_venue={self.leg2_marketdata_venue},leg2_price_offset={self.leg2_price_offset},leg2_price_ratio={self.leg2_price_ratio},leg2_quantity_ratio={self.leg2_quantity_ratio},leg2_symbol={self.leg2_symbol},limit_price={self.limit_price},order_lockout={self.order_lockout},quantity={self.quantity},leg1_account={self.leg1_account},leg1_execution_venue={self.leg1_execution_venue},leg2_account={self.leg2_account},leg2_execution_venue={self.leg2_execution_venue})"
|
2384
|
+
|
2385
|
+
|
2386
|
+
class SpreaderStatus(Struct, omit_defaults=True):
|
2387
|
+
current_spreader_phase: SpreaderPhase
|
2388
|
+
leg1_fill_quantity: Decimal
|
2389
|
+
leg2_fill_quantity: Decimal
|
2390
|
+
implied_spread_vwap: Optional[Decimal] = None
|
2391
|
+
|
2392
|
+
# Constructor that takes all field titles as arguments for convenience
|
2393
|
+
@classmethod
|
2394
|
+
def new(
|
2395
|
+
cls,
|
2396
|
+
current_spreader_phase: SpreaderPhase,
|
2397
|
+
leg1_fill_quantity: Decimal,
|
2398
|
+
leg2_fill_quantity: Decimal,
|
2399
|
+
implied_spread_vwap: Optional[Decimal] = None,
|
2400
|
+
):
|
2401
|
+
return cls(
|
2402
|
+
current_spreader_phase,
|
2403
|
+
leg1_fill_quantity,
|
2404
|
+
leg2_fill_quantity,
|
2405
|
+
implied_spread_vwap,
|
2406
|
+
)
|
2407
|
+
|
2408
|
+
def __str__(self) -> str:
|
2409
|
+
return f"SpreaderStatus(current_spreader_phase={self.current_spreader_phase},leg1_fill_quantity={self.leg1_fill_quantity},leg2_fill_quantity={self.leg2_fill_quantity},implied_spread_vwap={self.implied_spread_vwap})"
|
2410
|
+
|
2411
|
+
|
2258
2412
|
class Account(Struct, omit_defaults=True):
|
2259
2413
|
id: str
|
2260
2414
|
name: AccountName
|
@@ -0,0 +1,138 @@
|
|
1
|
+
import asyncio
|
2
|
+
import contextlib
|
3
|
+
from typing import TYPE_CHECKING, Any, AsyncGenerator, AsyncIterator, Optional, Union
|
4
|
+
|
5
|
+
import grpc.aio
|
6
|
+
|
7
|
+
from architect_py.grpc.models.Orderflow.Orderflow import Orderflow
|
8
|
+
from architect_py.grpc.models.Orderflow.OrderflowRequest import (
|
9
|
+
OrderflowRequest,
|
10
|
+
OrderflowRequest_route,
|
11
|
+
OrderflowRequestUnannotatedResponseType,
|
12
|
+
)
|
13
|
+
|
14
|
+
if TYPE_CHECKING:
|
15
|
+
from architect_py.async_client import AsyncClient
|
16
|
+
|
17
|
+
_CLOSE_SENTINEL: Any = object() # internal close marker
|
18
|
+
|
19
|
+
|
20
|
+
class OrderflowChannel:
|
21
|
+
"""
|
22
|
+
Web-socket–like helper around Architect’s bidirectional Orderflow gRPC stream.
|
23
|
+
|
24
|
+
Must call OrderflowManager.start() to open the stream.
|
25
|
+
|
26
|
+
Example usage 1:
|
27
|
+
om = client.orderflow(queue_size=2048) # instantiate
|
28
|
+
await om.start() # open stream
|
29
|
+
|
30
|
+
await om.place_order(args)
|
31
|
+
update = await om.receive() # receive a single orderflow update
|
32
|
+
# to receive continuously, either but in a loop or see example usage 2.
|
33
|
+
print("first update:", update)
|
34
|
+
|
35
|
+
await om.close() # graceful shutdown, not required
|
36
|
+
|
37
|
+
Example usage 2:
|
38
|
+
async with await client.orderflow() as om: # om.start() happens automatically
|
39
|
+
async for update in om: # this will run until the stream is closed
|
40
|
+
print("order update:", update)
|
41
|
+
|
42
|
+
For more advanced usage, see the funding_rate_mean_reversion_algo.py example.
|
43
|
+
"""
|
44
|
+
|
45
|
+
def __init__(self, client: "AsyncClient", *, max_queue_size: int = 1024) -> None:
|
46
|
+
self._client = client
|
47
|
+
self._send_q: asyncio.Queue[Union[OrderflowRequest, Any]] = asyncio.Queue(
|
48
|
+
maxsize=max_queue_size
|
49
|
+
)
|
50
|
+
self._receive_q: asyncio.Queue[Orderflow] = asyncio.Queue()
|
51
|
+
self._stream_task: Optional[asyncio.Task[None]] = None
|
52
|
+
self._grpc_call: Optional[
|
53
|
+
grpc.aio.StreamStreamCall[OrderflowRequest, Orderflow]
|
54
|
+
] = None
|
55
|
+
self._closed = asyncio.Event()
|
56
|
+
|
57
|
+
async def start(self) -> None:
|
58
|
+
""" """
|
59
|
+
if self._stream_task is None:
|
60
|
+
self._stream_task = asyncio.create_task(
|
61
|
+
self._stream_loop(), name="orderflow-stream"
|
62
|
+
)
|
63
|
+
|
64
|
+
async def send(self, req: OrderflowRequest) -> None:
|
65
|
+
"""
|
66
|
+
Note that some of the OrderflowRequest types are tagged versions of the classes that exist elsewhere.
|
67
|
+
Only use the TAGGED versions, as the untagged versions will not work with the stream.
|
68
|
+
"""
|
69
|
+
await self._send_q.put(req)
|
70
|
+
|
71
|
+
async def receive(self) -> Orderflow:
|
72
|
+
"""Await the next Orderflow update from the server."""
|
73
|
+
return await self._receive_q.get()
|
74
|
+
|
75
|
+
async def close(self) -> None:
|
76
|
+
if self._stream_task is None:
|
77
|
+
return
|
78
|
+
await self._send_q.put(_CLOSE_SENTINEL)
|
79
|
+
|
80
|
+
if self._grpc_call is not None:
|
81
|
+
self._grpc_call.cancel()
|
82
|
+
|
83
|
+
self._stream_task.cancel()
|
84
|
+
with contextlib.suppress(asyncio.CancelledError):
|
85
|
+
await self._stream_task
|
86
|
+
self._stream_task = None
|
87
|
+
|
88
|
+
async def __aenter__(self):
|
89
|
+
await self.start()
|
90
|
+
return self
|
91
|
+
|
92
|
+
async def __aexit__(self, exc_type, exc, tb):
|
93
|
+
await self.close()
|
94
|
+
|
95
|
+
def __aiter__(self) -> AsyncGenerator[Orderflow, None]:
|
96
|
+
return self._iter_updates()
|
97
|
+
|
98
|
+
async def _iter_updates(self) -> AsyncGenerator[Orderflow, None]:
|
99
|
+
while not self._closed.is_set():
|
100
|
+
try:
|
101
|
+
yield await self.receive()
|
102
|
+
except asyncio.CancelledError:
|
103
|
+
break
|
104
|
+
|
105
|
+
async def _request_iter(self) -> AsyncIterator[OrderflowRequest]:
|
106
|
+
while True:
|
107
|
+
item = await self._send_q.get()
|
108
|
+
if item is _CLOSE_SENTINEL:
|
109
|
+
break
|
110
|
+
yield item
|
111
|
+
|
112
|
+
async def _stream_loop(self) -> None:
|
113
|
+
try:
|
114
|
+
async for update in self._grpc_orderflow_stream(self._request_iter()):
|
115
|
+
await self._receive_q.put(update)
|
116
|
+
finally:
|
117
|
+
self._closed.set()
|
118
|
+
|
119
|
+
async def _grpc_orderflow_stream(
|
120
|
+
self, request_iterator: AsyncIterator[OrderflowRequest]
|
121
|
+
) -> AsyncGenerator[Orderflow, None]:
|
122
|
+
"""Low-level wrapper around Architect’s gRPC bidirectional stream."""
|
123
|
+
grpc_client = await self._client.core()
|
124
|
+
decoder = grpc_client.get_decoder(OrderflowRequestUnannotatedResponseType)
|
125
|
+
|
126
|
+
stub: grpc.aio.StreamStreamMultiCallable = grpc_client.channel.stream_stream(
|
127
|
+
OrderflowRequest_route,
|
128
|
+
request_serializer=grpc_client.encoder().encode,
|
129
|
+
response_deserializer=decoder.decode,
|
130
|
+
)
|
131
|
+
|
132
|
+
self._grpc_call = stub(
|
133
|
+
request_iterator,
|
134
|
+
metadata=(("authorization", f"Bearer {grpc_client.jwt}"),),
|
135
|
+
)
|
136
|
+
|
137
|
+
async for update in self._grpc_call:
|
138
|
+
yield update
|
@@ -4,18 +4,18 @@ from decimal import Decimal
|
|
4
4
|
import pytest
|
5
5
|
|
6
6
|
from architect_py import AsyncClient, OrderDir, TickRoundMethod
|
7
|
+
from architect_py.grpc.models.definitions import OrderType
|
7
8
|
|
8
9
|
|
9
10
|
@pytest.mark.asyncio
|
10
11
|
@pytest.mark.timeout(3)
|
11
12
|
async def test_place_limit_order(async_client: AsyncClient):
|
12
|
-
# CR alee: there's no good way to get the front month future
|
13
|
-
symbol = "MET 20250530 CME Future/USD"
|
14
13
|
venue = "CME"
|
15
|
-
|
14
|
+
front_future = await async_client.get_front_future("ES CME Futures", venue)
|
15
|
+
info = await async_client.get_execution_info(front_future, venue)
|
16
16
|
assert info is not None
|
17
17
|
assert info.tick_size is not None
|
18
|
-
snap = await async_client.get_ticker(
|
18
|
+
snap = await async_client.get_ticker(front_future, venue)
|
19
19
|
assert snap is not None
|
20
20
|
assert snap.bid_price is not None
|
21
21
|
accounts = await async_client.list_accounts()
|
@@ -23,14 +23,17 @@ async def test_place_limit_order(async_client: AsyncClient):
|
|
23
23
|
|
24
24
|
# bid far below the best bid
|
25
25
|
limit_price = TickRoundMethod.FLOOR(snap.bid_price * Decimal(0.9), info.tick_size)
|
26
|
-
order = await async_client.
|
27
|
-
symbol=
|
26
|
+
order = await async_client.place_order(
|
27
|
+
symbol=front_future,
|
28
28
|
execution_venue=venue,
|
29
29
|
dir=OrderDir.BUY,
|
30
30
|
quantity=Decimal(1),
|
31
|
+
order_type=OrderType.LIMIT,
|
31
32
|
limit_price=limit_price,
|
33
|
+
post_only=False,
|
32
34
|
account=str(account.account.id),
|
33
35
|
)
|
36
|
+
|
34
37
|
assert order is not None
|
35
38
|
await asyncio.sleep(1)
|
36
39
|
await async_client.cancel_order(order.id)
|
@@ -1,41 +1,130 @@
|
|
1
|
-
"""
|
2
1
|
import asyncio
|
2
|
+
from decimal import Decimal
|
3
3
|
|
4
4
|
import pytest
|
5
5
|
|
6
|
-
from architect_py import
|
7
|
-
from architect_py.grpc.models.Orderflow.
|
6
|
+
from architect_py import *
|
7
|
+
from architect_py.grpc.models.Orderflow.Orderflow import TaggedOrderAck
|
8
|
+
from architect_py.grpc.models.Orderflow.OrderflowRequest import (
|
9
|
+
CancelOrder,
|
10
|
+
OrderflowRequestUnannotatedResponseType,
|
11
|
+
PlaceOrder,
|
12
|
+
)
|
8
13
|
|
9
14
|
|
10
|
-
|
11
|
-
|
12
|
-
|
15
|
+
@pytest.mark.asyncio
|
16
|
+
@pytest.mark.timeout(3)
|
17
|
+
async def test_orderflow(async_client: AsyncClient):
|
18
|
+
venue = "CME"
|
19
|
+
front_future = await async_client.get_front_future("ES CME Futures", venue)
|
20
|
+
async with await async_client.orderflow() as om:
|
21
|
+
l1_snapshot = await async_client.get_l1_book_snapshot(front_future, venue=venue)
|
22
|
+
assert l1_snapshot.best_bid is not None
|
23
|
+
assert l1_snapshot.best_ask is not None
|
24
|
+
spread = l1_snapshot.best_ask[0] - l1_snapshot.best_bid[0]
|
25
|
+
price: Decimal = l1_snapshot.best_bid[0] - spread * 10
|
13
26
|
|
14
|
-
|
15
|
-
|
16
|
-
|
27
|
+
accounts = await async_client.list_accounts()
|
28
|
+
account = next(
|
29
|
+
acc.account.name
|
30
|
+
for acc in accounts
|
31
|
+
if not acc.account.name.startswith("RQD")
|
32
|
+
)
|
17
33
|
|
18
|
-
|
19
|
-
|
34
|
+
await om.send(
|
35
|
+
PlaceOrder.new(
|
36
|
+
dir=OrderDir.BUY,
|
37
|
+
symbol=front_future,
|
38
|
+
time_in_force=TimeInForce.DAY,
|
39
|
+
quantity=Decimal(1),
|
40
|
+
execution_venue=venue,
|
41
|
+
order_type=OrderType.LIMIT,
|
42
|
+
limit_price=price,
|
43
|
+
post_only=False,
|
44
|
+
account=account,
|
45
|
+
)
|
46
|
+
)
|
47
|
+
print("Order placed, waiting for updates...")
|
48
|
+
i = 0
|
49
|
+
async for update in om:
|
50
|
+
assert isinstance(update, OrderflowRequestUnannotatedResponseType)
|
20
51
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
52
|
+
if isinstance(update, TaggedOrderAck):
|
53
|
+
await om.send(
|
54
|
+
CancelOrder(
|
55
|
+
id=update.id,
|
56
|
+
)
|
57
|
+
)
|
58
|
+
i += 1
|
59
|
+
if i > 1:
|
60
|
+
break
|
26
61
|
|
27
|
-
|
28
|
-
|
29
|
-
self.queue.append(item)
|
30
|
-
self.condition.notify()
|
62
|
+
await async_client.cancel_all_orders()
|
63
|
+
await async_client.close()
|
31
64
|
|
32
65
|
|
33
66
|
@pytest.mark.asyncio
|
34
|
-
@pytest.mark.timeout(
|
35
|
-
async def
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
67
|
+
@pytest.mark.timeout(4)
|
68
|
+
async def test_stream_orderflow(async_client: AsyncClient):
|
69
|
+
print("Starting to stream Orderflow events...")
|
70
|
+
|
71
|
+
asyncio.create_task(send_order(async_client))
|
72
|
+
i = 0
|
73
|
+
async for event in async_client.stream_orderflow():
|
74
|
+
print(f"Received Orderflow event: {event}")
|
75
|
+
assert isinstance(event, OrderflowRequestUnannotatedResponseType)
|
76
|
+
|
77
|
+
if isinstance(event, OrderAck):
|
78
|
+
await async_client.cancel_order(event.order_id)
|
79
|
+
|
80
|
+
i += 1
|
81
|
+
if i > 1:
|
82
|
+
break
|
83
|
+
|
84
|
+
await async_client.close()
|
85
|
+
|
86
|
+
|
87
|
+
async def send_order(client: AsyncClient):
|
88
|
+
venue = "CME"
|
89
|
+
front_future = await client.get_front_future("ES CME Futures", venue)
|
90
|
+
|
91
|
+
l1_snapshot = await client.get_l1_book_snapshot(front_future, venue=venue)
|
92
|
+
assert l1_snapshot.best_bid is not None
|
93
|
+
assert l1_snapshot.best_ask is not None
|
94
|
+
spread = l1_snapshot.best_ask[0] - l1_snapshot.best_bid[0]
|
95
|
+
price: Decimal = l1_snapshot.best_bid[0] - spread * 10
|
96
|
+
|
97
|
+
accounts = await client.list_accounts()
|
98
|
+
account = next(
|
99
|
+
acc.account.name for acc in accounts if not acc.account.name.startswith("RQD")
|
100
|
+
)
|
101
|
+
|
102
|
+
await asyncio.sleep(0.5)
|
103
|
+
await client.place_order(
|
104
|
+
dir=OrderDir.BUY,
|
105
|
+
symbol=front_future,
|
106
|
+
time_in_force=TimeInForce.DAY,
|
107
|
+
quantity=Decimal(1),
|
108
|
+
execution_venue=venue,
|
109
|
+
order_type=OrderType.LIMIT,
|
110
|
+
limit_price=price,
|
111
|
+
post_only=False,
|
112
|
+
account=account,
|
113
|
+
)
|
114
|
+
|
115
|
+
|
116
|
+
if __name__ == "__main__":
|
117
|
+
loop = asyncio.new_event_loop()
|
118
|
+
asyncio.set_event_loop(loop)
|
119
|
+
|
120
|
+
async_client = loop.run_until_complete(
|
121
|
+
AsyncClient.connect(
|
122
|
+
api_key="",
|
123
|
+
api_secret="",
|
124
|
+
paper_trading=True,
|
125
|
+
endpoint="https://app.architect.co",
|
126
|
+
)
|
127
|
+
)
|
40
128
|
|
41
|
-
|
129
|
+
loop.run_until_complete(test_stream_orderflow(async_client))
|
130
|
+
# loop.run_until_complete(test_orderflow(async_client))
|