architect-py 5.1.4b1__py3-none-any.whl → 5.1.5__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 +158 -45
- architect_py/client.py +4 -0
- architect_py/client.pyi +33 -9
- architect_py/grpc/client.py +24 -9
- 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/Core/ConfigResponse.py +7 -2
- architect_py/grpc/models/Folio/AccountHistoryRequest.py +35 -4
- architect_py/grpc/models/Folio/AccountSummary.py +7 -1
- architect_py/grpc/models/Marketdata/Candle.py +6 -0
- architect_py/grpc/models/Marketdata/L1BookSnapshot.py +17 -3
- architect_py/grpc/models/Marketdata/L2BookSnapshot.py +6 -0
- architect_py/grpc/models/Marketdata/Liquidation.py +6 -0
- architect_py/grpc/models/Marketdata/Ticker.py +6 -0
- architect_py/grpc/models/Marketdata/Trade.py +6 -0
- 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 +173 -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/tests/test_positions.py +173 -0
- architect_py/tests/test_rounding.py +28 -28
- architect_py/utils/pandas.py +50 -1
- {architect_py-5.1.4b1.dist-info → architect_py-5.1.5.dist-info}/METADATA +1 -1
- {architect_py-5.1.4b1.dist-info → architect_py-5.1.5.dist-info}/RECORD +35 -29
- examples/funding_rate_mean_reversion_algo.py +23 -47
- scripts/postprocess_grpc.py +17 -2
- {architect_py-5.1.4b1.dist-info → architect_py-5.1.5.dist-info}/WHEEL +0 -0
- {architect_py-5.1.4b1.dist-info → architect_py-5.1.5.dist-info}/licenses/LICENSE +0 -0
- {architect_py-5.1.4b1.dist-info → architect_py-5.1.5.dist-info}/top_level.txt +0 -0
@@ -11,6 +11,9 @@ from .Algo.StartAlgoRequest import StartAlgoRequest
|
|
11
11
|
from .Algo.StartAlgoResponse import StartAlgoResponse
|
12
12
|
from .Algo.StopAlgoRequest import StopAlgoRequest
|
13
13
|
from .Algo.StopAlgoResponse import StopAlgoResponse
|
14
|
+
from .AlgoHelper.AlgoParamTypes import AlgoParamTypes
|
15
|
+
from .Auth.AuthInfoRequest import AuthInfoRequest
|
16
|
+
from .Auth.AuthInfoResponse import AuthInfoResponse
|
14
17
|
from .Auth.CreateJwtRequest import CreateJwtRequest
|
15
18
|
from .Auth.CreateJwtResponse import CreateJwtResponse
|
16
19
|
from .Boss.DepositsRequest import DepositsRequest
|
@@ -110,4 +113,4 @@ from .Symbology.UploadProductCatalogResponse import UploadProductCatalogResponse
|
|
110
113
|
from .Symbology.UploadSymbologyRequest import UploadSymbologyRequest
|
111
114
|
from .Symbology.UploadSymbologyResponse import UploadSymbologyResponse
|
112
115
|
|
113
|
-
__all__ = ["AccountsRequest", "AccountsResponse", "AlgoOrder", "AlgoOrderRequest", "AlgoOrdersRequest", "AlgoOrdersResponse", "CreateAlgoOrderRequest", "PauseAlgoRequest", "PauseAlgoResponse", "StartAlgoRequest", "StartAlgoResponse", "StopAlgoRequest", "StopAlgoResponse", "CreateJwtRequest", "CreateJwtResponse", "DepositsRequest", "DepositsResponse", "RqdAccountStatisticsRequest", "RqdAccountStatisticsResponse", "StatementUrlRequest", "StatementUrlResponse", "StatementsRequest", "StatementsResponse", "WithdrawalsRequest", "WithdrawalsResponse", "ConfigRequest", "ConfigResponse", "RestartCptyRequest", "RestartCptyResponse", "CptyRequest", "CptyResponse", "CptyStatus", "CptyStatusRequest", "CptysRequest", "CptysResponse", "AccountHistoryRequest", "AccountHistoryResponse", "AccountSummariesRequest", "AccountSummariesResponse", "AccountSummary", "AccountSummaryRequest", "HistoricalFillsRequest", "HistoricalFillsResponse", "HistoricalOrdersRequest", "HistoricalOrdersResponse", "HealthCheckRequest", "HealthCheckResponse", "ArrayOfL1BookSnapshot", "Candle", "HistoricalCandlesRequest", "HistoricalCandlesResponse", "L1BookSnapshot", "L1BookSnapshotRequest", "L1BookSnapshotsRequest", "L2BookSnapshot", "L2BookSnapshotRequest", "L2BookUpdate", "Liquidation", "MarketStatus", "MarketStatusRequest", "SubscribeCandlesRequest", "SubscribeCurrentCandlesRequest", "SubscribeL1BookSnapshotsRequest", "SubscribeL2BookUpdatesRequest", "SubscribeLiquidationsRequest", "SubscribeManyCandlesRequest", "SubscribeTickersRequest", "SubscribeTradesRequest", "Ticker", "TickerRequest", "TickerUpdate", "TickersRequest", "TickersResponse", "Trade", "Cancel", "CancelAllOrdersRequest", "CancelAllOrdersResponse", "CancelOrderRequest", "OpenOrdersRequest", "OpenOrdersResponse", "Order", "PendingCancelsRequest", "PendingCancelsResponse", "PlaceOrderRequest", "OptionsChain", "OptionsChainGreeks", "OptionsChainGreeksRequest", "OptionsChainRequest", "OptionsExpirations", "OptionsExpirationsRequest", "Dropcopy", "DropcopyRequest", "Orderflow", "OrderflowRequest", "SubscribeOrderflowRequest", "DownloadProductCatalogRequest", "DownloadProductCatalogResponse", "ExecutionInfoRequest", "ExecutionInfoResponse", "PruneExpiredSymbolsRequest", "PruneExpiredSymbolsResponse", "SubscribeSymbology", "SymbologyRequest", "SymbologySnapshot", "SymbologyUpdate", "SymbolsRequest", "SymbolsResponse", "UploadProductCatalogRequest", "UploadProductCatalogResponse", "UploadSymbologyRequest", "UploadSymbologyResponse"]
|
116
|
+
__all__ = ["AccountsRequest", "AccountsResponse", "AlgoOrder", "AlgoOrderRequest", "AlgoOrdersRequest", "AlgoOrdersResponse", "CreateAlgoOrderRequest", "PauseAlgoRequest", "PauseAlgoResponse", "StartAlgoRequest", "StartAlgoResponse", "StopAlgoRequest", "StopAlgoResponse", "AlgoParamTypes", "AuthInfoRequest", "AuthInfoResponse", "CreateJwtRequest", "CreateJwtResponse", "DepositsRequest", "DepositsResponse", "RqdAccountStatisticsRequest", "RqdAccountStatisticsResponse", "StatementUrlRequest", "StatementUrlResponse", "StatementsRequest", "StatementsResponse", "WithdrawalsRequest", "WithdrawalsResponse", "ConfigRequest", "ConfigResponse", "RestartCptyRequest", "RestartCptyResponse", "CptyRequest", "CptyResponse", "CptyStatus", "CptyStatusRequest", "CptysRequest", "CptysResponse", "AccountHistoryRequest", "AccountHistoryResponse", "AccountSummariesRequest", "AccountSummariesResponse", "AccountSummary", "AccountSummaryRequest", "HistoricalFillsRequest", "HistoricalFillsResponse", "HistoricalOrdersRequest", "HistoricalOrdersResponse", "HealthCheckRequest", "HealthCheckResponse", "ArrayOfL1BookSnapshot", "Candle", "HistoricalCandlesRequest", "HistoricalCandlesResponse", "L1BookSnapshot", "L1BookSnapshotRequest", "L1BookSnapshotsRequest", "L2BookSnapshot", "L2BookSnapshotRequest", "L2BookUpdate", "Liquidation", "MarketStatus", "MarketStatusRequest", "SubscribeCandlesRequest", "SubscribeCurrentCandlesRequest", "SubscribeL1BookSnapshotsRequest", "SubscribeL2BookUpdatesRequest", "SubscribeLiquidationsRequest", "SubscribeManyCandlesRequest", "SubscribeTickersRequest", "SubscribeTradesRequest", "Ticker", "TickerRequest", "TickerUpdate", "TickersRequest", "TickersResponse", "Trade", "Cancel", "CancelAllOrdersRequest", "CancelAllOrdersResponse", "CancelOrderRequest", "OpenOrdersRequest", "OpenOrdersResponse", "Order", "PendingCancelsRequest", "PendingCancelsResponse", "PlaceOrderRequest", "OptionsChain", "OptionsChainGreeks", "OptionsChainGreeksRequest", "OptionsChainRequest", "OptionsExpirations", "OptionsExpirationsRequest", "Dropcopy", "DropcopyRequest", "Orderflow", "OrderflowRequest", "SubscribeOrderflowRequest", "DownloadProductCatalogRequest", "DownloadProductCatalogResponse", "ExecutionInfoRequest", "ExecutionInfoResponse", "PruneExpiredSymbolsRequest", "PruneExpiredSymbolsResponse", "SubscribeSymbology", "SymbologyRequest", "SymbologySnapshot", "SymbologyUpdate", "SymbolsRequest", "SymbolsResponse", "UploadProductCatalogRequest", "UploadProductCatalogResponse", "UploadSymbologyRequest", "UploadSymbologyResponse"]
|
@@ -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
|
@@ -414,10 +422,16 @@ class L2BookDiff(Struct, omit_defaults=True):
|
|
414
422
|
|
415
423
|
@property
|
416
424
|
def datetime(self) -> datetime:
|
425
|
+
"""
|
426
|
+
Convenience property to get the timestamp as a datetime object in UTC.
|
427
|
+
"""
|
417
428
|
return datetime.fromtimestamp(self.ts, tz=timezone.utc)
|
418
429
|
|
419
430
|
@property
|
420
431
|
def datetime_local(self) -> datetime:
|
432
|
+
"""
|
433
|
+
Convenience property to get the timestamp as a datetime object in local time.
|
434
|
+
"""
|
421
435
|
return datetime.fromtimestamp(self.ts)
|
422
436
|
|
423
437
|
|
@@ -502,6 +516,7 @@ class OrderType(str, Enum):
|
|
502
516
|
LIMIT = "LIMIT"
|
503
517
|
STOP_LOSS_LIMIT = "STOP_LOSS_LIMIT"
|
504
518
|
TAKE_PROFIT_LIMIT = "TAKE_PROFIT_LIMIT"
|
519
|
+
BRACKET = "BRACKET"
|
505
520
|
|
506
521
|
|
507
522
|
class ProductCatalogInfo(Struct, omit_defaults=True):
|
@@ -790,6 +805,42 @@ class Statement(Struct, omit_defaults=True):
|
|
790
805
|
TraderIdOrEmail = str
|
791
806
|
|
792
807
|
|
808
|
+
class TriggerLimitOrderType(Struct, omit_defaults=True):
|
809
|
+
p: Annotated[Decimal, Meta(title="limit_price")]
|
810
|
+
tp: Annotated[Decimal, Meta(title="trigger_price")]
|
811
|
+
|
812
|
+
# Constructor that takes all field titles as arguments for convenience
|
813
|
+
@classmethod
|
814
|
+
def new(
|
815
|
+
cls,
|
816
|
+
limit_price: Decimal,
|
817
|
+
trigger_price: Decimal,
|
818
|
+
):
|
819
|
+
return cls(
|
820
|
+
limit_price,
|
821
|
+
trigger_price,
|
822
|
+
)
|
823
|
+
|
824
|
+
def __str__(self) -> str:
|
825
|
+
return f"TriggerLimitOrderType(limit_price={self.p},trigger_price={self.tp})"
|
826
|
+
|
827
|
+
@property
|
828
|
+
def limit_price(self) -> Decimal:
|
829
|
+
return self.p
|
830
|
+
|
831
|
+
@limit_price.setter
|
832
|
+
def limit_price(self, value: Decimal) -> None:
|
833
|
+
self.p = value
|
834
|
+
|
835
|
+
@property
|
836
|
+
def trigger_price(self) -> Decimal:
|
837
|
+
return self.tp
|
838
|
+
|
839
|
+
@trigger_price.setter
|
840
|
+
def trigger_price(self, value: Decimal) -> None:
|
841
|
+
self.tp = value
|
842
|
+
|
843
|
+
|
793
844
|
UserId = str
|
794
845
|
|
795
846
|
|
@@ -869,6 +920,9 @@ class FillKind(int, Enum):
|
|
869
920
|
Correction = 2
|
870
921
|
|
871
922
|
|
923
|
+
HumanDuration = str
|
924
|
+
|
925
|
+
|
872
926
|
class Unit(str, Enum):
|
873
927
|
base = "base"
|
874
928
|
quote = "quote"
|
@@ -1160,6 +1214,17 @@ SnapshotOrUpdateForStringAndString = Union[
|
|
1160
1214
|
]
|
1161
1215
|
|
1162
1216
|
|
1217
|
+
class SpreaderPhase(str, Enum):
|
1218
|
+
ScanningForTakes = "ScanningForTakes"
|
1219
|
+
AwaitingOrderResults = "AwaitingOrderResults"
|
1220
|
+
OrderLockout = "OrderLockout"
|
1221
|
+
NoBbo = "NoBbo"
|
1222
|
+
NotEnoughBboSize = "NotEnoughBboSize"
|
1223
|
+
DoneOverfilled = "DoneOverfilled"
|
1224
|
+
DoneAndFullyHedged = "DoneAndFullyHedged"
|
1225
|
+
DoneAndGivingUp = "DoneAndGivingUp"
|
1226
|
+
|
1227
|
+
|
1163
1228
|
class SimpleDecimal(Struct, omit_defaults=True):
|
1164
1229
|
simple: Decimal
|
1165
1230
|
|
@@ -1795,6 +1860,20 @@ class Fill(Struct, omit_defaults=True):
|
|
1795
1860
|
def trade_time(self, value: int) -> None:
|
1796
1861
|
self.ts = value
|
1797
1862
|
|
1863
|
+
@property
|
1864
|
+
def datetime(self) -> datetime:
|
1865
|
+
"""
|
1866
|
+
Convenience property to get the timestamp as a datetime object in UTC.
|
1867
|
+
"""
|
1868
|
+
return datetime.fromtimestamp(self.ts, tz=timezone.utc)
|
1869
|
+
|
1870
|
+
@property
|
1871
|
+
def datetime_local(self) -> datetime:
|
1872
|
+
"""
|
1873
|
+
Convenience property to get the timestamp as a datetime object in local time.
|
1874
|
+
"""
|
1875
|
+
return datetime.fromtimestamp(self.ts)
|
1876
|
+
|
1798
1877
|
@property
|
1799
1878
|
def execution_venue(self) -> str:
|
1800
1879
|
return self.x
|
@@ -2256,6 +2335,100 @@ SnapshotOrUpdateForStringAndSnapshotOrUpdateForStringAndProductCatalogInfo = Uni
|
|
2256
2335
|
]
|
2257
2336
|
|
2258
2337
|
|
2338
|
+
class SpreaderParams(Struct, omit_defaults=True):
|
2339
|
+
dir: OrderDir
|
2340
|
+
leg1_marketdata_venue: str
|
2341
|
+
leg1_price_offset: Decimal
|
2342
|
+
leg1_price_ratio: Decimal
|
2343
|
+
leg1_quantity_ratio: Decimal
|
2344
|
+
leg1_symbol: str
|
2345
|
+
leg2_marketdata_venue: str
|
2346
|
+
leg2_price_offset: Decimal
|
2347
|
+
leg2_price_ratio: Decimal
|
2348
|
+
leg2_quantity_ratio: Decimal
|
2349
|
+
leg2_symbol: str
|
2350
|
+
limit_price: Decimal
|
2351
|
+
order_lockout: HumanDuration
|
2352
|
+
quantity: Decimal
|
2353
|
+
leg1_account: Optional[AccountIdOrName] = None
|
2354
|
+
leg1_execution_venue: Optional[str] = None
|
2355
|
+
leg2_account: Optional[AccountIdOrName] = None
|
2356
|
+
leg2_execution_venue: Optional[str] = None
|
2357
|
+
|
2358
|
+
# Constructor that takes all field titles as arguments for convenience
|
2359
|
+
@classmethod
|
2360
|
+
def new(
|
2361
|
+
cls,
|
2362
|
+
dir: OrderDir,
|
2363
|
+
leg1_marketdata_venue: str,
|
2364
|
+
leg1_price_offset: Decimal,
|
2365
|
+
leg1_price_ratio: Decimal,
|
2366
|
+
leg1_quantity_ratio: Decimal,
|
2367
|
+
leg1_symbol: str,
|
2368
|
+
leg2_marketdata_venue: str,
|
2369
|
+
leg2_price_offset: Decimal,
|
2370
|
+
leg2_price_ratio: Decimal,
|
2371
|
+
leg2_quantity_ratio: Decimal,
|
2372
|
+
leg2_symbol: str,
|
2373
|
+
limit_price: Decimal,
|
2374
|
+
order_lockout: HumanDuration,
|
2375
|
+
quantity: Decimal,
|
2376
|
+
leg1_account: Optional[AccountIdOrName] = None,
|
2377
|
+
leg1_execution_venue: Optional[str] = None,
|
2378
|
+
leg2_account: Optional[AccountIdOrName] = None,
|
2379
|
+
leg2_execution_venue: Optional[str] = None,
|
2380
|
+
):
|
2381
|
+
return cls(
|
2382
|
+
dir,
|
2383
|
+
leg1_marketdata_venue,
|
2384
|
+
leg1_price_offset,
|
2385
|
+
leg1_price_ratio,
|
2386
|
+
leg1_quantity_ratio,
|
2387
|
+
leg1_symbol,
|
2388
|
+
leg2_marketdata_venue,
|
2389
|
+
leg2_price_offset,
|
2390
|
+
leg2_price_ratio,
|
2391
|
+
leg2_quantity_ratio,
|
2392
|
+
leg2_symbol,
|
2393
|
+
limit_price,
|
2394
|
+
order_lockout,
|
2395
|
+
quantity,
|
2396
|
+
leg1_account,
|
2397
|
+
leg1_execution_venue,
|
2398
|
+
leg2_account,
|
2399
|
+
leg2_execution_venue,
|
2400
|
+
)
|
2401
|
+
|
2402
|
+
def __str__(self) -> str:
|
2403
|
+
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})"
|
2404
|
+
|
2405
|
+
|
2406
|
+
class SpreaderStatus(Struct, omit_defaults=True):
|
2407
|
+
current_spreader_phase: SpreaderPhase
|
2408
|
+
leg1_fill_quantity: Decimal
|
2409
|
+
leg2_fill_quantity: Decimal
|
2410
|
+
implied_spread_vwap: Optional[Decimal] = None
|
2411
|
+
|
2412
|
+
# Constructor that takes all field titles as arguments for convenience
|
2413
|
+
@classmethod
|
2414
|
+
def new(
|
2415
|
+
cls,
|
2416
|
+
current_spreader_phase: SpreaderPhase,
|
2417
|
+
leg1_fill_quantity: Decimal,
|
2418
|
+
leg2_fill_quantity: Decimal,
|
2419
|
+
implied_spread_vwap: Optional[Decimal] = None,
|
2420
|
+
):
|
2421
|
+
return cls(
|
2422
|
+
current_spreader_phase,
|
2423
|
+
leg1_fill_quantity,
|
2424
|
+
leg2_fill_quantity,
|
2425
|
+
implied_spread_vwap,
|
2426
|
+
)
|
2427
|
+
|
2428
|
+
def __str__(self) -> str:
|
2429
|
+
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})"
|
2430
|
+
|
2431
|
+
|
2259
2432
|
class Account(Struct, omit_defaults=True):
|
2260
2433
|
id: str
|
2261
2434
|
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))
|