architect-py 3.2.1__py3-none-any.whl → 5.0.0__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 +18 -2
- architect_py/async_client.py +1089 -658
- architect_py/client.py +36 -33
- architect_py/client_interface.py +63 -0
- architect_py/common_types/__init__.py +6 -0
- architect_py/common_types/order_dir.py +91 -0
- architect_py/common_types/scalars.py +25 -0
- architect_py/common_types/tradable_product.py +59 -0
- architect_py/graphql_client/__init__.py +2 -0
- architect_py/graphql_client/client.py +3 -6
- architect_py/graphql_client/enums.py +5 -0
- architect_py/graphql_client/fragments.py +3 -6
- architect_py/graphql_client/get_fills_query.py +2 -1
- architect_py/graphql_client/search_symbols_query.py +2 -1
- architect_py/graphql_client/subscribe_orderflow.py +2 -1
- architect_py/graphql_client/subscribe_trades.py +2 -1
- architect_py/grpc/__init__.py +145 -0
- architect_py/grpc/client.py +94 -0
- architect_py/{grpc_client → grpc/models}/Accounts/AccountsRequest.py +6 -3
- architect_py/{grpc_client → grpc/models}/Accounts/AccountsResponse.py +1 -1
- architect_py/{grpc_client → grpc/models}/Accounts/__init__.py +1 -1
- architect_py/grpc/models/Algo/AlgoOrder.py +114 -0
- architect_py/grpc/models/Algo/AlgoOrderRequest.py +46 -0
- architect_py/grpc/models/Algo/AlgoOrdersRequest.py +72 -0
- architect_py/grpc/models/Algo/AlgoOrdersResponse.py +27 -0
- architect_py/grpc/models/Algo/CreateAlgoOrderRequest.py +56 -0
- architect_py/grpc/models/Algo/PauseAlgoRequest.py +42 -0
- architect_py/grpc/models/Algo/PauseAlgoResponse.py +20 -0
- architect_py/grpc/models/Algo/StartAlgoRequest.py +42 -0
- architect_py/grpc/models/Algo/StartAlgoResponse.py +20 -0
- architect_py/grpc/models/Algo/StopAlgoRequest.py +42 -0
- architect_py/grpc/models/Algo/StopAlgoResponse.py +20 -0
- architect_py/{grpc_client → grpc/models}/Algo/__init__.py +1 -1
- architect_py/grpc/models/Auth/CreateJwtRequest.py +47 -0
- architect_py/grpc/models/Auth/CreateJwtResponse.py +23 -0
- architect_py/{grpc_client/Cpty → grpc/models/Auth}/__init__.py +1 -1
- architect_py/grpc/models/Boss/DepositsRequest.py +40 -0
- architect_py/grpc/models/Boss/DepositsResponse.py +27 -0
- architect_py/grpc/models/Boss/RqdAccountStatisticsRequest.py +42 -0
- architect_py/grpc/models/Boss/RqdAccountStatisticsResponse.py +25 -0
- architect_py/grpc/models/Boss/StatementUrlRequest.py +40 -0
- architect_py/grpc/models/Boss/StatementUrlResponse.py +23 -0
- architect_py/grpc/models/Boss/StatementsRequest.py +40 -0
- architect_py/grpc/models/Boss/StatementsResponse.py +27 -0
- architect_py/grpc/models/Boss/WithdrawalsRequest.py +40 -0
- architect_py/grpc/models/Boss/WithdrawalsResponse.py +27 -0
- architect_py/{grpc_client/Folio → grpc/models/Boss}/__init__.py +1 -1
- architect_py/grpc/models/Core/ConfigRequest.py +37 -0
- architect_py/grpc/models/Core/ConfigResponse.py +25 -0
- architect_py/grpc/models/Core/__init__.py +2 -0
- architect_py/{grpc_client → grpc/models}/Cpty/CptyRequest.py +5 -4
- architect_py/{grpc_client → grpc/models}/Cpty/CptyResponse.py +6 -6
- architect_py/grpc/models/Cpty/CptyStatus.py +48 -0
- architect_py/grpc/models/Cpty/CptyStatusRequest.py +45 -0
- architect_py/grpc/models/Cpty/CptysRequest.py +37 -0
- architect_py/grpc/models/Cpty/CptysResponse.py +27 -0
- architect_py/grpc/models/Cpty/__init__.py +2 -0
- architect_py/{grpc_client → grpc/models}/Folio/AccountHistoryRequest.py +2 -2
- architect_py/{grpc_client → grpc/models}/Folio/AccountHistoryResponse.py +1 -1
- architect_py/{grpc_client → grpc/models}/Folio/AccountSummariesRequest.py +2 -2
- architect_py/{grpc_client → grpc/models}/Folio/AccountSummariesResponse.py +1 -1
- architect_py/{grpc_client → grpc/models}/Folio/AccountSummary.py +1 -1
- architect_py/{grpc_client → grpc/models}/Folio/AccountSummaryRequest.py +2 -2
- architect_py/{grpc_client → grpc/models}/Folio/HistoricalFillsRequest.py +7 -4
- architect_py/{grpc_client → grpc/models}/Folio/HistoricalFillsResponse.py +1 -1
- architect_py/{grpc_client → grpc/models}/Folio/HistoricalOrdersRequest.py +3 -3
- architect_py/{grpc_client → grpc/models}/Folio/HistoricalOrdersResponse.py +1 -1
- architect_py/grpc/models/Folio/__init__.py +2 -0
- architect_py/{grpc_client → grpc/models}/Health/HealthCheckRequest.py +2 -2
- architect_py/{grpc_client → grpc/models}/Health/HealthCheckResponse.py +1 -1
- architect_py/grpc/models/Health/__init__.py +2 -0
- architect_py/{grpc_client → grpc/models}/Marketdata/Candle.py +1 -1
- architect_py/{grpc_client → grpc/models}/Marketdata/HistoricalCandlesRequest.py +11 -8
- architect_py/{grpc_client → grpc/models}/Marketdata/HistoricalCandlesResponse.py +1 -1
- architect_py/{grpc_client → grpc/models}/Marketdata/L1BookSnapshot.py +52 -5
- architect_py/{grpc_client → grpc/models}/Marketdata/L1BookSnapshotRequest.py +8 -3
- architect_py/{grpc_client → grpc/models}/Marketdata/L1BookSnapshotsRequest.py +6 -3
- architect_py/{grpc_client → grpc/models}/Marketdata/L2BookSnapshot.py +1 -1
- architect_py/{grpc_client → grpc/models}/Marketdata/L2BookSnapshotRequest.py +2 -2
- architect_py/{grpc_client → grpc/models}/Marketdata/Liquidation.py +2 -2
- architect_py/{grpc_client → grpc/models}/Marketdata/MarketStatus.py +1 -1
- architect_py/{grpc_client → grpc/models}/Marketdata/MarketStatusRequest.py +2 -2
- architect_py/{grpc_client → grpc/models}/Marketdata/SubscribeCandlesRequest.py +2 -2
- architect_py/{grpc_client → grpc/models}/Marketdata/SubscribeCurrentCandlesRequest.py +3 -4
- architect_py/{grpc_client → grpc/models}/Marketdata/SubscribeL1BookSnapshotsRequest.py +6 -3
- architect_py/{grpc_client → grpc/models}/Marketdata/SubscribeL2BookUpdatesRequest.py +2 -2
- architect_py/{grpc_client → grpc/models}/Marketdata/SubscribeLiquidationsRequest.py +2 -2
- architect_py/{grpc_client → grpc/models}/Marketdata/SubscribeManyCandlesRequest.py +2 -2
- architect_py/{grpc_client → grpc/models}/Marketdata/SubscribeTickersRequest.py +2 -2
- architect_py/{grpc_client → grpc/models}/Marketdata/SubscribeTradesRequest.py +2 -2
- architect_py/{grpc_client → grpc/models}/Marketdata/Ticker.py +14 -3
- architect_py/{grpc_client → grpc/models}/Marketdata/TickerRequest.py +2 -2
- architect_py/{grpc_client → grpc/models}/Marketdata/TickersRequest.py +2 -2
- architect_py/{grpc_client → grpc/models}/Marketdata/TickersResponse.py +1 -1
- architect_py/{grpc_client → grpc/models}/Marketdata/Trade.py +2 -2
- architect_py/grpc/models/Marketdata/__init__.py +2 -0
- architect_py/grpc/models/Oms/Cancel.py +90 -0
- architect_py/{grpc_client → grpc/models}/Oms/CancelAllOrdersRequest.py +2 -2
- architect_py/{grpc_client → grpc/models}/Oms/CancelAllOrdersResponse.py +1 -1
- architect_py/{grpc_client → grpc/models}/Oms/CancelOrderRequest.py +2 -2
- architect_py/{grpc_client → grpc/models}/Oms/OpenOrdersRequest.py +2 -2
- architect_py/{grpc_client → grpc/models}/Oms/OpenOrdersResponse.py +1 -1
- architect_py/{grpc_client → grpc/models}/Oms/Order.py +6 -13
- architect_py/{grpc_client → grpc/models}/Oms/PendingCancelsRequest.py +2 -2
- architect_py/{grpc_client → grpc/models}/Oms/PendingCancelsResponse.py +1 -1
- architect_py/{grpc_client → grpc/models}/Oms/PlaceOrderRequest.py +16 -23
- architect_py/grpc/models/Oms/__init__.py +2 -0
- architect_py/grpc/models/OptionsMarketdata/OptionsChain.py +30 -0
- architect_py/grpc/models/OptionsMarketdata/OptionsChainGreeks.py +30 -0
- architect_py/grpc/models/OptionsMarketdata/OptionsChainGreeksRequest.py +47 -0
- architect_py/grpc/models/OptionsMarketdata/OptionsChainRequest.py +45 -0
- architect_py/grpc/models/OptionsMarketdata/OptionsExpirations.py +29 -0
- architect_py/grpc/models/OptionsMarketdata/OptionsExpirationsRequest.py +42 -0
- architect_py/grpc/models/OptionsMarketdata/__init__.py +2 -0
- architect_py/{grpc_client → grpc/models}/Orderflow/DropcopyRequest.py +2 -2
- architect_py/{grpc_client → grpc/models}/Orderflow/OrderflowRequest.py +2 -1
- architect_py/{grpc_client → grpc/models}/Orderflow/SubscribeOrderflowRequest.py +2 -2
- architect_py/grpc/models/Orderflow/__init__.py +2 -0
- architect_py/grpc/models/Symbology/DownloadProductCatalogRequest.py +42 -0
- architect_py/grpc/models/Symbology/DownloadProductCatalogResponse.py +27 -0
- architect_py/grpc/models/Symbology/ExecutionInfoRequest.py +47 -0
- architect_py/grpc/models/Symbology/ExecutionInfoResponse.py +27 -0
- architect_py/{grpc_client → grpc/models}/Symbology/PruneExpiredSymbolsRequest.py +2 -2
- architect_py/{grpc_client → grpc/models}/Symbology/PruneExpiredSymbolsResponse.py +1 -1
- architect_py/{grpc_client → grpc/models}/Symbology/SubscribeSymbology.py +1 -1
- architect_py/{grpc_client → grpc/models}/Symbology/SymbologyRequest.py +2 -2
- architect_py/{grpc_client → grpc/models}/Symbology/SymbologySnapshot.py +7 -2
- architect_py/{grpc_client → grpc/models}/Symbology/SymbologyUpdate.py +9 -2
- architect_py/{grpc_client → grpc/models}/Symbology/SymbolsRequest.py +2 -2
- architect_py/{grpc_client → grpc/models}/Symbology/SymbolsResponse.py +1 -1
- architect_py/grpc/models/Symbology/UploadProductCatalogRequest.py +49 -0
- architect_py/grpc/models/Symbology/UploadProductCatalogResponse.py +20 -0
- architect_py/{grpc_client → grpc/models}/Symbology/UploadSymbologyRequest.py +2 -2
- architect_py/{grpc_client → grpc/models}/Symbology/UploadSymbologyResponse.py +1 -1
- architect_py/grpc/models/Symbology/__init__.py +2 -0
- architect_py/grpc/models/__init__.py +2 -0
- architect_py/{grpc_client → grpc/models}/definitions.py +671 -934
- architect_py/grpc/resolve_endpoint.py +70 -0
- architect_py/{grpc_client/grpc_server.py → grpc/server.py} +13 -9
- architect_py/grpc/utils.py +32 -0
- architect_py/internal_utils/__init__.py +0 -0
- architect_py/internal_utils/no_pandas.py +3 -0
- architect_py/tests/conftest.py +91 -85
- architect_py/tests/test_book_building.py +49 -50
- architect_py/tests/test_marketdata.py +168 -0
- architect_py/tests/test_order_entry.py +37 -0
- architect_py/tests/test_orderflow.py +41 -0
- architect_py/tests/test_portfolio_management.py +23 -0
- architect_py/tests/test_rounding.py +28 -28
- architect_py/tests/test_symbology.py +37 -30
- architect_py/utils/nearest_tick.py +2 -5
- architect_py/utils/nearest_tick_2.py +1 -2
- architect_py/utils/orderbook.py +35 -0
- architect_py/utils/pandas.py +44 -0
- architect_py/utils/price_bands.py +0 -3
- architect_py/utils/symbol_parsing.py +29 -0
- architect_py-5.0.0.dist-info/METADATA +54 -0
- architect_py-5.0.0.dist-info/RECORD +214 -0
- {architect_py-3.2.1.dist-info → architect_py-5.0.0.dist-info}/WHEEL +2 -1
- architect_py-5.0.0.dist-info/top_level.txt +4 -0
- examples/__init__.py +0 -0
- examples/book_subscription.py +52 -0
- examples/candles.py +30 -0
- examples/common.py +116 -0
- examples/external_cpty.py +77 -0
- examples/funding_rate_mean_reversion_algo.py +186 -0
- examples/order_sending.py +91 -0
- examples/stream_l1_marketdata.py +25 -0
- examples/stream_l2_marketdata.py +38 -0
- examples/trades.py +21 -0
- examples/tutorial_async.py +86 -0
- examples/tutorial_sync.py +95 -0
- scripts/generate_functions_md.py +166 -0
- scripts/generate_sync_interface.py +226 -0
- scripts/postprocess_grpc.py +604 -0
- scripts/preprocess_grpc_schema.py +708 -0
- templates/exceptions.py +83 -0
- templates/juniper_base_client.py +371 -0
- architect_py/client_protocol.py +0 -52
- architect_py/grpc_client/Algo/AlgoOrderForTwapAlgo.py +0 -61
- architect_py/grpc_client/Algo/CreateAlgoOrderRequestForTwapAlgo.py +0 -59
- architect_py/grpc_client/Algo/ModifyAlgoOrderRequestForTwapAlgo.py +0 -45
- architect_py/grpc_client/Folio/AggregatedAccountSummariesRequest.py +0 -59
- architect_py/grpc_client/Folio/AggregatedAccountSummariesResponse.py +0 -27
- architect_py/grpc_client/Health/__init__.py +0 -2
- architect_py/grpc_client/Marketdata/__init__.py +0 -2
- architect_py/grpc_client/Oms/Cancel.py +0 -42
- architect_py/grpc_client/Oms/__init__.py +0 -2
- architect_py/grpc_client/Orderflow/__init__.py +0 -2
- architect_py/grpc_client/Symbology/__init__.py +0 -2
- architect_py/grpc_client/__init__.py +0 -2
- architect_py/grpc_client/grpc_client.py +0 -405
- architect_py/scalars.py +0 -172
- architect_py/tests/test_accounts.py +0 -31
- architect_py/tests/test_client.py +0 -29
- architect_py/tests/test_grpc_client.py +0 -30
- architect_py/tests/test_order_sending.py +0 -61
- architect_py/tests/test_snapshots.py +0 -52
- architect_py/tests/test_subscriptions.py +0 -129
- architect_py-3.2.1.dist-info/METADATA +0 -212
- architect_py-3.2.1.dist-info/RECORD +0 -146
- /architect_py/{grpc_client → grpc/models}/Marketdata/ArrayOfL1BookSnapshot.py +0 -0
- /architect_py/{grpc_client → grpc/models}/Marketdata/L2BookUpdate.py +0 -0
- /architect_py/{grpc_client → grpc/models}/Marketdata/TickerUpdate.py +0 -0
- /architect_py/{grpc_client → grpc/models}/Orderflow/Dropcopy.py +0 -0
- /architect_py/{grpc_client → grpc/models}/Orderflow/Orderflow.py +0 -0
- {architect_py-3.2.1.dist-info → architect_py-5.0.0.dist-info/licenses}/LICENSE +0 -0
@@ -1,59 +0,0 @@
|
|
1
|
-
# generated by datamodel-codegen:
|
2
|
-
# filename: Folio/AggregatedAccountSummariesRequest.json
|
3
|
-
|
4
|
-
from __future__ import annotations
|
5
|
-
from architect_py.grpc_client.Folio.AggregatedAccountSummariesResponse import (
|
6
|
-
AggregatedAccountSummariesResponse,
|
7
|
-
)
|
8
|
-
|
9
|
-
from typing import Annotated, List, Optional
|
10
|
-
|
11
|
-
from msgspec import Meta, Struct
|
12
|
-
|
13
|
-
from .. import definitions
|
14
|
-
|
15
|
-
|
16
|
-
class AggregatedAccountSummariesRequest(Struct, omit_defaults=True):
|
17
|
-
accounts: Optional[
|
18
|
-
Annotated[
|
19
|
-
List[definitions.AccountIdOrName],
|
20
|
-
Meta(
|
21
|
-
description="If trader and accounts are both None, return all accounts for the user"
|
22
|
-
),
|
23
|
-
]
|
24
|
-
] = None
|
25
|
-
"""
|
26
|
-
If trader and accounts are both None, return all accounts for the user
|
27
|
-
"""
|
28
|
-
trader: Optional[definitions.TraderIdOrEmail] = None
|
29
|
-
|
30
|
-
# below is a constructor that takes all field titles as arguments for convenience
|
31
|
-
@classmethod
|
32
|
-
def new(
|
33
|
-
cls,
|
34
|
-
accounts: Optional[List[definitions.AccountIdOrName]] = None,
|
35
|
-
trader: Optional[definitions.TraderIdOrEmail] = None,
|
36
|
-
):
|
37
|
-
return cls(
|
38
|
-
accounts,
|
39
|
-
trader,
|
40
|
-
)
|
41
|
-
|
42
|
-
def __str__(self) -> str:
|
43
|
-
return f"AggregatedAccountSummariesRequest(accounts={self.accounts},trader={self.trader})"
|
44
|
-
|
45
|
-
@staticmethod
|
46
|
-
def get_response_type():
|
47
|
-
return AggregatedAccountSummariesResponse
|
48
|
-
|
49
|
-
@staticmethod
|
50
|
-
def get_unannotated_response_type():
|
51
|
-
return AggregatedAccountSummariesResponse
|
52
|
-
|
53
|
-
@staticmethod
|
54
|
-
def get_route() -> str:
|
55
|
-
return "/json.architect.Folio/AggregatedAccountSummaries"
|
56
|
-
|
57
|
-
@staticmethod
|
58
|
-
def get_rpc_method():
|
59
|
-
return "unary"
|
@@ -1,27 +0,0 @@
|
|
1
|
-
# generated by datamodel-codegen:
|
2
|
-
# filename: Folio/AggregatedAccountSummariesResponse.json
|
3
|
-
|
4
|
-
from __future__ import annotations
|
5
|
-
|
6
|
-
from typing import List
|
7
|
-
|
8
|
-
from msgspec import Struct
|
9
|
-
|
10
|
-
from .. import definitions
|
11
|
-
|
12
|
-
|
13
|
-
class AggregatedAccountSummariesResponse(Struct, omit_defaults=True):
|
14
|
-
account_summaries: List[definitions.AggregatedAccountSummary]
|
15
|
-
|
16
|
-
# below is a constructor that takes all field titles as arguments for convenience
|
17
|
-
@classmethod
|
18
|
-
def new(
|
19
|
-
cls,
|
20
|
-
account_summaries: List[definitions.AggregatedAccountSummary],
|
21
|
-
):
|
22
|
-
return cls(
|
23
|
-
account_summaries,
|
24
|
-
)
|
25
|
-
|
26
|
-
def __str__(self) -> str:
|
27
|
-
return f"AggregatedAccountSummariesResponse(account_summaries={self.account_summaries})"
|
@@ -1,42 +0,0 @@
|
|
1
|
-
# generated by datamodel-codegen:
|
2
|
-
# filename: Oms/Cancel.json
|
3
|
-
|
4
|
-
from __future__ import annotations
|
5
|
-
|
6
|
-
from typing import Annotated, Optional
|
7
|
-
|
8
|
-
from msgspec import Meta, Struct
|
9
|
-
|
10
|
-
from .. import definitions
|
11
|
-
|
12
|
-
|
13
|
-
class Cancel(Struct, omit_defaults=True):
|
14
|
-
id: definitions.OrderId
|
15
|
-
o: definitions.CancelStatus
|
16
|
-
tn: Annotated[int, Meta(ge=0)]
|
17
|
-
ts: int
|
18
|
-
xid: str
|
19
|
-
r: Optional[str] = None
|
20
|
-
|
21
|
-
# below is a constructor that takes all field titles as arguments for convenience
|
22
|
-
@classmethod
|
23
|
-
def new(
|
24
|
-
cls,
|
25
|
-
id: definitions.OrderId,
|
26
|
-
o: definitions.CancelStatus,
|
27
|
-
tn: int,
|
28
|
-
ts: int,
|
29
|
-
xid: str,
|
30
|
-
r: Optional[str] = None,
|
31
|
-
):
|
32
|
-
return cls(
|
33
|
-
id,
|
34
|
-
o,
|
35
|
-
tn,
|
36
|
-
ts,
|
37
|
-
xid,
|
38
|
-
r,
|
39
|
-
)
|
40
|
-
|
41
|
-
def __str__(self) -> str:
|
42
|
-
return f"Cancel(id={self.id},o={self.o},tn={self.tn},ts={self.ts},xid={self.xid},r={self.r})"
|
@@ -1,405 +0,0 @@
|
|
1
|
-
from asyncio.log import logger
|
2
|
-
from types import UnionType
|
3
|
-
from typing import (
|
4
|
-
Any,
|
5
|
-
AsyncIterator,
|
6
|
-
Optional,
|
7
|
-
Protocol,
|
8
|
-
Type,
|
9
|
-
TypeVar,
|
10
|
-
cast,
|
11
|
-
)
|
12
|
-
from datetime import datetime, timedelta
|
13
|
-
from urllib.parse import urlparse
|
14
|
-
|
15
|
-
import msgspec
|
16
|
-
|
17
|
-
import dns.asyncresolver
|
18
|
-
import dns.resolver
|
19
|
-
from dns.rdtypes.IN.SRV import SRV
|
20
|
-
|
21
|
-
import grpc
|
22
|
-
|
23
|
-
import bisect
|
24
|
-
from decimal import Decimal
|
25
|
-
|
26
|
-
from architect_py.graphql_client.client import GraphQLClient
|
27
|
-
|
28
|
-
|
29
|
-
from architect_py.grpc_client.Marketdata.L1BookSnapshot import L1BookSnapshot
|
30
|
-
from architect_py.grpc_client.Marketdata.L1BookSnapshotRequest import (
|
31
|
-
L1BookSnapshotRequest,
|
32
|
-
)
|
33
|
-
from architect_py.grpc_client.Marketdata.L2BookSnapshot import L2BookSnapshot
|
34
|
-
from architect_py.grpc_client.Marketdata.L2BookSnapshotRequest import (
|
35
|
-
L2BookSnapshotRequest,
|
36
|
-
)
|
37
|
-
from architect_py.grpc_client.Marketdata.L2BookUpdate import (
|
38
|
-
L2BookUpdate,
|
39
|
-
)
|
40
|
-
from architect_py.grpc_client.Marketdata.SubscribeL1BookSnapshotsRequest import (
|
41
|
-
SubscribeL1BookSnapshotsRequest,
|
42
|
-
)
|
43
|
-
from architect_py.grpc_client.Marketdata.SubscribeL2BookUpdatesRequest import (
|
44
|
-
SubscribeL2BookUpdatesRequest,
|
45
|
-
)
|
46
|
-
from architect_py.grpc_client.Orderflow.Orderflow import Orderflow
|
47
|
-
from architect_py.grpc_client.Orderflow.OrderflowRequest import (
|
48
|
-
OrderflowRequest,
|
49
|
-
OrderflowRequest_route,
|
50
|
-
OrderflowRequestUnannotatedResponseType,
|
51
|
-
)
|
52
|
-
from architect_py.grpc_client.definitions import L2BookDiff
|
53
|
-
from architect_py.scalars import TradableProduct
|
54
|
-
|
55
|
-
|
56
|
-
"""
|
57
|
-
get_account_summaries_for_cpty
|
58
|
-
"""
|
59
|
-
|
60
|
-
|
61
|
-
def enc_hook(obj: Any) -> Any:
|
62
|
-
if isinstance(obj, TradableProduct):
|
63
|
-
return str(obj)
|
64
|
-
|
65
|
-
|
66
|
-
encoder = msgspec.json.Encoder(enc_hook=enc_hook)
|
67
|
-
ResponseTypeGeneric = TypeVar("ResponseTypeGeneric", covariant=True)
|
68
|
-
|
69
|
-
|
70
|
-
class RequestType(Protocol[ResponseTypeGeneric]):
|
71
|
-
@staticmethod
|
72
|
-
def get_unannotated_response_type() -> Type[ResponseTypeGeneric]: ...
|
73
|
-
|
74
|
-
@staticmethod
|
75
|
-
def get_response_type() -> Type[ResponseTypeGeneric]: ...
|
76
|
-
|
77
|
-
@staticmethod
|
78
|
-
def get_route() -> str: ...
|
79
|
-
|
80
|
-
@staticmethod
|
81
|
-
def get_rpc_method() -> Any: ...
|
82
|
-
|
83
|
-
|
84
|
-
class GRPCClient:
|
85
|
-
jwt: str
|
86
|
-
jwt_expiration: datetime
|
87
|
-
|
88
|
-
graphql_client: GraphQLClient
|
89
|
-
l1_books: dict[TradableProduct, L1BookSnapshot]
|
90
|
-
l2_books: dict[TradableProduct, L2BookSnapshot]
|
91
|
-
channel: grpc.aio.Channel
|
92
|
-
_decoders: dict[type | UnionType, msgspec.json.Decoder]
|
93
|
-
|
94
|
-
def __init__(
|
95
|
-
self,
|
96
|
-
graphql_client: GraphQLClient,
|
97
|
-
endpoint: str = "cme.marketdata.architect.co",
|
98
|
-
):
|
99
|
-
"""
|
100
|
-
Please ensure to call the initialize method before using the gRPC client.
|
101
|
-
|
102
|
-
grpc_client = GRPCClient(graphql_client, endpoint)
|
103
|
-
await grpc_client.initialize()
|
104
|
-
|
105
|
-
|
106
|
-
Brave users may create their own requests using the subscribe and request methods.
|
107
|
-
The types are correct so if a typechecker such as PyLance is throwing errors,
|
108
|
-
it's likely a bug in user code.
|
109
|
-
|
110
|
-
async for snap in self.subscribe(
|
111
|
-
RequestType.get_request_helper(), # add args/kwargs here
|
112
|
-
):
|
113
|
-
|
114
|
-
snap = await self.request(
|
115
|
-
RequestType.get_request_helper(), # add args/kwargs here
|
116
|
-
)
|
117
|
-
"""
|
118
|
-
self.graphql_client = graphql_client
|
119
|
-
|
120
|
-
self.jwt_expiration = datetime(1995, 11, 10)
|
121
|
-
|
122
|
-
self.l1_books: dict[TradableProduct, L1BookSnapshot] = {}
|
123
|
-
self.l2_books: dict[TradableProduct, L2BookSnapshot] = {}
|
124
|
-
self.endpoint = endpoint
|
125
|
-
|
126
|
-
self._decoders: dict[type | UnionType, msgspec.json.Decoder] = {}
|
127
|
-
|
128
|
-
async def initialize(self) -> Optional[str]:
|
129
|
-
"""
|
130
|
-
Initialize the gRPC channel with the given endpoint.
|
131
|
-
Must call this method before using the gRPC client.
|
132
|
-
"""
|
133
|
-
# "binance-futures-usd-m.marketdata.architect.co",
|
134
|
-
# "https://usdm.binance.marketdata.architect.co"
|
135
|
-
# "bybit.marketdata.architect.co",
|
136
|
-
# "binance.marketdata.architect.co",
|
137
|
-
# "cme.marketdata.architect.co",
|
138
|
-
self.channel = await self.get_grpc_channel(self.endpoint)
|
139
|
-
|
140
|
-
async def change_channel(self, endpoint: str) -> None:
|
141
|
-
self.channel = await self.get_grpc_channel(endpoint)
|
142
|
-
|
143
|
-
async def get_grpc_channel(
|
144
|
-
self,
|
145
|
-
endpoint: str,
|
146
|
-
) -> grpc.aio.Channel:
|
147
|
-
if "://" not in endpoint:
|
148
|
-
endpoint = f"http://{endpoint}"
|
149
|
-
url = urlparse(endpoint)
|
150
|
-
if url.hostname is None:
|
151
|
-
raise Exception(f"Invalid endpoint: {endpoint}")
|
152
|
-
|
153
|
-
is_https = url.scheme == "https"
|
154
|
-
srv_records: dns.resolver.Answer = await dns.asyncresolver.resolve(
|
155
|
-
url.hostname, "SRV"
|
156
|
-
)
|
157
|
-
if len(srv_records) == 0:
|
158
|
-
raise Exception(f"No SRV records found for {url.hostname}")
|
159
|
-
|
160
|
-
record = cast(SRV, srv_records[0])
|
161
|
-
|
162
|
-
connect_str = f"{record.target}:{record.port}"
|
163
|
-
if is_https:
|
164
|
-
credentials = grpc.ssl_channel_credentials()
|
165
|
-
return grpc.aio.secure_channel(connect_str, credentials)
|
166
|
-
else:
|
167
|
-
return grpc.aio.insecure_channel(connect_str)
|
168
|
-
|
169
|
-
async def refresh_grpc_credentials(self, force: bool = False) -> str:
|
170
|
-
"""
|
171
|
-
Refresh the JWT for the gRPC channel if it's nearing expiration (within 1 minute).
|
172
|
-
If force is True, refresh the JWT unconditionally.
|
173
|
-
"""
|
174
|
-
if force or datetime.now() > self.jwt_expiration - timedelta(minutes=1):
|
175
|
-
try:
|
176
|
-
self.jwt = (await self.graphql_client.create_jwt()).create_jwt
|
177
|
-
self.jwt_expiration = datetime.now() + timedelta(hours=23)
|
178
|
-
except Exception as e:
|
179
|
-
logger.error("Failed to refresh gRPC credentials: %s", e)
|
180
|
-
return self.jwt
|
181
|
-
|
182
|
-
def get_decoder(
|
183
|
-
self,
|
184
|
-
response_type: type[ResponseTypeGeneric] | UnionType,
|
185
|
-
) -> msgspec.json.Decoder:
|
186
|
-
try:
|
187
|
-
return self._decoders[response_type]
|
188
|
-
except KeyError:
|
189
|
-
# we use a try / except because we sacrifice first time query
|
190
|
-
# to optimize for repeated lookups
|
191
|
-
decoder = msgspec.json.Decoder(type=response_type)
|
192
|
-
self._decoders[response_type] = decoder
|
193
|
-
return decoder
|
194
|
-
|
195
|
-
async def request_l1_book_snapshot(self, symbol: TradableProduct) -> L1BookSnapshot:
|
196
|
-
request = L1BookSnapshotRequest(symbol=symbol)
|
197
|
-
return await self.request(request)
|
198
|
-
|
199
|
-
async def request_l2_book_snapshot(
|
200
|
-
self, venue: Optional[str], symbol: TradableProduct
|
201
|
-
) -> L2BookSnapshot:
|
202
|
-
request = L2BookSnapshotRequest(venue=venue, symbol=symbol)
|
203
|
-
return await self.request(request)
|
204
|
-
|
205
|
-
def initialize_l1_books(
|
206
|
-
self, symbols: list[TradableProduct]
|
207
|
-
) -> list[L1BookSnapshot]:
|
208
|
-
if symbols is not None:
|
209
|
-
if len(self.l1_books) + len(symbols) > 100:
|
210
|
-
raise ValueError(
|
211
|
-
"Not suggestible to watch more than 100 L1 symbols at once, as it may cause performance issues."
|
212
|
-
)
|
213
|
-
symbols = [symbol for symbol in symbols if symbol not in self.l1_books]
|
214
|
-
|
215
|
-
for symbol in symbols:
|
216
|
-
self.l1_books[symbol] = L1BookSnapshot(symbol, 0, 0)
|
217
|
-
else:
|
218
|
-
raise ValueError("symbols must be a list of TradableProduct")
|
219
|
-
# could technically be None, but we don't want to allow that
|
220
|
-
# as users should be explicit about what they want to watch
|
221
|
-
|
222
|
-
return [self.l1_books[symbol] for symbol in symbols]
|
223
|
-
|
224
|
-
async def watch_l1_books(self, symbols: list[TradableProduct]) -> None:
|
225
|
-
symbols_cast = cast(list[str], symbols)
|
226
|
-
|
227
|
-
request = SubscribeL1BookSnapshotsRequest(symbols=symbols_cast)
|
228
|
-
|
229
|
-
async for snap in self.subscribe(request):
|
230
|
-
book = self.l1_books[cast(TradableProduct, snap.symbol)]
|
231
|
-
update_struct(book, snap)
|
232
|
-
|
233
|
-
def initialize_l2_book(
|
234
|
-
self, symbol: TradableProduct, venue: Optional[str]
|
235
|
-
) -> L2BookSnapshot:
|
236
|
-
if symbol not in self.l2_books:
|
237
|
-
if len(self.l2_books) > 20:
|
238
|
-
raise ValueError(
|
239
|
-
"Not suggestible to watch more than 20 L2 symbols at once, as it may cause performance issues."
|
240
|
-
)
|
241
|
-
self.l2_books[symbol] = L2BookSnapshot([], [], 0, 0, 0, 0)
|
242
|
-
return self.l2_books[symbol]
|
243
|
-
|
244
|
-
async def subscribe_l1_books_stream(
|
245
|
-
self, symbols: list[str]
|
246
|
-
) -> AsyncIterator[L1BookSnapshot]:
|
247
|
-
request = SubscribeL1BookSnapshotsRequest(symbols=symbols)
|
248
|
-
return self.subscribe(
|
249
|
-
request,
|
250
|
-
)
|
251
|
-
|
252
|
-
async def subscribe_l2_books_stream(
|
253
|
-
self, symbol: TradableProduct, venue: Optional[str]
|
254
|
-
) -> AsyncIterator[L2BookUpdate]:
|
255
|
-
decoder: msgspec.json.Decoder[L2BookUpdate] = self.get_decoder(
|
256
|
-
SubscribeL2BookUpdatesRequest.get_unannotated_response_type()
|
257
|
-
)
|
258
|
-
stub = self.channel.unary_stream(
|
259
|
-
SubscribeL2BookUpdatesRequest.get_route(),
|
260
|
-
request_serializer=encoder.encode,
|
261
|
-
response_deserializer=decoder.decode,
|
262
|
-
)
|
263
|
-
req = SubscribeL2BookUpdatesRequest(symbol=symbol, venue=venue)
|
264
|
-
jwt = await self.refresh_grpc_credentials()
|
265
|
-
call = stub(req, metadata=(("authorization", f"Bearer {jwt}"),))
|
266
|
-
async for update in call:
|
267
|
-
yield update
|
268
|
-
|
269
|
-
async def watch_l2_book(
|
270
|
-
self, symbol: TradableProduct, venue: Optional[str]
|
271
|
-
) -> None:
|
272
|
-
async for up in self.subscribe_l2_books_stream(symbol, venue):
|
273
|
-
if isinstance(up, L2BookDiff): # elif up.t = "d": # diff
|
274
|
-
if symbol not in self.l2_books:
|
275
|
-
raise ValueError(
|
276
|
-
f"received update before snapshot for L2 book {symbol}"
|
277
|
-
)
|
278
|
-
book = self.l2_books[symbol]
|
279
|
-
if (
|
280
|
-
up.sequence_id != book.sequence_id
|
281
|
-
or up.sequence_number != book.sequence_number + 1
|
282
|
-
):
|
283
|
-
raise ValueError(
|
284
|
-
f"received update out of order for L2 book {symbol}"
|
285
|
-
)
|
286
|
-
L2_update_from_diff(book, up)
|
287
|
-
elif isinstance(up, L2BookSnapshot): # if up.t = "s":
|
288
|
-
book = self.l2_books[symbol]
|
289
|
-
update_struct(book, up)
|
290
|
-
|
291
|
-
async def subscribe_orderflow_stream(
|
292
|
-
self, request_iterator: AsyncIterator[OrderflowRequest]
|
293
|
-
) -> AsyncIterator[Orderflow]:
|
294
|
-
"""
|
295
|
-
subscribe_orderflow_stream is a duplex_stream meaning that it is a stream that can be read from and written to.
|
296
|
-
This is a stream that will be used to send orders to the Architect and receive order updates from the Architect.
|
297
|
-
"""
|
298
|
-
decoder = self.get_decoder(OrderflowRequestUnannotatedResponseType)
|
299
|
-
stub = self.channel.stream_stream(
|
300
|
-
OrderflowRequest_route,
|
301
|
-
request_serializer=encoder.encode,
|
302
|
-
response_deserializer=decoder.decode,
|
303
|
-
)
|
304
|
-
jwt = await self.refresh_grpc_credentials()
|
305
|
-
call = stub(request_iterator, metadata=(("authorization", f"Bearer {jwt}"),))
|
306
|
-
async for update in call:
|
307
|
-
yield update
|
308
|
-
|
309
|
-
async def subscribe(
|
310
|
-
self,
|
311
|
-
request: RequestType[ResponseTypeGeneric],
|
312
|
-
) -> AsyncIterator[ResponseTypeGeneric]:
|
313
|
-
"""
|
314
|
-
Generic function for subscribing to a stream of updates from the gRPC server.
|
315
|
-
|
316
|
-
request_type and ResponseTypeGeneric *cannot* be union types
|
317
|
-
"""
|
318
|
-
decoder: msgspec.json.Decoder[ResponseTypeGeneric] = self.get_decoder(
|
319
|
-
request.get_unannotated_response_type()
|
320
|
-
)
|
321
|
-
stub = self.channel.unary_stream(
|
322
|
-
request.get_route(),
|
323
|
-
request_serializer=encoder.encode,
|
324
|
-
response_deserializer=decoder.decode,
|
325
|
-
)
|
326
|
-
jwt = await self.refresh_grpc_credentials()
|
327
|
-
call = stub(request, metadata=(("authorization", f"Bearer {jwt}"),))
|
328
|
-
async for update in call:
|
329
|
-
yield update
|
330
|
-
|
331
|
-
async def request(
|
332
|
-
self,
|
333
|
-
request: RequestType[ResponseTypeGeneric],
|
334
|
-
) -> ResponseTypeGeneric:
|
335
|
-
"""
|
336
|
-
Generic function for making a unary request to the gRPC server.
|
337
|
-
|
338
|
-
request_type and ResponseTypeGeneric *cannot* be union types
|
339
|
-
"""
|
340
|
-
decoder: msgspec.json.Decoder[ResponseTypeGeneric] = self.get_decoder(
|
341
|
-
request.get_unannotated_response_type()
|
342
|
-
)
|
343
|
-
stub = self.channel.unary_unary(
|
344
|
-
request.get_route(),
|
345
|
-
request_serializer=encoder.encode,
|
346
|
-
response_deserializer=decoder.decode,
|
347
|
-
)
|
348
|
-
jwt = await self.refresh_grpc_credentials()
|
349
|
-
return await stub(request, metadata=(("authorization", f"Bearer {jwt}"),))
|
350
|
-
|
351
|
-
|
352
|
-
def update_order_list(
|
353
|
-
order_list: list[list[Decimal]],
|
354
|
-
price: Decimal,
|
355
|
-
size: Decimal,
|
356
|
-
ascending: bool,
|
357
|
-
) -> None:
|
358
|
-
"""
|
359
|
-
Updates a sorted order list (either ascending for asks or descending for bids)
|
360
|
-
using binary search to insert, update, or remove the given price level.
|
361
|
-
"""
|
362
|
-
if ascending:
|
363
|
-
idx = bisect.bisect_left(order_list, [price, Decimal(0)])
|
364
|
-
else:
|
365
|
-
lo, hi = 0, len(order_list)
|
366
|
-
while lo < hi:
|
367
|
-
mid = (lo + hi) // 2
|
368
|
-
if order_list[mid][0] > price:
|
369
|
-
lo = mid + 1
|
370
|
-
else:
|
371
|
-
hi = mid
|
372
|
-
idx = lo
|
373
|
-
|
374
|
-
if idx < len(order_list) and order_list[idx][0] == price:
|
375
|
-
if size.is_zero():
|
376
|
-
order_list.pop(idx)
|
377
|
-
else:
|
378
|
-
# Update the size.
|
379
|
-
order_list[idx][1] = size
|
380
|
-
else:
|
381
|
-
if not size.is_zero():
|
382
|
-
order_list.insert(idx, [price, size])
|
383
|
-
|
384
|
-
|
385
|
-
def L2_update_from_diff(book: L2BookSnapshot, diff: L2BookDiff) -> None:
|
386
|
-
"""
|
387
|
-
we use binary search because the L2 does not have many levels
|
388
|
-
and is simpler to maintain in the context of the codegen
|
389
|
-
"""
|
390
|
-
|
391
|
-
book.timestamp = diff.timestamp
|
392
|
-
book.timestamp_ns = diff.timestamp_ns
|
393
|
-
book.sequence_id = diff.sequence_id
|
394
|
-
book.sequence_number = diff.sequence_number
|
395
|
-
|
396
|
-
for price, size in diff.bids:
|
397
|
-
update_order_list(book.bids, price, size, ascending=False)
|
398
|
-
|
399
|
-
for price, size in diff.asks:
|
400
|
-
update_order_list(book.asks, price, size, ascending=True)
|
401
|
-
|
402
|
-
|
403
|
-
def update_struct(A: msgspec.Struct, B: msgspec.Struct) -> None:
|
404
|
-
for field in B.__struct_fields__:
|
405
|
-
setattr(A, field, getattr(B, field))
|