architect-py 3.2.2__py3-none-any.whl → 5.0.0b2__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 +15 -2
- architect_py/async_client.py +1060 -643
- architect_py/client.py +25 -26
- 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 +143 -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 +2 -2
- architect_py/{grpc_client → grpc/models}/Cpty/CptyResponse.py +3 -3
- architect_py/{grpc_client → grpc/models}/Cpty/CptyStatus.py +1 -1
- architect_py/{grpc_client → grpc/models}/Cpty/CptyStatusRequest.py +2 -2
- architect_py/{grpc_client → grpc/models}/Cpty/CptysRequest.py +2 -2
- architect_py/{grpc_client → grpc/models}/Cpty/CptysResponse.py +1 -1
- 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 +1 -1
- 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 +1 -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 +690 -841
- architect_py/grpc/resolve_endpoint.py +70 -0
- architect_py/{grpc_client/grpc_server.py → grpc/server.py} +9 -6
- 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 -87
- 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.0b2.dist-info/METADATA +123 -0
- architect_py-5.0.0b2.dist-info/RECORD +214 -0
- {architect_py-3.2.2.dist-info → architect_py-5.0.0b2.dist-info}/WHEEL +2 -1
- architect_py-5.0.0b2.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 -53
- 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/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 -413
- 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 -65
- architect_py/tests/test_snapshots.py +0 -52
- architect_py/tests/test_subscriptions.py +0 -126
- architect_py-3.2.2.dist-info/METADATA +0 -191
- architect_py-3.2.2.dist-info/RECORD +0 -148
- /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.2.dist-info → architect_py-5.0.0b2.dist-info/licenses}/LICENSE +0 -0
architect_py/async_client.py
CHANGED
@@ -1,216 +1,359 @@
|
|
1
|
-
"""
|
2
|
-
This file composes the GraphQLClient class to provide a higher-level interface
|
3
|
-
for order entry with the Architect API.
|
4
|
-
|
5
|
-
These are not required to send orders, but provide typed interfaces for the
|
6
|
-
various order types and algorithms that can be sent to the OMS.
|
7
|
-
|
8
|
-
|
9
|
-
The functions to send orders will return the order ID string
|
10
|
-
After sending the order, this string can be used to retrieve the order status
|
11
|
-
|
12
|
-
send_limit_order -> get_order
|
13
|
-
|
14
|
-
The individual graphql types are subject to change, so it is not recommended to use them directly.
|
15
|
-
"""
|
16
|
-
|
17
1
|
import asyncio
|
18
|
-
import functools
|
19
2
|
import logging
|
20
|
-
|
3
|
+
import re
|
4
|
+
from datetime import date, datetime, timedelta, timezone
|
21
5
|
from decimal import Decimal
|
22
|
-
from typing import
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
6
|
+
from typing import (
|
7
|
+
Any,
|
8
|
+
AsyncGenerator,
|
9
|
+
AsyncIterator,
|
10
|
+
List,
|
11
|
+
Literal,
|
12
|
+
Optional,
|
13
|
+
Sequence,
|
14
|
+
Union,
|
15
|
+
overload,
|
27
16
|
)
|
28
17
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
from architect_py.
|
34
|
-
|
18
|
+
import grpc
|
19
|
+
import pandas as pd
|
20
|
+
|
21
|
+
from architect_py.grpc.models.Oms.Cancel import Cancel
|
22
|
+
from architect_py.grpc.models.Oms.CancelAllOrdersRequest import CancelAllOrdersRequest
|
23
|
+
from architect_py.grpc.models.Oms.CancelOrderRequest import CancelOrderRequest
|
24
|
+
from architect_py.grpc.models.Oms.OpenOrdersRequest import OpenOrdersRequest
|
25
|
+
from architect_py.grpc.models.Oms.Order import Order
|
26
|
+
from architect_py.grpc.models.Oms.PlaceOrderRequest import (
|
27
|
+
PlaceOrderRequest,
|
35
28
|
)
|
36
|
-
|
37
|
-
from architect_py.
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
from architect_py.grpc_client.Marketdata.SubscribeCandlesRequest import (
|
42
|
-
SubscribeCandlesRequest,
|
29
|
+
from architect_py.grpc.models.Orderflow.Orderflow import Orderflow
|
30
|
+
from architect_py.grpc.models.Orderflow.OrderflowRequest import (
|
31
|
+
OrderflowRequest,
|
32
|
+
OrderflowRequest_route,
|
33
|
+
OrderflowRequestUnannotatedResponseType,
|
43
34
|
)
|
44
|
-
from architect_py.
|
45
|
-
|
35
|
+
from architect_py.grpc.models.Orderflow.SubscribeOrderflowRequest import (
|
36
|
+
SubscribeOrderflowRequest,
|
46
37
|
)
|
47
|
-
from architect_py.grpc_client.Marketdata.Trade import Trade
|
48
|
-
from architect_py.scalars import OrderDir, TradableProduct
|
49
|
-
from architect_py.utils.nearest_tick import TickRoundMethod
|
50
38
|
|
39
|
+
from .common_types import OrderDir, TradableProduct, Venue
|
51
40
|
from .graphql_client import GraphQLClient
|
52
|
-
from .graphql_client.
|
53
|
-
OrderType,
|
54
|
-
TimeInForce,
|
55
|
-
)
|
41
|
+
from .graphql_client.exceptions import GraphQLClientGraphQLMultiError
|
56
42
|
from .graphql_client.fragments import (
|
57
|
-
AccountSummaryFields,
|
58
|
-
AccountWithPermissionsFields,
|
59
|
-
CancelFields,
|
60
43
|
ExecutionInfoFields,
|
61
|
-
L2BookFields,
|
62
|
-
MarketStatusFields,
|
63
|
-
MarketTickerFields,
|
64
|
-
OrderFields,
|
65
44
|
ProductInfoFields,
|
66
45
|
)
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
# CreateSmartOrderRouterAlgo,
|
73
|
-
# CreateSpreadAlgo,
|
74
|
-
# CreateSpreadAlgoHedgeMarket,
|
75
|
-
# CreateTimeInForce,
|
76
|
-
# CreateTimeInForceInstruction,
|
77
|
-
# CreateTwapAlgo,
|
78
|
-
# )
|
79
|
-
from .grpc_client import GRPCClient
|
80
|
-
|
46
|
+
from .grpc import *
|
47
|
+
from .grpc.client import GrpcClient
|
48
|
+
from .utils.nearest_tick import TickRoundMethod
|
49
|
+
from .utils.orderbook import update_orderbook_side
|
50
|
+
from .utils.pandas import candles_to_dataframe
|
81
51
|
from .utils.price_bands import price_band_pairs
|
82
|
-
|
83
|
-
logger = logging.getLogger(__name__)
|
52
|
+
from .utils.symbol_parsing import nominative_expiration
|
84
53
|
|
85
54
|
|
86
55
|
class AsyncClient:
|
56
|
+
api_key: Optional[str] = None
|
57
|
+
api_secret: Optional[str] = None
|
58
|
+
paper_trading: bool
|
87
59
|
graphql_client: GraphQLClient
|
88
|
-
|
60
|
+
grpc_core: Optional[GrpcClient] = None
|
61
|
+
grpc_marketdata: dict[Venue, GrpcClient] = {}
|
62
|
+
grpc_hmart: Optional[GrpcClient] = None
|
63
|
+
jwt: str | None = None
|
64
|
+
jwt_expiration: datetime | None = None
|
65
|
+
|
66
|
+
l1_books: dict[Venue, dict[TradableProduct, tuple[L1BookSnapshot, asyncio.Task]]]
|
67
|
+
l2_books: dict[Venue, dict[TradableProduct, tuple[L2BookSnapshot, asyncio.Task]]]
|
89
68
|
|
90
69
|
# ------------------------------------------------------------
|
91
|
-
# Initialization
|
70
|
+
# Initialization and configuration
|
92
71
|
# ------------------------------------------------------------
|
93
72
|
|
94
73
|
@staticmethod
|
95
74
|
async def connect(
|
96
75
|
*,
|
97
|
-
api_key: str,
|
98
|
-
api_secret: str,
|
76
|
+
api_key: Optional[str] = None,
|
77
|
+
api_secret: Optional[str] = None,
|
99
78
|
paper_trading: bool,
|
100
|
-
|
101
|
-
|
102
|
-
_port: Optional[int] = None,
|
79
|
+
endpoint: str = "https://app.architect.co",
|
80
|
+
graphql_port: Optional[int] = None,
|
103
81
|
**kwargs: Any,
|
104
82
|
) -> "AsyncClient":
|
105
83
|
"""
|
106
|
-
|
107
|
-
|
108
|
-
Args:
|
109
|
-
api_key: API key for the user
|
110
|
-
api_secret: API secret for the user
|
111
|
-
host: Host for the GraphQL server, defaults to "app.architect.co"
|
112
|
-
paper_trading: Whether to use the paper trading environment, defaults to True
|
113
|
-
_port: Port for the GraphQL server, more for debugging purposes, do not set this unless you are sure of the port
|
84
|
+
Connect to an Architect installation.
|
114
85
|
|
115
|
-
the API key and secret
|
116
|
-
|
117
|
-
Returns:
|
118
|
-
Client object
|
119
|
-
|
120
|
-
Raises:
|
121
|
-
ValueError: If the API key or secret are not the correct length or contain invalid characters
|
122
|
-
|
123
|
-
For any request, if you get a "GraphQLClientHttpError: HTTP status code: 500" it likely means that your
|
124
|
-
API key and secret are incorrect. Please double check your credentials.
|
125
|
-
|
126
|
-
If you get a "GraphQLClientHttpError: HTTP status code: 400", please contact support so we can fix the function.
|
127
|
-
|
128
|
-
If you get an AttributeError on the grpc_client, it means that the GRPC client has not been initialized
|
129
|
-
likely due to the client not being instantiated with the connect method
|
86
|
+
Raises ValueError if the API key and secret are not the correct length or contain invalid characters.
|
130
87
|
"""
|
131
88
|
if paper_trading:
|
132
|
-
|
133
|
-
|
89
|
+
COLOR = "\033[30;43m"
|
90
|
+
RESET = "\033[0m"
|
91
|
+
print(f"🧻 {COLOR} YOU ARE IN PAPER TRADING MODE {RESET}")
|
92
|
+
|
93
|
+
if "grpc_endpoint" in kwargs:
|
94
|
+
logging.warning(
|
95
|
+
"as of v5.0.0: grpc_endpoint parameter is deprecated; ignored"
|
96
|
+
)
|
97
|
+
if "host" in kwargs:
|
98
|
+
logging.warning(
|
99
|
+
"as of v5.0.0: host parameter is deprecated, use endpoint instead; setting endpoint to %s",
|
100
|
+
kwargs["endpoint"],
|
134
101
|
)
|
102
|
+
endpoint = kwargs["endpoint"]
|
135
103
|
|
136
|
-
|
104
|
+
grpc_host, grpc_port, use_ssl = await resolve_endpoint(endpoint)
|
105
|
+
logging.info(
|
106
|
+
f"Resolved endpoint {endpoint}: {grpc_host}:{grpc_port} use_ssl={use_ssl}"
|
107
|
+
)
|
108
|
+
|
109
|
+
client = AsyncClient(
|
137
110
|
api_key=api_key,
|
138
111
|
api_secret=api_secret,
|
139
|
-
host=host,
|
140
112
|
paper_trading=paper_trading,
|
141
|
-
|
113
|
+
grpc_host=grpc_host,
|
114
|
+
grpc_port=grpc_port,
|
115
|
+
graphql_port=graphql_port,
|
116
|
+
use_ssl=use_ssl,
|
142
117
|
_i_know_what_i_am_doing=True,
|
143
|
-
**kwargs,
|
144
118
|
)
|
145
119
|
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
120
|
+
logging.info("Exchanging credentials...")
|
121
|
+
await client.refresh_jwt()
|
122
|
+
|
123
|
+
logging.info("Discovering marketdata endpoints...")
|
124
|
+
await client.discover_marketdata()
|
125
|
+
|
126
|
+
return client
|
151
127
|
|
152
128
|
def __init__(
|
153
129
|
self,
|
154
130
|
*,
|
155
|
-
api_key: str,
|
156
|
-
api_secret: str,
|
157
|
-
|
158
|
-
|
159
|
-
|
131
|
+
api_key: Optional[str] = None,
|
132
|
+
api_secret: Optional[str] = None,
|
133
|
+
paper_trading: bool,
|
134
|
+
grpc_host: str = "app.architect.co",
|
135
|
+
grpc_port: int,
|
136
|
+
graphql_port: Optional[int] = None,
|
137
|
+
use_ssl: bool = True,
|
160
138
|
_i_know_what_i_am_doing: bool = False,
|
161
|
-
**kwargs: Any,
|
162
139
|
):
|
163
140
|
"""
|
164
|
-
|
165
|
-
|
166
|
-
Use the connect method instead.
|
167
|
-
See self.connect for arg explanations
|
141
|
+
Use AsyncClient.connect instead.
|
168
142
|
"""
|
169
|
-
|
170
143
|
if not _i_know_what_i_am_doing:
|
171
|
-
raise ValueError(
|
172
|
-
"Please use the connect method to create an AsyncClient object."
|
173
|
-
)
|
144
|
+
raise ValueError("Use AsyncClient.connect to create an AsyncClient object.")
|
174
145
|
|
175
|
-
if not
|
176
|
-
raise ValueError(
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
raise ValueError(
|
181
|
-
"API key and secret cannot contain commas, please double check your credentials."
|
182
|
-
)
|
183
|
-
elif " " in api_key or " " in api_secret:
|
146
|
+
if api_key is not None and not re.match(r"^[a-zA-Z0-9]{24}$", api_key):
|
147
|
+
raise ValueError("API key must be exactly 24 alphanumeric characters")
|
148
|
+
if api_secret is not None and not re.match(
|
149
|
+
r"^[a-zA-Z0-9+\/=]{44}$", api_secret
|
150
|
+
):
|
184
151
|
raise ValueError(
|
185
|
-
"API
|
152
|
+
"API secret must be a Base64-encoded string, 44 characters long"
|
186
153
|
)
|
187
|
-
|
154
|
+
|
155
|
+
if paper_trading and (graphql_port is not None or grpc_port is not None):
|
188
156
|
raise ValueError(
|
189
|
-
"
|
157
|
+
"If paper_trading is True, graphql_port and grpc_port must be None"
|
190
158
|
)
|
191
159
|
|
192
|
-
if
|
160
|
+
if graphql_port is None:
|
193
161
|
if paper_trading:
|
194
|
-
|
162
|
+
graphql_port = 5678
|
195
163
|
else:
|
196
|
-
|
164
|
+
graphql_port = 4567
|
197
165
|
|
166
|
+
self.api_key = api_key
|
167
|
+
self.api_secret = api_secret
|
168
|
+
self.paper_trading = paper_trading
|
198
169
|
self.graphql_client = GraphQLClient(
|
199
|
-
|
170
|
+
host=grpc_host,
|
171
|
+
port=graphql_port,
|
172
|
+
use_tls=use_ssl,
|
173
|
+
api_key=api_key,
|
174
|
+
api_secret=api_secret,
|
200
175
|
)
|
176
|
+
self.grpc_core = GrpcClient(host=grpc_host, port=grpc_port, use_ssl=use_ssl)
|
177
|
+
|
178
|
+
async def refresh_jwt(self, force: bool = False):
|
179
|
+
"""
|
180
|
+
Refresh the JWT for the gRPC channel if it's nearing expiration (within 1 minute).
|
181
|
+
If force=True, refresh the JWT unconditionally.
|
182
|
+
|
183
|
+
Query methods on AsyncClient that require auth will call this method internally.
|
184
|
+
"""
|
185
|
+
if not self.api_key or not self.api_secret:
|
186
|
+
raise ValueError("API key and secret not set")
|
187
|
+
if self.grpc_core is None:
|
188
|
+
raise ValueError("gRPC client to Architect not initialized")
|
189
|
+
|
190
|
+
if (
|
191
|
+
force
|
192
|
+
or self.jwt_expiration is None
|
193
|
+
or datetime.now() > self.jwt_expiration - timedelta(minutes=1)
|
194
|
+
):
|
195
|
+
try:
|
196
|
+
req = CreateJwtRequest(api_key=self.api_key, api_secret=self.api_secret)
|
197
|
+
res: CreateJwtResponse = await self.grpc_core.unary_unary(req)
|
198
|
+
self.jwt = res.jwt
|
199
|
+
# CR alee: actually read the JWT to get the expiration time;
|
200
|
+
# for now, we just "know" that the JWTs are granted for an hour
|
201
|
+
self.jwt_expiration = datetime.now() + timedelta(hours=1)
|
202
|
+
except Exception as e:
|
203
|
+
logging.error("Failed to refresh gRPC credentials: %s", e)
|
204
|
+
|
205
|
+
def set_jwt(self, jwt: str | None, jwt_expiration: datetime | None = None):
|
206
|
+
"""
|
207
|
+
Manually set the JWT for gRPC authentication.
|
208
|
+
|
209
|
+
Args:
|
210
|
+
jwt: the JWT to set;
|
211
|
+
None to clear the JWT
|
212
|
+
jwt_expiration: when to expire the JWT
|
213
|
+
"""
|
214
|
+
self.jwt = jwt
|
215
|
+
self.jwt_expiration = jwt_expiration
|
216
|
+
|
217
|
+
async def discover_marketdata(self):
|
218
|
+
"""
|
219
|
+
Load marketdata endpoints from the server config.
|
220
|
+
|
221
|
+
The Architect core is responsible for telling you where to find marketdata as per
|
222
|
+
its configuration. You can also manually set marketdata endpoints by calling
|
223
|
+
set_marketdata directly.
|
201
224
|
|
202
|
-
|
225
|
+
This method is called on AsyncClient.connect.
|
203
226
|
"""
|
204
|
-
|
227
|
+
try:
|
228
|
+
grpc_client = await self.core()
|
229
|
+
req = ConfigRequest()
|
230
|
+
res: ConfigResponse = await grpc_client.unary_unary(req)
|
231
|
+
for venue, endpoint in res.marketdata.items():
|
232
|
+
try:
|
233
|
+
grpc_host, grpc_port, use_ssl = await resolve_endpoint(endpoint)
|
234
|
+
logging.info(
|
235
|
+
"Setting marketdata endpoint for %s: %s:%d use_ssl=%s",
|
236
|
+
venue,
|
237
|
+
grpc_host,
|
238
|
+
grpc_port,
|
239
|
+
use_ssl,
|
240
|
+
)
|
241
|
+
self.grpc_marketdata[venue] = GrpcClient(
|
242
|
+
host=grpc_host, port=grpc_port, use_ssl=use_ssl
|
243
|
+
)
|
244
|
+
except Exception as e:
|
245
|
+
logging.error("Failed to set marketdata endpoint: %s", e)
|
246
|
+
except Exception as e:
|
247
|
+
logging.error("Failed to get marketdata config: %s", e)
|
205
248
|
|
206
|
-
|
249
|
+
async def set_marketdata(self, venue: Venue, endpoint: str):
|
250
|
+
"""
|
251
|
+
Manually set the marketdata endpoint for a venue.
|
207
252
|
"""
|
208
|
-
|
253
|
+
try:
|
254
|
+
grpc_host, grpc_port, use_ssl = await resolve_endpoint(endpoint)
|
255
|
+
self.grpc_marketdata[venue] = GrpcClient(
|
256
|
+
host=grpc_host, port=grpc_port, use_ssl=use_ssl
|
257
|
+
)
|
258
|
+
except Exception as e:
|
259
|
+
logging.error("Failed to set marketdata endpoint: %s", e)
|
260
|
+
|
261
|
+
async def marketdata(self, venue: Venue) -> GrpcClient:
|
262
|
+
"""
|
263
|
+
Get the marketdata client for a venue.
|
264
|
+
"""
|
265
|
+
if venue not in self.grpc_marketdata:
|
266
|
+
raise ValueError(f"Marketdata not configured for venue: {venue}")
|
267
|
+
|
268
|
+
await self.refresh_jwt()
|
269
|
+
self.grpc_marketdata[venue].set_jwt(self.jwt)
|
270
|
+
return self.grpc_marketdata[venue]
|
271
|
+
|
272
|
+
async def set_hmart(self, endpoint: str):
|
273
|
+
"""
|
274
|
+
Manually set the hmart (historical marketdata service) endpoint.
|
275
|
+
"""
|
276
|
+
try:
|
277
|
+
grpc_host, grpc_port, use_ssl = await resolve_endpoint(endpoint)
|
278
|
+
logging.info(
|
279
|
+
"Resolved hmart endpoint %s: %s:%d use_ssl=%s",
|
280
|
+
endpoint,
|
281
|
+
grpc_host,
|
282
|
+
grpc_port,
|
283
|
+
use_ssl,
|
284
|
+
)
|
285
|
+
self.grpc_hmart = GrpcClient(
|
286
|
+
host=grpc_host, port=grpc_port, use_ssl=use_ssl
|
287
|
+
)
|
288
|
+
except Exception as e:
|
289
|
+
logging.error("Failed to set hmart endpoint: %s", e)
|
290
|
+
|
291
|
+
async def hmart(self) -> GrpcClient:
|
292
|
+
"""
|
293
|
+
Get the hmart (historical marketdata service) client.
|
294
|
+
"""
|
295
|
+
if self.grpc_hmart is None:
|
296
|
+
# default to historical.marketdata.architect.co
|
297
|
+
await self.set_hmart("https://historical.marketdata.architect.co")
|
298
|
+
|
299
|
+
if self.grpc_hmart is None:
|
300
|
+
raise ValueError("hmart client not initialized")
|
301
|
+
|
302
|
+
await self.refresh_jwt()
|
303
|
+
self.grpc_hmart.set_jwt(self.jwt)
|
304
|
+
return self.grpc_hmart
|
305
|
+
|
306
|
+
async def core(self) -> GrpcClient:
|
307
|
+
"""
|
308
|
+
Get the core client.
|
309
|
+
"""
|
310
|
+
if self.grpc_core is None:
|
311
|
+
raise ValueError("gRPC client to Architect not initialized")
|
312
|
+
|
313
|
+
await self.refresh_jwt()
|
314
|
+
self.grpc_core.set_jwt(self.jwt)
|
315
|
+
return self.grpc_core
|
316
|
+
|
317
|
+
async def who_am_i(self) -> tuple[str, str]:
|
318
|
+
"""
|
319
|
+
Gets the user_id and user_email for the user that the API key belongs to.
|
320
|
+
|
321
|
+
Returns:
|
322
|
+
(user_id, user_email)
|
323
|
+
"""
|
324
|
+
res = await self.graphql_client.user_id_query()
|
325
|
+
return res.user_id, res.user_email
|
326
|
+
|
327
|
+
def enable_orderflow(self):
|
328
|
+
"""
|
329
|
+
@deprecated(reason="No longer needed; orderflow is enabled by default")
|
330
|
+
"""
|
331
|
+
logging.warning(
|
332
|
+
"as of v5.0.0: enable_orderflow is deprecated; orderflow is enabled by default"
|
333
|
+
)
|
209
334
|
|
210
335
|
# ------------------------------------------------------------
|
211
336
|
# Symbology
|
212
337
|
# ------------------------------------------------------------
|
213
338
|
|
339
|
+
async def list_symbols(self, *, marketdata: Optional[Venue] = None) -> list[str]:
|
340
|
+
"""
|
341
|
+
List all symbols.
|
342
|
+
|
343
|
+
Args:
|
344
|
+
marketdata: query marketdata endpoint for the specified venue directly;
|
345
|
+
If provided, query the venue's marketdata endpoint directly,
|
346
|
+
instead of the Architect core. This is sometimes useful for
|
347
|
+
cross-referencing symbols or checking availability.
|
348
|
+
"""
|
349
|
+
if marketdata is not None:
|
350
|
+
grpc_client = await self.marketdata(marketdata)
|
351
|
+
else:
|
352
|
+
grpc_client = await self.core()
|
353
|
+
req = SymbolsRequest()
|
354
|
+
res: SymbolsResponse = await grpc_client.unary_unary(req)
|
355
|
+
return res.symbols
|
356
|
+
|
214
357
|
async def search_symbols(
|
215
358
|
self,
|
216
359
|
search_string: Optional[str] = None,
|
@@ -219,75 +362,74 @@ class AsyncClient:
|
|
219
362
|
limit: int = 20,
|
220
363
|
) -> List[TradableProduct]:
|
221
364
|
"""
|
222
|
-
Search for
|
365
|
+
Search for tradable products on Architect.
|
223
366
|
|
224
367
|
Args:
|
225
|
-
search_string: a string to search for in the symbol
|
368
|
+
search_string: a string to search for in the symbol
|
369
|
+
Can be "*" for wild card search.
|
226
370
|
Examples: "ES", "NQ", "GC"
|
227
371
|
execution_venue: the execution venue to search in
|
228
372
|
Examples: "CME"
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
limit=limit,
|
238
|
-
)
|
239
|
-
).search_symbols
|
240
|
-
|
241
|
-
return markets
|
373
|
+
"""
|
374
|
+
res = await self.graphql_client.search_symbols_query(
|
375
|
+
search_string=search_string,
|
376
|
+
execution_venue=execution_venue,
|
377
|
+
offset=offset,
|
378
|
+
limit=limit,
|
379
|
+
)
|
380
|
+
return res.search_symbols
|
242
381
|
|
243
382
|
async def get_product_info(self, symbol: str) -> Optional[ProductInfoFields]:
|
244
383
|
"""
|
245
|
-
Get
|
384
|
+
Get information about a product, e.g. product_type, underlying, multiplier.
|
246
385
|
|
247
386
|
Args:
|
248
387
|
symbol: the symbol to get information for
|
388
|
+
the symbol should *not* have a quote,
|
389
|
+
ie "ES 20250620 CME Future" instead of "ES 20250620 CME Future/USD"
|
390
|
+
|
391
|
+
If you used TradableProduct, you can use the base() method to get the symbol
|
392
|
+
|
249
393
|
Returns:
|
250
|
-
|
394
|
+
None if the symbol does not exist
|
251
395
|
"""
|
252
|
-
|
253
|
-
return
|
396
|
+
res = await self.graphql_client.get_product_info_query(symbol)
|
397
|
+
return res.product_info
|
254
398
|
|
255
399
|
async def get_product_infos(
|
256
400
|
self, symbols: Optional[list[str]]
|
257
401
|
) -> Sequence[ProductInfoFields]:
|
258
402
|
"""
|
259
|
-
Get
|
403
|
+
Get information about products, e.g. product_type, underlying, multiplier.
|
260
404
|
|
261
405
|
Args:
|
262
|
-
symbols: the symbols to get information for
|
263
|
-
Returns:
|
264
|
-
a list of ProductInfoFields
|
406
|
+
symbols: the symbols to get information for, or None for all symbols
|
265
407
|
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
408
|
+
Returns:
|
409
|
+
Product infos for each symbol. Not guaranteed to contain all symbols
|
410
|
+
that were asked for, or in the same order; any duplicates or invalid
|
411
|
+
symbols will be ignored.
|
270
412
|
"""
|
271
|
-
|
272
|
-
return
|
413
|
+
res = await self.graphql_client.get_product_infos_query(symbols)
|
414
|
+
return res.product_infos
|
273
415
|
|
274
416
|
async def get_execution_info(
|
275
|
-
self, symbol: TradableProduct, execution_venue: str
|
417
|
+
self, symbol: TradableProduct | str, execution_venue: str
|
276
418
|
) -> Optional[ExecutionInfoFields]:
|
277
419
|
"""
|
278
|
-
Get
|
420
|
+
Get information about tradable product execution, e.g. tick_size,
|
421
|
+
step_size, margins.
|
279
422
|
|
280
423
|
Args:
|
281
424
|
symbol: the symbol to get execution information for
|
282
425
|
execution_venue: the execution venue e.g. "CME"
|
283
426
|
|
284
427
|
Returns:
|
285
|
-
|
286
|
-
|
428
|
+
None if the symbol doesn't exist
|
287
429
|
"""
|
288
430
|
try:
|
289
431
|
execution_info = await self.graphql_client.get_execution_info_query(
|
290
|
-
symbol, execution_venue
|
432
|
+
TradableProduct(symbol), execution_venue
|
291
433
|
)
|
292
434
|
return execution_info.execution_info
|
293
435
|
except GraphQLClientGraphQLMultiError:
|
@@ -301,59 +443,67 @@ class AsyncClient:
|
|
301
443
|
execution_venue: Optional[str] = None,
|
302
444
|
) -> Sequence[ExecutionInfoFields]:
|
303
445
|
"""
|
304
|
-
Get
|
446
|
+
Get information about tradable product execution, e.g. tick_size,
|
447
|
+
step_size, margins, for many symbols.
|
305
448
|
|
306
449
|
Args:
|
307
|
-
symbols: the symbols to get execution information for
|
450
|
+
symbols: the symbols to get execution information for, or None for all symbols
|
308
451
|
execution_venue: the execution venue e.g. "CME"
|
309
452
|
|
310
453
|
Returns:
|
311
|
-
|
312
|
-
|
313
|
-
|
454
|
+
Execution infos for each symbol. Not guaranteed to contain all symbols
|
455
|
+
that were asked for, or in the same order; any duplicates or invalid
|
456
|
+
symbols will be ignored.
|
314
457
|
"""
|
315
|
-
|
458
|
+
res = await self.graphql_client.get_execution_infos_query(
|
316
459
|
symbols, execution_venue
|
317
460
|
)
|
318
|
-
return
|
461
|
+
return res.execution_infos
|
319
462
|
|
320
463
|
async def get_cme_first_notice_date(self, symbol: str) -> Optional[date]:
|
321
464
|
"""
|
465
|
+
@deprecated(reason="Use get_product_info instead; first_notice_date is now a field")
|
466
|
+
|
322
467
|
Get the first notice date for a CME future.
|
323
468
|
|
324
469
|
Args:
|
325
470
|
symbol: the symbol to get the first notice date for a CME future
|
326
471
|
|
327
472
|
Returns:
|
328
|
-
|
473
|
+
The first notice date as a date object if it exists
|
329
474
|
"""
|
330
|
-
|
331
|
-
if
|
475
|
+
res = await self.graphql_client.get_first_notice_date_query(symbol)
|
476
|
+
if res is None or res.product_info is None:
|
332
477
|
return None
|
333
|
-
return
|
478
|
+
return res.product_info.first_notice_date
|
334
479
|
|
335
480
|
async def get_future_series(self, series_symbol: str) -> list[str]:
|
336
481
|
"""
|
337
|
-
|
482
|
+
@deprecated(reason="Use get_futures_series instead")
|
483
|
+
"""
|
484
|
+
futures = await self.get_futures_series(series_symbol)
|
485
|
+
return futures
|
486
|
+
|
487
|
+
async def get_futures_series(self, series_symbol: str) -> list[str]:
|
488
|
+
"""
|
489
|
+
List all futures in a given series.
|
338
490
|
|
339
491
|
Args:
|
340
|
-
series_symbol: the
|
341
|
-
e.g. ES CME Futures" would yield a list of all the ES futures
|
492
|
+
series_symbol: the futures series
|
493
|
+
e.g. "ES CME Futures" would yield a list of all the ES futures
|
342
494
|
Returns:
|
343
|
-
|
495
|
+
List of futures products
|
344
496
|
"""
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
futures_series = await self.graphql_client.get_future_series_query(
|
350
|
-
series_symbol
|
351
|
-
)
|
352
|
-
return futures_series.futures_series
|
497
|
+
if not series_symbol.endswith("Futures"):
|
498
|
+
raise ValueError("series_symbol must end with 'Futures'")
|
499
|
+
res = await self.graphql_client.get_future_series_query(series_symbol)
|
500
|
+
return res.futures_series
|
353
501
|
|
354
502
|
@staticmethod
|
355
|
-
def get_expiration_from_CME_name(name: str) -> date:
|
503
|
+
def get_expiration_from_CME_name(name: str) -> Optional[date]:
|
356
504
|
"""
|
505
|
+
@deprecated(reason="Use utils.symbol_parsing.nominative_expiration instead")
|
506
|
+
|
357
507
|
Get the expiration date from a CME future name.
|
358
508
|
|
359
509
|
Args:
|
@@ -362,12 +512,13 @@ class AsyncClient:
|
|
362
512
|
Returns:
|
363
513
|
the expiration date as a date object
|
364
514
|
"""
|
365
|
-
|
366
|
-
return datetime.strptime(d, "%Y%m%d").date()
|
515
|
+
return nominative_expiration(name)
|
367
516
|
|
368
517
|
async def get_cme_futures_series(self, series: str) -> list[tuple[date, str]]:
|
369
518
|
"""
|
370
|
-
|
519
|
+
@deprecated(reason="Use get_futures_series instead")
|
520
|
+
|
521
|
+
List all futures in a given CME series.
|
371
522
|
|
372
523
|
Args:
|
373
524
|
series: the series to get the futures for
|
@@ -377,39 +528,37 @@ class AsyncClient:
|
|
377
528
|
the symbol for each future in the series
|
378
529
|
|
379
530
|
e.g.
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
531
|
+
```
|
532
|
+
[
|
533
|
+
(datetime.date(2025, 3, 21), 'ES 20250321 CME Future'),
|
534
|
+
(datetime.date(2025, 6, 20), 'ES 20250620 CME Future'),
|
535
|
+
(datetime.date(2025, 9, 19), 'ES 20250919 CME Future'),
|
536
|
+
# ...
|
384
537
|
]
|
538
|
+
```
|
385
539
|
"""
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
]
|
393
|
-
|
394
|
-
filtered_markets.sort(key=lambda x: x[0])
|
395
|
-
|
396
|
-
return filtered_markets
|
540
|
+
futures = await self.get_futures_series(series)
|
541
|
+
exp_and_futures = []
|
542
|
+
for future in futures:
|
543
|
+
exp = nominative_expiration(future)
|
544
|
+
if exp is not None:
|
545
|
+
exp_and_futures.append((exp, future))
|
546
|
+
exp_and_futures.sort(key=lambda x: x[0])
|
547
|
+
return exp_and_futures
|
397
548
|
|
398
549
|
async def get_cme_future_from_root_month_year(
|
399
550
|
self, root: str, month: int, year: int
|
400
551
|
) -> str:
|
401
552
|
"""
|
402
553
|
Get the symbol for a CME future from the root, month, and year.
|
554
|
+
This is a simple wrapper around search_symbols.
|
403
555
|
|
404
556
|
Args:
|
405
557
|
root: the root symbol for the future e.g. "ES"
|
406
558
|
month: the month of the future
|
407
559
|
year: the year of the future
|
408
560
|
Returns:
|
409
|
-
|
410
|
-
|
411
|
-
Errors if the result is not unique
|
412
|
-
This is a simple wrapper around search_symbols
|
561
|
+
The future symbol if it exists and is unique.
|
413
562
|
"""
|
414
563
|
[market] = [
|
415
564
|
market
|
@@ -419,101 +568,507 @@ class AsyncClient:
|
|
419
568
|
)
|
420
569
|
if market.startswith(f"{root} {year}{month:02d}")
|
421
570
|
]
|
422
|
-
|
423
571
|
return market
|
424
572
|
|
425
573
|
# ------------------------------------------------------------
|
426
|
-
#
|
574
|
+
# Marketdata
|
427
575
|
# ------------------------------------------------------------
|
428
576
|
|
429
|
-
async def
|
577
|
+
async def get_market_status(
|
578
|
+
self, symbol: TradableProduct | str, venue: Venue
|
579
|
+
) -> MarketStatus:
|
430
580
|
"""
|
431
|
-
|
581
|
+
Returns market status for symbol (e.g. if it's currently quoting or trading).
|
582
|
+
|
583
|
+
Args:
|
584
|
+
symbol: the symbol to get the market status for, e.g. "ES 20250321 CME Future/USD"
|
585
|
+
venue: the venue that the symbol is traded at, e.g. CME
|
586
|
+
"""
|
587
|
+
grpc_client = await self.marketdata(venue)
|
588
|
+
req = MarketStatusRequest(symbol=str(symbol), venue=venue)
|
589
|
+
res: MarketStatus = await grpc_client.unary_unary(req)
|
590
|
+
return res
|
591
|
+
|
592
|
+
async def get_market_snapshot(
|
593
|
+
self, symbol: TradableProduct | str, venue: Venue
|
594
|
+
) -> L1BookSnapshot:
|
595
|
+
"""
|
596
|
+
@deprecated(reason="Use get_l1_snapshot instead")
|
597
|
+
|
598
|
+
This is an alias for l1_book_snapshot.
|
432
599
|
|
600
|
+
Args:
|
601
|
+
symbol: the symbol to get the market snapshot for, e.g. "ES 20250321 CME Future/USD"
|
602
|
+
venue: the venue that the symbol is traded at, e.g. CME
|
433
603
|
Returns:
|
434
|
-
|
604
|
+
L1BookSnapshot for the symbol
|
605
|
+
"""
|
606
|
+
return await self.get_l1_book_snapshot(symbol=symbol, venue=venue)
|
607
|
+
|
608
|
+
async def get_market_snapshots(
|
609
|
+
self, symbols: list[TradableProduct | str], venue: Venue
|
610
|
+
) -> Sequence[L1BookSnapshot]:
|
611
|
+
"""
|
612
|
+
@deprecated(reason="Use get_l1_snapshots instead")
|
613
|
+
|
614
|
+
This is an alias for l1_book_snapshots.
|
615
|
+
|
616
|
+
Args:
|
617
|
+
symbols: the symbols to get the market snapshots for
|
618
|
+
venue: the venue that the symbols are traded at
|
619
|
+
"""
|
620
|
+
return await self.get_l1_book_snapshots(
|
621
|
+
venue=venue,
|
622
|
+
symbols=symbols, # pyright: ignore[reportArgumentType]
|
623
|
+
)
|
624
|
+
|
625
|
+
@overload
|
626
|
+
async def get_historical_candles(
|
627
|
+
self,
|
628
|
+
symbol: TradableProduct | str,
|
629
|
+
venue: Venue,
|
630
|
+
candle_width: CandleWidth,
|
631
|
+
start: datetime,
|
632
|
+
end: datetime,
|
633
|
+
*,
|
634
|
+
as_dataframe: Literal[True],
|
635
|
+
) -> pd.DataFrame: ...
|
636
|
+
|
637
|
+
@overload
|
638
|
+
async def get_historical_candles(
|
639
|
+
self,
|
640
|
+
symbol: TradableProduct | str,
|
641
|
+
venue: Venue,
|
642
|
+
candle_width: CandleWidth,
|
643
|
+
start: datetime,
|
644
|
+
end: datetime,
|
645
|
+
) -> List[Candle]: ...
|
646
|
+
|
647
|
+
async def get_historical_candles(
|
648
|
+
self,
|
649
|
+
symbol: TradableProduct | str,
|
650
|
+
venue: Venue,
|
651
|
+
candle_width: CandleWidth,
|
652
|
+
start: datetime,
|
653
|
+
end: datetime,
|
654
|
+
*,
|
655
|
+
as_dataframe: bool = False,
|
656
|
+
) -> Union[List[Candle], pd.DataFrame]:
|
435
657
|
"""
|
436
|
-
|
658
|
+
Gets historical candles for a symbol.
|
437
659
|
|
438
|
-
|
660
|
+
Args:
|
661
|
+
symbol: the symbol to get the candles for
|
662
|
+
venue: the venue of the symbol
|
663
|
+
candle_width: the width of the candles
|
664
|
+
start: the start date to get candles for;
|
665
|
+
end: the end date to get candles for;
|
666
|
+
as_dataframe: if True, return a pandas DataFrame
|
439
667
|
|
440
|
-
|
668
|
+
"""
|
669
|
+
grpc_client = await self.hmart()
|
670
|
+
if start.tzinfo is not timezone.utc:
|
671
|
+
raise ValueError(
|
672
|
+
"start must be a utc datetime:\n"
|
673
|
+
"for example datetime.now(timezone.utc) or \n"
|
674
|
+
"dt = datetime(2025, 4, 15, 12, 0, 0, tzinfo=timezone.utc)"
|
675
|
+
)
|
676
|
+
if end.tzinfo is not timezone.utc:
|
677
|
+
raise ValueError(
|
678
|
+
"end must be a utc datetime:\n"
|
679
|
+
"for example datetime.now(timezone.utc) or \n"
|
680
|
+
"dt = datetime(2025, 4, 15, 12, 0, 0, tzinfo=timezone.utc)"
|
681
|
+
)
|
682
|
+
req = HistoricalCandlesRequest(
|
683
|
+
venue=venue,
|
684
|
+
symbol=str(symbol),
|
685
|
+
candle_width=candle_width,
|
686
|
+
start_date=start,
|
687
|
+
end_date=end,
|
688
|
+
)
|
689
|
+
res: HistoricalCandlesResponse = await grpc_client.unary_unary(req)
|
690
|
+
|
691
|
+
if as_dataframe:
|
692
|
+
return candles_to_dataframe(res.candles)
|
693
|
+
else:
|
694
|
+
return res.candles
|
695
|
+
|
696
|
+
async def get_l1_book_snapshot(
|
697
|
+
self,
|
698
|
+
symbol: TradableProduct | str,
|
699
|
+
venue: Venue,
|
700
|
+
) -> L1BookSnapshot:
|
701
|
+
"""
|
702
|
+
Gets the L1 book snapshot for a symbol.
|
703
|
+
|
704
|
+
Args:
|
705
|
+
symbol: the symbol to get the l1 book snapshot for
|
706
|
+
venue: the venue that the symbol is traded at
|
707
|
+
"""
|
708
|
+
grpc_client = await self.marketdata(venue)
|
709
|
+
req = L1BookSnapshotRequest(symbol=str(symbol), venue=venue)
|
710
|
+
res: L1BookSnapshot = await grpc_client.unary_unary(req)
|
711
|
+
return res
|
712
|
+
|
713
|
+
async def get_l1_book_snapshots(
|
714
|
+
self, symbols: list[TradableProduct | str], venue: Venue
|
715
|
+
) -> Sequence[L1BookSnapshot]:
|
716
|
+
"""
|
717
|
+
Gets the L1 book snapshots for a list of symbols.
|
718
|
+
|
719
|
+
Args:
|
720
|
+
symbols: the symbols to get the l1 book snapshots for
|
721
|
+
venue: the venue that the symbols are traded at
|
722
|
+
"""
|
723
|
+
grpc_client = await self.marketdata(venue)
|
724
|
+
req = L1BookSnapshotsRequest(symbols=symbols)
|
725
|
+
res: ArrayOfL1BookSnapshot = await grpc_client.unary_unary(
|
726
|
+
req # pyright: ignore[reportArgumentType]
|
727
|
+
)
|
728
|
+
return res
|
729
|
+
|
730
|
+
async def get_l2_book_snapshot(
|
731
|
+
self, symbol: TradableProduct | str, venue: Venue
|
732
|
+
) -> L2BookSnapshot:
|
733
|
+
"""
|
734
|
+
Gets the L2 book snapshot for a symbol.
|
735
|
+
|
736
|
+
Args:
|
737
|
+
symbol: the symbol to get the l2 book snapshot for
|
738
|
+
venue: the venue that the symbol is traded at
|
739
|
+
"""
|
740
|
+
grpc_client = await self.marketdata(venue)
|
741
|
+
req = L2BookSnapshotRequest(symbol=str(symbol), venue=venue)
|
742
|
+
res: L2BookSnapshot = await grpc_client.unary_unary(req)
|
743
|
+
return res
|
744
|
+
|
745
|
+
async def get_ticker(self, symbol: TradableProduct | str, venue: Venue) -> Ticker:
|
746
|
+
"""
|
747
|
+
Gets the ticker for a symbol.
|
748
|
+
"""
|
749
|
+
grpc_client = await self.marketdata(venue)
|
750
|
+
req = TickerRequest(symbol=str(symbol), venue=venue)
|
751
|
+
res: Ticker = await grpc_client.unary_unary(req)
|
752
|
+
return res
|
753
|
+
|
754
|
+
async def stream_l1_book_snapshots(
|
755
|
+
self, symbols: Sequence[TradableProduct | str], venue: Venue
|
756
|
+
) -> AsyncGenerator[L1BookSnapshot, None]:
|
757
|
+
"""
|
758
|
+
Subscribe to the stream of L1BookSnapshots for a symbol.
|
759
|
+
|
760
|
+
Args:
|
761
|
+
symbols: the symbols to subscribe to;
|
762
|
+
If symbols=None, subscribe to all symbols available for the venue.
|
763
|
+
venue: the venue to subscribe to
|
764
|
+
"""
|
765
|
+
grpc_client = await self.marketdata(venue)
|
766
|
+
req = SubscribeL1BookSnapshotsRequest(symbols=list(symbols), venue=venue)
|
767
|
+
async for res in grpc_client.unary_stream(req):
|
768
|
+
yield res
|
769
|
+
|
770
|
+
async def stream_l2_book_updates(
|
771
|
+
self, symbol: TradableProduct | str, venue: Venue
|
772
|
+
) -> AsyncGenerator[L2BookUpdate, None]:
|
773
|
+
"""
|
774
|
+
Subscribe to the stream of L2BookUpdates for a symbol.
|
775
|
+
|
776
|
+
This stream is a diff stream; to construct and maintain the actual state of
|
777
|
+
the L2 book, apply the updates stream using the method described.
|
778
|
+
|
779
|
+
Args:
|
780
|
+
symbol: the symbol to subscribe to
|
781
|
+
venue: the marketdata venue
|
782
|
+
"""
|
783
|
+
grpc_client = await self.marketdata(venue)
|
784
|
+
req = SubscribeL2BookUpdatesRequest(symbol=str(symbol), venue=venue)
|
785
|
+
async for res in grpc_client.unary_stream(
|
786
|
+
req # pyright: ignore[reportArgumentType]
|
787
|
+
):
|
788
|
+
yield res
|
789
|
+
|
790
|
+
async def subscribe_l1_book(
|
791
|
+
self, symbol: TradableProduct | str, venue: Venue
|
792
|
+
) -> L1BookSnapshot:
|
793
|
+
"""
|
794
|
+
Subscribe to the L1 stream for a symbol in the background.
|
795
|
+
|
796
|
+
If a subscription is already active, the existing reference will be
|
797
|
+
returned; otherwise, a new subscription will be created.
|
798
|
+
|
799
|
+
Snapshots will have an initial value of timestamp=0 and bid/ask=None.
|
800
|
+
|
801
|
+
Args:
|
802
|
+
symbol: the symbol to subscribe to
|
803
|
+
venue: the marketdata venue
|
804
|
+
|
805
|
+
Return:
|
806
|
+
An L1 book object that is constantly updating in the background.
|
807
|
+
"""
|
808
|
+
symbol = TradableProduct(symbol)
|
809
|
+
|
810
|
+
if venue in self.l1_books:
|
811
|
+
if symbol in self.l1_books[venue]:
|
812
|
+
return self.l1_books[venue][symbol][0]
|
813
|
+
else:
|
814
|
+
self.l1_books[venue] = {}
|
815
|
+
|
816
|
+
grpc_client = await self.marketdata(venue)
|
817
|
+
book = L1BookSnapshot(symbol, 0, 0)
|
818
|
+
self.l1_books[venue][symbol] = (
|
819
|
+
book,
|
820
|
+
asyncio.create_task(
|
821
|
+
self.__subscribe_l1_book_task(symbol, venue, grpc_client, book)
|
822
|
+
),
|
823
|
+
)
|
824
|
+
return book
|
825
|
+
|
826
|
+
async def unsubscribe_l1_book(self, symbol: TradableProduct | str, venue: Venue):
|
827
|
+
"""
|
828
|
+
Unsubscribe from the L1 stream for a symbol, ie undoes subscribe_l1_book.
|
829
|
+
"""
|
830
|
+
symbol = TradableProduct(symbol)
|
831
|
+
try:
|
832
|
+
task = self.l1_books[venue][symbol][1]
|
833
|
+
task.cancel()
|
834
|
+
except Exception as e:
|
835
|
+
logging.error(
|
836
|
+
f"Error unsubscribing from L1 book for {symbol}, venue {venue}: {e}"
|
837
|
+
)
|
838
|
+
finally:
|
839
|
+
if venue in self.l1_books and symbol in self.l1_books[venue]:
|
840
|
+
del self.l1_books[venue][symbol]
|
841
|
+
|
842
|
+
async def __subscribe_l1_book_task(
|
843
|
+
self,
|
844
|
+
symbol: TradableProduct,
|
845
|
+
venue: Venue,
|
846
|
+
grpc_client: GrpcClient,
|
847
|
+
book: L1BookSnapshot,
|
848
|
+
):
|
849
|
+
try:
|
850
|
+
req = SubscribeL1BookSnapshotsRequest(symbols=[symbol], venue=venue)
|
851
|
+
stream = grpc_client.unary_stream(req)
|
852
|
+
async for snap in stream:
|
853
|
+
book.tn = snap.tn
|
854
|
+
book.ts = snap.ts
|
855
|
+
book.a = snap.a
|
856
|
+
book.b = snap.b
|
857
|
+
book.rt = snap.rt
|
858
|
+
book.rtn = snap.rtn
|
859
|
+
except Exception as e:
|
860
|
+
logging.error(
|
861
|
+
f"Error subscribing to L1 book for {symbol}, venue {venue}: {e}"
|
862
|
+
)
|
863
|
+
finally:
|
864
|
+
del self.l1_books[venue][symbol]
|
865
|
+
|
866
|
+
async def subscribe_l2_book(
|
867
|
+
self,
|
868
|
+
symbol: TradableProduct | str,
|
869
|
+
venue: Venue,
|
870
|
+
) -> L2BookSnapshot:
|
871
|
+
"""
|
872
|
+
Subscribe to the L2 stream for a symbol in the background.
|
873
|
+
|
874
|
+
If a subscription is already active, the existing reference will be
|
875
|
+
returned; otherwise, a new subscription will be created.
|
876
|
+
|
877
|
+
Snapshots will have an initial value of timestamp=0 and bids/asks=[].
|
878
|
+
|
879
|
+
Args:
|
880
|
+
symbol: the symbol to subscribe to
|
881
|
+
venue: the marketdata venue
|
882
|
+
|
883
|
+
Return:
|
884
|
+
An L2 book object that is constantly updating in the background.
|
885
|
+
"""
|
886
|
+
symbol = TradableProduct(symbol)
|
887
|
+
|
888
|
+
if venue in self.l2_books:
|
889
|
+
if symbol in self.l2_books[venue]:
|
890
|
+
return self.l2_books[venue][symbol][0]
|
891
|
+
else:
|
892
|
+
self.l2_books[venue] = {}
|
893
|
+
|
894
|
+
grpc_client = await self.marketdata(venue)
|
895
|
+
book = L2BookSnapshot([], [], 0, 0, 0, 0)
|
896
|
+
self.l2_books[venue][symbol] = (
|
897
|
+
book,
|
898
|
+
asyncio.create_task(
|
899
|
+
self.__subscribe_l2_book_task(symbol, venue, grpc_client, book)
|
900
|
+
),
|
901
|
+
)
|
902
|
+
return book
|
903
|
+
|
904
|
+
async def __subscribe_l2_book_task(
|
905
|
+
self,
|
906
|
+
symbol: TradableProduct,
|
907
|
+
venue: Venue,
|
908
|
+
grpc_client: GrpcClient,
|
909
|
+
book: L2BookSnapshot,
|
910
|
+
):
|
911
|
+
try:
|
912
|
+
req = SubscribeL2BookUpdatesRequest(symbol=str(symbol), venue=venue)
|
913
|
+
stream = grpc_client.unary_stream(
|
914
|
+
req # pyright: ignore[reportArgumentType]
|
915
|
+
)
|
916
|
+
async for up in stream:
|
917
|
+
if isinstance(up, L2BookDiff):
|
918
|
+
if (
|
919
|
+
up.sequence_id != book.sequence_id
|
920
|
+
or up.sequence_number != book.sequence_number + 1
|
921
|
+
):
|
922
|
+
raise ValueError(
|
923
|
+
f"Received update out-of-order for L2 book: {symbol}"
|
924
|
+
)
|
925
|
+
book.sid = up.sid
|
926
|
+
book.sn = up.sn
|
927
|
+
book.ts = up.ts
|
928
|
+
book.tn = up.tn
|
929
|
+
for px, sz in up.bids:
|
930
|
+
update_orderbook_side(book.bids, px, sz, ascending=False)
|
931
|
+
for px, sz in up.asks:
|
932
|
+
update_orderbook_side(book.asks, px, sz, ascending=True)
|
933
|
+
elif isinstance(up, L2BookSnapshot):
|
934
|
+
book.sid = up.sid
|
935
|
+
book.sn = up.sn
|
936
|
+
book.ts = up.ts
|
937
|
+
book.tn = up.tn
|
938
|
+
book.a = up.a
|
939
|
+
book.b = up.b
|
940
|
+
except Exception as e:
|
941
|
+
logging.error(
|
942
|
+
f"Error subscribing to L2 book for {symbol}, venue {venue}: {e}"
|
943
|
+
)
|
944
|
+
finally:
|
945
|
+
del self.l2_books[venue][symbol]
|
946
|
+
|
947
|
+
async def stream_trades(
|
948
|
+
self, symbol: TradableProduct | str, venue: Venue
|
949
|
+
) -> AsyncGenerator[Trade, None]:
|
950
|
+
"""
|
951
|
+
Subscribe to a stream of trades for a symbol.
|
952
|
+
"""
|
953
|
+
grpc_client = await self.marketdata(venue)
|
954
|
+
req = SubscribeTradesRequest(symbol=str(symbol), venue=venue)
|
955
|
+
async for res in grpc_client.unary_stream(req):
|
956
|
+
yield res
|
957
|
+
|
958
|
+
async def stream_candles(
|
959
|
+
self,
|
960
|
+
symbol: TradableProduct | str,
|
961
|
+
venue: Venue,
|
962
|
+
candle_widths: Optional[list[CandleWidth]],
|
963
|
+
) -> AsyncGenerator[Candle, None]:
|
964
|
+
"""
|
965
|
+
Subscribe to a stream of candles for a symbol.
|
966
|
+
"""
|
967
|
+
grpc_client = await self.marketdata(venue)
|
968
|
+
req = SubscribeCandlesRequest(
|
969
|
+
symbol=str(symbol),
|
970
|
+
venue=venue,
|
971
|
+
candle_widths=candle_widths,
|
972
|
+
)
|
973
|
+
async for res in grpc_client.unary_stream(req):
|
974
|
+
yield res
|
975
|
+
|
976
|
+
# ------------------------------------------------------------
|
977
|
+
# Portfolio management
|
978
|
+
# ------------------------------------------------------------
|
979
|
+
|
980
|
+
async def list_accounts(self) -> List[AccountWithPermissions]:
|
441
981
|
"""
|
442
982
|
List accounts for the user that the API key belongs to.
|
443
983
|
|
444
984
|
Returns:
|
445
985
|
a list of AccountWithPermissionsFields for the user that the API key belongs to
|
986
|
+
a list of AccountWithPermissions for the user that the API key belongs to
|
446
987
|
(use who_am_i to get the user_id / email)
|
447
988
|
"""
|
448
|
-
|
449
|
-
|
989
|
+
grpc_client = await self.core()
|
990
|
+
req = AccountsRequest(paper=self.paper_trading)
|
991
|
+
res = await grpc_client.unary_unary(req)
|
992
|
+
return res.accounts
|
450
993
|
|
451
|
-
async def get_account_summary(self, account: str) ->
|
994
|
+
async def get_account_summary(self, account: str) -> AccountSummary:
|
452
995
|
"""
|
453
|
-
|
996
|
+
Get account summary, including balances, positions, pnls, etc.
|
454
997
|
|
455
998
|
Args:
|
456
|
-
account:
|
457
|
-
|
458
|
-
Returns:
|
459
|
-
AccountSummaryFields for the account
|
999
|
+
account: account uuid or name
|
1000
|
+
Examples: "00000000-0000-0000-0000-000000000000", "STONEX:000000/JDoe"
|
460
1001
|
"""
|
461
|
-
|
462
|
-
|
1002
|
+
grpc_client = await self.core()
|
1003
|
+
req = AccountSummaryRequest(account=account)
|
1004
|
+
res = await grpc_client.unary_unary(req)
|
1005
|
+
return res
|
463
1006
|
|
464
1007
|
async def get_account_summaries(
|
465
1008
|
self,
|
466
1009
|
accounts: Optional[list[str]] = None,
|
467
1010
|
trader: Optional[str] = None,
|
468
|
-
) ->
|
1011
|
+
) -> list[AccountSummary]:
|
469
1012
|
"""
|
470
|
-
|
1013
|
+
Get account summaries for accounts matching the filters.
|
471
1014
|
|
472
1015
|
Args:
|
473
|
-
accounts:
|
474
|
-
|
475
|
-
trader: the trader / userId to get summaries for
|
1016
|
+
accounts: list of account uuids or names
|
1017
|
+
trader: if specified, return summaries for all accounts for this trader
|
476
1018
|
|
477
|
-
|
478
|
-
Returns:
|
479
|
-
a list of AccountSummary for the accounts
|
1019
|
+
If both arguments are given, the union of matching accounts are returned.
|
480
1020
|
"""
|
481
|
-
|
482
|
-
|
1021
|
+
grpc_client = await self.core()
|
1022
|
+
request = AccountSummariesRequest(
|
1023
|
+
accounts=accounts,
|
1024
|
+
trader=trader,
|
483
1025
|
)
|
484
|
-
|
1026
|
+
res = await grpc_client.unary_unary(request)
|
1027
|
+
return res.account_summaries
|
485
1028
|
|
486
1029
|
async def get_account_history(
|
487
1030
|
self,
|
488
1031
|
account: str,
|
489
1032
|
from_inclusive: Optional[datetime] = None,
|
490
1033
|
to_exclusive: Optional[datetime] = None,
|
491
|
-
) ->
|
1034
|
+
) -> list[AccountSummary]:
|
492
1035
|
"""
|
493
|
-
|
494
|
-
|
495
|
-
Returns:
|
496
|
-
a list of AccountSummaryFields for the account for the given dates
|
497
|
-
use timestamp to get the time of the of the summary
|
1036
|
+
Get historical sequence of account summaries for the given account.
|
498
1037
|
"""
|
499
|
-
|
1038
|
+
grpc_client = await self.core()
|
1039
|
+
if from_inclusive is not None:
|
1040
|
+
assert from_inclusive.tzinfo is timezone.utc, (
|
1041
|
+
"from_inclusive must be a utc datetime:\n"
|
1042
|
+
"for example datetime.now(timezone.utc) or \n"
|
1043
|
+
"dt = datetime(2025, 4, 15, 12, 0, 0, tzinfo=timezone.utc)"
|
1044
|
+
)
|
1045
|
+
|
1046
|
+
if to_exclusive is not None:
|
1047
|
+
assert to_exclusive.tzinfo is timezone.utc, (
|
1048
|
+
"to_exclusive must be a utc datetime:\n"
|
1049
|
+
"for example datetime.now(timezone.utc) or \n"
|
1050
|
+
"dt = datetime(2025, 4, 15, 12, 0, 0, tzinfo=timezone.utc)"
|
1051
|
+
)
|
1052
|
+
|
1053
|
+
req = AccountHistoryRequest(
|
500
1054
|
account=account, from_inclusive=from_inclusive, to_exclusive=to_exclusive
|
501
1055
|
)
|
502
|
-
|
1056
|
+
res = await grpc_client.unary_unary(req)
|
1057
|
+
return res.history
|
503
1058
|
|
504
1059
|
# ------------------------------------------------------------
|
505
|
-
# Order
|
1060
|
+
# Order management
|
506
1061
|
# ------------------------------------------------------------
|
507
1062
|
|
508
1063
|
async def get_open_orders(
|
509
1064
|
self,
|
510
|
-
order_ids: Optional[list[
|
1065
|
+
order_ids: Optional[list[OrderId]] = None,
|
511
1066
|
venue: Optional[str] = None,
|
512
1067
|
account: Optional[str] = None,
|
513
1068
|
trader: Optional[str] = None,
|
514
1069
|
symbol: Optional[str] = None,
|
515
|
-
parent_order_id: Optional[
|
516
|
-
) ->
|
1070
|
+
parent_order_id: Optional[OrderId] = None,
|
1071
|
+
) -> list[Order]:
|
517
1072
|
"""
|
518
1073
|
Returns a list of open orders for the user that match the filters.
|
519
1074
|
|
@@ -525,12 +1080,11 @@ class AsyncClient:
|
|
525
1080
|
symbol: the symbol to get orders for
|
526
1081
|
parent_order_id: the parent order id to get orders for
|
527
1082
|
|
528
|
-
these filters are combinewd via OR statements so if you pass
|
529
|
-
in multiple arguments, it will return the union of the results
|
530
1083
|
Returns:
|
531
|
-
|
1084
|
+
Open orders that match the union of the filters
|
532
1085
|
"""
|
533
|
-
|
1086
|
+
grpc_client = await self.core()
|
1087
|
+
open_orders_request = OpenOrdersRequest(
|
534
1088
|
venue=venue,
|
535
1089
|
account=account,
|
536
1090
|
trader=trader,
|
@@ -538,46 +1092,63 @@ class AsyncClient:
|
|
538
1092
|
parent_order_id=parent_order_id,
|
539
1093
|
order_ids=order_ids,
|
540
1094
|
)
|
541
|
-
return orders.open_orders
|
542
1095
|
|
543
|
-
|
1096
|
+
open_orders = await grpc_client.unary_unary(open_orders_request)
|
1097
|
+
return open_orders.open_orders
|
1098
|
+
|
1099
|
+
async def get_all_open_orders(self) -> list[Order]:
|
544
1100
|
"""
|
545
|
-
|
1101
|
+
@deprecated(reason="Use get_open_orders with no parameters instead")
|
546
1102
|
|
547
|
-
Returns
|
548
|
-
a list of OrderFields of all the open orders for the user
|
1103
|
+
Returns a list of all open orders for the authenticated user.
|
549
1104
|
"""
|
550
|
-
|
551
|
-
return orders.open_orders
|
1105
|
+
return await self.get_open_orders()
|
552
1106
|
|
553
1107
|
async def get_historical_orders(
|
554
1108
|
self,
|
555
|
-
order_ids: Optional[list[
|
1109
|
+
order_ids: Optional[list[OrderId]] = None,
|
556
1110
|
from_inclusive: Optional[datetime] = None,
|
557
1111
|
to_exclusive: Optional[datetime] = None,
|
558
1112
|
venue: Optional[str] = None,
|
559
1113
|
account: Optional[str] = None,
|
560
|
-
parent_order_id: Optional[
|
561
|
-
) ->
|
1114
|
+
parent_order_id: Optional[OrderId] = None,
|
1115
|
+
) -> list[Order]:
|
562
1116
|
"""
|
563
|
-
|
1117
|
+
Returns a list of all historical orders that match the filters.
|
1118
|
+
|
1119
|
+
Historical orders are orders that are not open, having been filled,
|
1120
|
+
canceled, expired, or outed.
|
564
1121
|
|
565
1122
|
Args:
|
566
1123
|
order_ids: a list of order ids to get
|
567
1124
|
from_inclusive: the start date to get orders for
|
568
1125
|
to_exclusive: the end date to get orders for
|
569
1126
|
venue: the venue to get orders for, e.g. CME
|
570
|
-
account:
|
571
|
-
can be the account id( (a UUID) or the account name (e.g. CQG:00000)
|
1127
|
+
account: account uuid or name
|
572
1128
|
parent_order_id: the parent order id to get orders for
|
573
1129
|
Returns:
|
574
|
-
|
1130
|
+
Historical orders that match the union of the filters.
|
575
1131
|
|
576
|
-
|
577
|
-
|
578
|
-
the from_inclusive and to_exclusive parameters need to be filled
|
1132
|
+
If order_ids is not specified, then from_inclusive and to_exclusive
|
1133
|
+
MUST be specified.
|
579
1134
|
"""
|
580
|
-
|
1135
|
+
grpc_client = await self.core()
|
1136
|
+
|
1137
|
+
if from_inclusive is not None:
|
1138
|
+
assert from_inclusive.tzinfo is timezone.utc, (
|
1139
|
+
"from_inclusive must be a utc datetime:\n"
|
1140
|
+
"for example datetime.now(timezone.utc) or \n"
|
1141
|
+
"dt = datetime(2025, 4, 15, 12, 0, 0, tzinfo=timezone.utc)"
|
1142
|
+
)
|
1143
|
+
|
1144
|
+
if to_exclusive is not None:
|
1145
|
+
assert to_exclusive.tzinfo is timezone.utc, (
|
1146
|
+
"to_exclusive must be a utc datetime:\n"
|
1147
|
+
"for example datetime.now(timezone.utc) or \n"
|
1148
|
+
"dt = datetime(2025, 4, 15, 12, 0, 0, tzinfo=timezone.utc)"
|
1149
|
+
)
|
1150
|
+
|
1151
|
+
historical_orders_request = HistoricalOrdersRequest.new(
|
581
1152
|
order_ids=order_ids,
|
582
1153
|
venue=venue,
|
583
1154
|
account=account,
|
@@ -585,406 +1156,221 @@ class AsyncClient:
|
|
585
1156
|
from_inclusive=from_inclusive,
|
586
1157
|
to_exclusive=to_exclusive,
|
587
1158
|
)
|
588
|
-
|
1159
|
+
orders = await grpc_client.unary_unary(historical_orders_request)
|
1160
|
+
return orders.orders
|
589
1161
|
|
590
|
-
async def get_order(self, order_id:
|
1162
|
+
async def get_order(self, order_id: OrderId) -> Optional[Order]:
|
591
1163
|
"""
|
592
|
-
Returns the
|
593
|
-
|
1164
|
+
Returns the specified order. Useful for looking at past sent orders.
|
1165
|
+
Queries open_orders first, then queries historical_orders.
|
594
1166
|
|
595
1167
|
Args:
|
596
1168
|
order_id: the order id to get
|
597
|
-
Returns:
|
598
|
-
the OrderFields object for the order
|
599
|
-
|
600
|
-
Queries open_orders first then queries historical_orders
|
601
1169
|
"""
|
602
|
-
|
603
|
-
|
1170
|
+
grpc_client = await self.core()
|
1171
|
+
req = OpenOrdersRequest.new(
|
1172
|
+
order_ids=[order_id],
|
604
1173
|
)
|
605
|
-
|
606
|
-
for open_order in
|
1174
|
+
res = await grpc_client.unary_unary(req)
|
1175
|
+
for open_order in res.open_orders:
|
607
1176
|
if open_order.id == order_id:
|
608
1177
|
return open_order
|
609
1178
|
|
610
|
-
|
611
|
-
order_ids=[order_id]
|
1179
|
+
req = HistoricalOrdersRequest.new(
|
1180
|
+
order_ids=[order_id],
|
612
1181
|
)
|
1182
|
+
res = await grpc_client.unary_unary(req)
|
1183
|
+
if res.orders and len(res.orders) == 1:
|
1184
|
+
return res.orders[0]
|
613
1185
|
|
614
|
-
|
615
|
-
return historical_orders.historical_orders[0]
|
616
|
-
|
617
|
-
async def get_orders(self, order_ids: list[str]) -> list[Optional[OrderFields]]:
|
1186
|
+
async def get_orders(self, order_ids: list[OrderId]) -> list[Optional[Order]]:
|
618
1187
|
"""
|
619
|
-
Returns
|
620
|
-
|
1188
|
+
Returns the specified orders. Useful for looking at past sent orders.
|
1189
|
+
Plural form of get_order.
|
621
1190
|
|
622
1191
|
Args:
|
623
1192
|
order_ids: a list of order ids to get
|
624
|
-
Returns:
|
625
|
-
a list of OrderFields objects for the orders
|
626
|
-
|
627
|
-
Plural form of get_order
|
628
1193
|
"""
|
629
|
-
|
1194
|
+
grpc_client = await self.core()
|
1195
|
+
orders_dict: dict[OrderId, Optional[Order]] = {
|
630
1196
|
order_id: None for order_id in order_ids
|
631
1197
|
}
|
1198
|
+
req = OpenOrdersRequest.new(
|
1199
|
+
order_ids=order_ids,
|
1200
|
+
)
|
632
1201
|
|
633
|
-
|
634
|
-
|
635
|
-
).open_orders
|
636
|
-
for open_order in open_orders:
|
1202
|
+
res = await grpc_client.unary_unary(req)
|
1203
|
+
for open_order in res.open_orders:
|
637
1204
|
orders_dict[open_order.id] = open_order
|
638
1205
|
|
639
1206
|
not_open_order_ids = [
|
640
1207
|
order_id for order_id in order_ids if orders_dict[order_id] is None
|
641
1208
|
]
|
642
1209
|
|
643
|
-
|
644
|
-
|
645
|
-
|
646
|
-
|
647
|
-
|
648
|
-
for historical_order in historical_orders:
|
1210
|
+
req = HistoricalOrdersRequest.new(
|
1211
|
+
order_ids=not_open_order_ids,
|
1212
|
+
)
|
1213
|
+
res = await grpc_client.unary_unary(req)
|
1214
|
+
for historical_order in res.orders:
|
649
1215
|
orders_dict[historical_order.id] = historical_order
|
650
1216
|
|
651
1217
|
return [orders_dict[order_id] for order_id in order_ids]
|
652
1218
|
|
653
1219
|
async def get_fills(
|
654
1220
|
self,
|
655
|
-
from_inclusive: Optional[datetime],
|
656
|
-
to_exclusive: Optional[datetime],
|
1221
|
+
from_inclusive: Optional[datetime] = None,
|
1222
|
+
to_exclusive: Optional[datetime] = None,
|
657
1223
|
venue: Optional[str] = None,
|
658
1224
|
account: Optional[str] = None,
|
659
|
-
order_id: Optional[
|
660
|
-
|
1225
|
+
order_id: Optional[OrderId] = None,
|
1226
|
+
limit: Optional[int] = None,
|
1227
|
+
) -> HistoricalFillsResponse:
|
661
1228
|
"""
|
662
|
-
Returns
|
1229
|
+
Returns all fills matching the given filters.
|
663
1230
|
|
664
1231
|
Args:
|
665
1232
|
from_inclusive: the start date to get fills for
|
666
1233
|
to_exclusive: the end date to get fills for
|
667
1234
|
venue: the venue to get fills for, e.g. "CME"
|
668
|
-
account:
|
669
|
-
can be the account id( (a UUID) or the account name (e.g. CQG:00000)
|
1235
|
+
account: account uuid or name
|
670
1236
|
order_id: the order id to get fills for
|
671
|
-
Returns:
|
672
|
-
a list of GetFillsQueryFolioHistoricalFills
|
673
|
-
"""
|
674
|
-
fills = await self.graphql_client.get_fills_query(
|
675
|
-
venue, account, order_id, from_inclusive, to_exclusive
|
676
|
-
)
|
677
|
-
return fills.historical_fills
|
678
|
-
|
679
|
-
# ------------------------------------------------------------
|
680
|
-
# Market Data
|
681
|
-
# ------------------------------------------------------------
|
682
|
-
|
683
|
-
async def get_market_status(
|
684
|
-
self, symbol: TradableProduct, venue: str
|
685
|
-
) -> MarketStatusFields:
|
686
1237
|
"""
|
687
|
-
|
688
|
-
|
689
|
-
|
690
|
-
|
691
|
-
|
692
|
-
|
693
|
-
|
694
|
-
"""
|
695
|
-
market_status = await self.graphql_client.get_market_status_query(symbol, venue)
|
696
|
-
return market_status.market_status
|
697
|
-
|
698
|
-
async def get_market_snapshot(
|
699
|
-
self, symbol: TradableProduct, venue: str
|
700
|
-
) -> MarketTickerFields:
|
701
|
-
"""
|
702
|
-
This is an alias for l1_book_snapshot.
|
703
|
-
|
704
|
-
Args:
|
705
|
-
symbol: the symbol to get the market snapshot for, e.g. "ES 20250321 CME Future/USD"
|
706
|
-
venue: the venue that the symbol is traded at, e.g. CME
|
707
|
-
Returns:
|
708
|
-
MarketTickerFields for the symbol
|
709
|
-
"""
|
710
|
-
return await self.get_l1_book_snapshot(symbol=symbol, venue=venue)
|
711
|
-
|
712
|
-
async def get_market_snapshots(
|
713
|
-
self, symbols: list[TradableProduct], venue: str
|
714
|
-
) -> Sequence[MarketTickerFields]:
|
715
|
-
"""
|
716
|
-
This is an alias for l1_book_snapshot.
|
717
|
-
|
718
|
-
Args:
|
719
|
-
symbols: the symbols to get the market snapshots for
|
720
|
-
venue: the venue that the symbols are traded at
|
721
|
-
Returns:
|
722
|
-
a list of MarketTickerFields for the symbols
|
723
|
-
"""
|
724
|
-
return await self.get_l1_book_snapshots(
|
725
|
-
venue=venue, symbols=symbols # type: ignore
|
726
|
-
)
|
727
|
-
|
728
|
-
async def get_historical_candles(
|
729
|
-
self,
|
730
|
-
symbol: str,
|
731
|
-
candle_width: grpc_definitions.CandleWidth,
|
732
|
-
start: datetime,
|
733
|
-
end: datetime,
|
734
|
-
) -> HistoricalCandlesResponse:
|
735
|
-
"""
|
736
|
-
Gets the historical candles for a symbol.
|
1238
|
+
grpc_client = await self.core()
|
1239
|
+
if from_inclusive is not None:
|
1240
|
+
assert from_inclusive.tzinfo is timezone.utc, (
|
1241
|
+
"from_inclusive must be a utc datetime:\n"
|
1242
|
+
"for example datetime.now(timezone.utc) or \n"
|
1243
|
+
"dt = datetime(2025, 4, 15, 12, 0, 0, tzinfo=timezone.utc)"
|
1244
|
+
)
|
737
1245
|
|
738
|
-
|
739
|
-
|
740
|
-
|
741
|
-
|
742
|
-
|
743
|
-
|
744
|
-
|
745
|
-
|
746
|
-
|
747
|
-
|
748
|
-
|
749
|
-
|
750
|
-
|
751
|
-
end_date=end,
|
1246
|
+
if to_exclusive is not None:
|
1247
|
+
assert to_exclusive.tzinfo is timezone.utc, (
|
1248
|
+
"to_exclusive must be a utc datetime:\n"
|
1249
|
+
"for example datetime.now(timezone.utc) or \n"
|
1250
|
+
"dt = datetime(2025, 4, 15, 12, 0, 0, tzinfo=timezone.utc)"
|
1251
|
+
)
|
1252
|
+
req = HistoricalFillsRequest(
|
1253
|
+
account=account,
|
1254
|
+
from_inclusive=from_inclusive,
|
1255
|
+
limit=limit,
|
1256
|
+
order_id=order_id,
|
1257
|
+
to_exclusive=to_exclusive,
|
1258
|
+
venue=venue,
|
752
1259
|
)
|
1260
|
+
res = await grpc_client.unary_unary(req)
|
1261
|
+
return res
|
753
1262
|
|
754
|
-
|
755
|
-
|
756
|
-
async def get_l1_book_snapshot(
|
1263
|
+
async def orderflow(
|
757
1264
|
self,
|
758
|
-
|
759
|
-
|
760
|
-
) -> MarketTickerFields:
|
1265
|
+
request_iterator: AsyncIterator[OrderflowRequest],
|
1266
|
+
) -> AsyncGenerator[Orderflow, None]:
|
761
1267
|
"""
|
762
|
-
|
1268
|
+
A two-way channel for both order entry and listening to order updates (fills, acks, outs, etc.).
|
763
1269
|
|
764
|
-
|
765
|
-
symbol: the symbol to get the l1 book snapshot for
|
766
|
-
venue: the venue that the symbol is traded at
|
767
|
-
Returns:
|
768
|
-
MarketTickerFields for the symbol
|
769
|
-
"""
|
770
|
-
snapshot = await self.graphql_client.get_l_1_book_snapshot_query(
|
771
|
-
symbol=symbol, venue=venue
|
772
|
-
)
|
773
|
-
return snapshot.ticker
|
1270
|
+
This is considered the most efficient way to trade in this SDK.
|
774
1271
|
|
775
|
-
|
776
|
-
|
777
|
-
) -> Sequence[MarketTickerFields]:
|
778
|
-
"""
|
779
|
-
Gets the L1 book snapshots for a list of symbols.
|
1272
|
+
Example:
|
1273
|
+
See test_orderflow.py for an example.
|
780
1274
|
|
781
|
-
|
782
|
-
symbols: the symbols to get the l1 book snapshots for
|
783
|
-
venue: the venue that the symbols are traded at
|
784
|
-
Returns:
|
785
|
-
a list of MarketTickerFields for the symbols
|
1275
|
+
This WILL block the event loop until the stream is closed.
|
786
1276
|
"""
|
787
|
-
|
788
|
-
|
1277
|
+
grpc_client = await self.core()
|
1278
|
+
decoder = grpc_client.get_decoder(OrderflowRequestUnannotatedResponseType)
|
1279
|
+
stub: grpc.aio.StreamStreamMultiCallable = grpc_client.channel.stream_stream(
|
1280
|
+
OrderflowRequest_route,
|
1281
|
+
request_serializer=grpc_client.encoder().encode,
|
1282
|
+
response_deserializer=decoder.decode,
|
789
1283
|
)
|
790
|
-
|
791
|
-
|
792
|
-
async def get_l2_book_snapshot(self, symbol: str, venue: str) -> L2BookFields:
|
793
|
-
"""
|
794
|
-
Gets the L2 book snapshot for a symbol.
|
795
|
-
|
796
|
-
Args:
|
797
|
-
symbol: the symbol to get the l2 book snapshot for
|
798
|
-
venue: the venue that the symbol is traded at
|
799
|
-
Returns:
|
800
|
-
L2BookFields for the symbol
|
801
|
-
|
802
|
-
Note: this does NOT update, it is a snapshot at a given time
|
803
|
-
For an object that updates, use subscribe_l2_book
|
804
|
-
"""
|
805
|
-
l2_book = await self.graphql_client.get_l_2_book_snapshot_query(
|
806
|
-
symbol=symbol, venue=venue
|
1284
|
+
call: grpc.aio._base_call.StreamStreamCall[OrderflowRequest, Orderflow] = stub(
|
1285
|
+
request_iterator, metadata=(("authorization", f"Bearer {grpc_client.jwt}"),)
|
807
1286
|
)
|
808
|
-
|
809
|
-
|
810
|
-
async def subscribe_l1_book_stream(
|
811
|
-
self, symbols: list[TradableProduct], venue: str
|
812
|
-
) -> AsyncIterator[L1BookSnapshot]:
|
813
|
-
"""
|
814
|
-
Subscribe to the stream of L1BookSnapshots for a symbol.
|
815
|
-
|
816
|
-
Args:
|
817
|
-
symbol: the symbol to subscribe to
|
818
|
-
venue: the venue to subscribe to
|
819
|
-
Returns:
|
820
|
-
an async iterator that yields L1BookSnapshot, representing the l1 book updates
|
821
|
-
"""
|
822
|
-
async for snapshot in await self.grpc_client.subscribe_l1_books_stream(
|
823
|
-
symbols=[str(s) for s in symbols]
|
824
|
-
):
|
825
|
-
yield snapshot
|
826
|
-
|
827
|
-
async def subscribe_l2_book_stream(
|
828
|
-
self, symbol: TradableProduct, venue: str
|
829
|
-
) -> AsyncIterator[L2BookUpdate]:
|
830
|
-
"""
|
831
|
-
Subscribe to the stream of L2BookUpdates for a symbol.
|
832
|
-
|
833
|
-
IMPORTANT: note that the Snapshot is a different type than
|
834
|
-
L2BookSnapshot
|
835
|
-
Args:
|
836
|
-
symbol: the symbol to subscribe to
|
837
|
-
venue: the venue to subscribe to
|
838
|
-
Returns:
|
839
|
-
an async iterator that yields L2BookFields
|
840
|
-
L2BookFields is either a Snapshot or a Diff
|
841
|
-
See the grpc_client code for how to handle the different types
|
842
|
-
"""
|
843
|
-
async for snapshot in self.grpc_client.subscribe_l2_books_stream(
|
844
|
-
symbol=symbol, venue=venue
|
845
|
-
):
|
846
|
-
yield snapshot
|
847
|
-
|
848
|
-
async def subscribe_l1_book(
|
849
|
-
self, symbols: list[TradableProduct]
|
850
|
-
) -> list[L1BookSnapshot]:
|
851
|
-
"""
|
852
|
-
Returns a L1BookSnapshot object that is constantly updating in the background.
|
1287
|
+
async for update in call:
|
1288
|
+
yield update
|
853
1289
|
|
854
|
-
|
855
|
-
symbols: the symbols to subscribe to
|
856
|
-
Return:
|
857
|
-
a list of L1BookSnapshot objects that are constantly updating in the background
|
858
|
-
For the duration of the program, the client will be subscribed to the stream
|
859
|
-
and be updating the L1BookSnapshot.
|
860
|
-
|
861
|
-
IMPORTANT: The L1BookSnapshot will be initialized with
|
862
|
-
a timestamp (field tn and ts) of 0
|
863
|
-
along with None for bid and ask
|
864
|
-
|
865
|
-
The reference to the object should be kept, but can also be referenced via
|
866
|
-
client.grpc_client.l1_books.get(symbol)
|
867
|
-
|
868
|
-
If you want direct access to the stream to do on_update type code, you can
|
869
|
-
call client.grpc_client.stream_l1_books
|
870
|
-
"""
|
871
|
-
books = self.grpc_client.initialize_l1_books(symbols)
|
872
|
-
asyncio.create_task(self.grpc_client.watch_l1_books(symbols=symbols))
|
873
|
-
i = 0
|
874
|
-
while not all(book.ts > 0 for book in books) and i < 10:
|
875
|
-
await asyncio.sleep(0.2)
|
876
|
-
i += 1
|
877
|
-
if i == 10:
|
878
|
-
raise ValueError(
|
879
|
-
f"Could not get L1 books for {symbols}. Check if market is quoting via client.get_market_status."
|
880
|
-
)
|
881
|
-
|
882
|
-
return books
|
883
|
-
|
884
|
-
async def subscribe_l2_book(
|
1290
|
+
async def stream_orderflow(
|
885
1291
|
self,
|
886
|
-
|
887
|
-
|
888
|
-
|
1292
|
+
account: Optional[AccountIdOrName] = None,
|
1293
|
+
execution_venue: Optional[str] = None,
|
1294
|
+
trader: Optional[TraderIdOrEmail] = None,
|
1295
|
+
) -> AsyncGenerator[Orderflow, None]:
|
889
1296
|
"""
|
890
|
-
|
891
|
-
|
892
|
-
Args:
|
893
|
-
symbols: the symbols to subscribe to
|
894
|
-
Return:
|
895
|
-
a list of L2BookSnapshot object that is constantly updating in the background
|
896
|
-
For the duration of the program, the client will be subscribed to the stream
|
897
|
-
and be updating the L2BookSnapshot.
|
898
|
-
|
899
|
-
IMPORTANT: The LBBookSnapshot will be initialized with
|
900
|
-
a timestamp (field tn and ts) of 0
|
901
|
-
along with None for bid and ask
|
902
|
-
|
903
|
-
The reference to the object should be kept, but can also be referenced via
|
904
|
-
client.grpc_client.l1_books.get(symbol)
|
905
|
-
|
906
|
-
If you want direct access to the stream to do on_update type code, you can
|
907
|
-
call client.grpc_client.stream_l2_book
|
908
|
-
"""
|
909
|
-
book = self.grpc_client.initialize_l2_book(symbol, venue)
|
910
|
-
asyncio.create_task(self.grpc_client.watch_l2_book(symbol, venue))
|
911
|
-
i = 0
|
912
|
-
while book.ts == 0 and i < 10:
|
913
|
-
await asyncio.sleep(0.2)
|
914
|
-
i += 1
|
915
|
-
if book.ts == 0:
|
916
|
-
if venue:
|
917
|
-
market_status = await self.get_market_status(symbol, venue)
|
918
|
-
if not market_status.is_quoting:
|
919
|
-
raise ValueError(
|
920
|
-
f"Market {symbol} is currently closed, cannot get L2."
|
921
|
-
)
|
922
|
-
raise ValueError(f"Could not get L2 book for {symbol}.")
|
923
|
-
|
924
|
-
return book
|
1297
|
+
A stream for listening to order updates (fills, acks, outs, etc.).
|
925
1298
|
|
926
|
-
|
927
|
-
|
928
|
-
|
929
|
-
|
930
|
-
|
931
|
-
|
932
|
-
request = SubscribeTradesRequest(symbol=symbol, venue=venue)
|
933
|
-
return self.grpc_client.subscribe(request)
|
1299
|
+
Example:
|
1300
|
+
```python
|
1301
|
+
request = SubscribeOrderflowRequest.new()
|
1302
|
+
async for of in client.subscribe_orderflow_stream(request):
|
1303
|
+
print(of)
|
1304
|
+
```
|
934
1305
|
|
935
|
-
|
936
|
-
self,
|
937
|
-
symbol: TradableProduct,
|
938
|
-
venue: Optional[str],
|
939
|
-
candle_widths: Optional[list[grpc_definitions.CandleWidth]],
|
940
|
-
) -> AsyncIterator[Candle]:
|
941
|
-
"""
|
942
|
-
Subscribe to a stream of candles for a symbol.
|
1306
|
+
This WILL block the event loop until the stream is closed.
|
943
1307
|
"""
|
944
|
-
|
945
|
-
|
946
|
-
|
947
|
-
|
1308
|
+
grpc_client = await self.core()
|
1309
|
+
request: SubscribeOrderflowRequest = SubscribeOrderflowRequest(
|
1310
|
+
account=account, execution_venue=execution_venue, trader=trader
|
1311
|
+
)
|
1312
|
+
grpc_client = await self.core()
|
1313
|
+
decoder = grpc_client.get_decoder(SubscribeOrderflowRequest)
|
1314
|
+
stub: grpc.aio.UnaryStreamMultiCallable[
|
1315
|
+
SubscribeOrderflowRequest, Orderflow
|
1316
|
+
] = grpc_client.channel.unary_stream(
|
1317
|
+
SubscribeOrderflowRequest.get_route(),
|
1318
|
+
request_serializer=grpc_client.encoder().encode,
|
1319
|
+
response_deserializer=decoder.decode,
|
948
1320
|
)
|
949
|
-
|
1321
|
+
call: grpc.aio._base_call.UnaryStreamCall[
|
1322
|
+
SubscribeOrderflowRequest, Orderflow
|
1323
|
+
] = stub(request, metadata=(("authorization", f"Bearer {grpc_client.jwt}"),))
|
1324
|
+
async for update in call:
|
1325
|
+
yield update
|
950
1326
|
|
951
1327
|
# ------------------------------------------------------------
|
952
|
-
# Order
|
1328
|
+
# Order entry
|
953
1329
|
# ------------------------------------------------------------
|
954
1330
|
|
955
1331
|
async def send_limit_order(
|
1332
|
+
self,
|
1333
|
+
*args,
|
1334
|
+
**kwargs,
|
1335
|
+
) -> Order:
|
1336
|
+
"""
|
1337
|
+
@deprecated(reason="Use place_limit_order instead")
|
1338
|
+
"""
|
1339
|
+
return await self.place_limit_order(*args, **kwargs)
|
1340
|
+
|
1341
|
+
async def place_limit_order(
|
956
1342
|
self,
|
957
1343
|
*,
|
958
|
-
id: Optional[
|
959
|
-
symbol: TradableProduct,
|
960
|
-
execution_venue: Optional[str],
|
961
|
-
|
1344
|
+
id: Optional[OrderId] = None,
|
1345
|
+
symbol: TradableProduct | str,
|
1346
|
+
execution_venue: Optional[str] = None,
|
1347
|
+
dir: Optional[OrderDir] = None,
|
962
1348
|
quantity: Decimal,
|
963
1349
|
limit_price: Decimal,
|
964
1350
|
order_type: OrderType = OrderType.LIMIT,
|
965
|
-
time_in_force: TimeInForce =
|
966
|
-
good_til_date: Optional[datetime] = None,
|
1351
|
+
time_in_force: TimeInForce = TimeInForceEnum.DAY,
|
967
1352
|
price_round_method: Optional[TickRoundMethod] = None,
|
968
1353
|
account: Optional[str] = None,
|
969
1354
|
trader: Optional[str] = None,
|
970
1355
|
post_only: bool = False,
|
971
1356
|
trigger_price: Optional[Decimal] = None,
|
972
|
-
|
1357
|
+
**kwargs: Any,
|
1358
|
+
) -> Order:
|
973
1359
|
"""
|
974
1360
|
Sends a regular limit order.
|
975
1361
|
|
976
1362
|
Args:
|
1363
|
+
id: in case user wants to generate their own order id, otherwise it will be generated automatically
|
977
1364
|
symbol: the symbol to send the order for
|
978
1365
|
execution_venue: the execution venue to send the order to,
|
979
1366
|
if execution_venue is set to None, the OMS will send the order to the primary_exchange
|
980
1367
|
the primary_exchange can be deduced from `get_product_info`
|
981
|
-
|
1368
|
+
dir: the direction of the order, BUY or SELL
|
982
1369
|
quantity: the quantity of the order
|
983
1370
|
limit_price: the limit price of the order
|
984
1371
|
It is highly recommended to make this a Decimal object from the decimal module to avoid floating point errors
|
985
1372
|
order_type: the type of the order
|
986
1373
|
time_in_force: the time in force of the order
|
987
|
-
good_til_date: the date the order is good until, only relevant for time_in_force = "GTD"
|
988
1374
|
price_round_method: the method to round the price to the nearest tick, will not round if None
|
989
1375
|
account: the account to send the order for
|
990
1376
|
While technically optional, for most order types, the account is required
|
@@ -993,13 +1379,21 @@ class AsyncClient:
|
|
993
1379
|
post_only: whether the order should be post only, not supported by all exchanges
|
994
1380
|
trigger_price: the trigger price for the order, only relevant for stop / take_profit orders
|
995
1381
|
Returns:
|
996
|
-
the
|
1382
|
+
the Order object for the order
|
997
1383
|
The order.status should be "PENDING" until the order is "OPEN" / "REJECTED" / "OUT" / "CANCELED" / "STALE"
|
998
1384
|
|
999
1385
|
If the order is rejected, the order.reject_reason and order.reject_message will be set
|
1000
1386
|
"""
|
1387
|
+
grpc_client = await self.core()
|
1001
1388
|
assert quantity > 0, "quantity must be positive"
|
1002
1389
|
|
1390
|
+
if dir is None:
|
1391
|
+
if "odir" in kwargs and isinstance(kwargs["odir"], OrderDir):
|
1392
|
+
logging.warning("odir is deprecated, use dir instead")
|
1393
|
+
dir = kwargs["odir"]
|
1394
|
+
else:
|
1395
|
+
raise ValueError("dir is required")
|
1396
|
+
|
1003
1397
|
if price_round_method is not None:
|
1004
1398
|
if execution_venue is None:
|
1005
1399
|
product_info = await self.get_product_info(symbol)
|
@@ -1025,44 +1419,43 @@ class AsyncClient:
|
|
1025
1419
|
else:
|
1026
1420
|
raise ValueError(f"Could not find market information for {symbol}")
|
1027
1421
|
|
1028
|
-
|
1029
|
-
|
1030
|
-
|
1031
|
-
|
1032
|
-
|
1033
|
-
|
1034
|
-
|
1035
|
-
|
1036
|
-
|
1037
|
-
|
1038
|
-
|
1039
|
-
|
1040
|
-
|
1041
|
-
post_only,
|
1042
|
-
trigger_price,
|
1043
|
-
good_til_date,
|
1044
|
-
execution_venue,
|
1422
|
+
req: PlaceOrderRequest = PlaceOrderRequest.new(
|
1423
|
+
dir=dir,
|
1424
|
+
quantity=quantity,
|
1425
|
+
symbol=symbol,
|
1426
|
+
time_in_force=time_in_force,
|
1427
|
+
limit_price=limit_price,
|
1428
|
+
order_type=order_type,
|
1429
|
+
account=account,
|
1430
|
+
id=id,
|
1431
|
+
parent_id=None,
|
1432
|
+
source=OrderSource.API,
|
1433
|
+
trader=trader,
|
1434
|
+
execution_venue=execution_venue,
|
1435
|
+
post_only=post_only,
|
1436
|
+
trigger_price=trigger_price,
|
1045
1437
|
)
|
1046
|
-
|
1047
|
-
return
|
1438
|
+
res = await grpc_client.unary_unary(req)
|
1439
|
+
return res
|
1048
1440
|
|
1049
1441
|
async def send_market_pro_order(
|
1050
1442
|
self,
|
1051
1443
|
*,
|
1052
|
-
id: Optional[
|
1053
|
-
symbol: TradableProduct,
|
1444
|
+
id: Optional[OrderId] = None,
|
1445
|
+
symbol: TradableProduct | str,
|
1054
1446
|
execution_venue: str,
|
1055
1447
|
odir: OrderDir,
|
1056
1448
|
quantity: Decimal,
|
1057
|
-
time_in_force: TimeInForce =
|
1449
|
+
time_in_force: TimeInForce = TimeInForceEnum.DAY,
|
1058
1450
|
account: Optional[str] = None,
|
1059
1451
|
fraction_through_market: Decimal = Decimal("0.001"),
|
1060
|
-
) ->
|
1452
|
+
) -> Order:
|
1061
1453
|
"""
|
1062
1454
|
Sends a market-order like limit price based on the BBO.
|
1063
1455
|
Meant to behave as a market order but with more protections.
|
1064
1456
|
|
1065
1457
|
Args:
|
1458
|
+
id: in case user wants to generate their own order id, otherwise it will be generated automatically
|
1066
1459
|
symbol: the symbol to send the order for
|
1067
1460
|
execution_venue: the execution venue to send the order to
|
1068
1461
|
odir: the direction of the order
|
@@ -1078,36 +1471,33 @@ class AsyncClient:
|
|
1078
1471
|
If the order is rejected, the order.reject_reason and order.reject_message will be set
|
1079
1472
|
"""
|
1080
1473
|
|
1081
|
-
|
1082
|
-
|
1083
|
-
symbol=symbol, venue=execution_venue
|
1084
|
-
)
|
1085
|
-
if bbo_snapshot is None:
|
1474
|
+
ticker = await self.get_ticker(symbol, execution_venue)
|
1475
|
+
if ticker is None:
|
1086
1476
|
raise ValueError(
|
1087
|
-
f"Failed to send market order with reason: no
|
1477
|
+
f"Failed to send market order with reason: no ticker for {symbol}"
|
1088
1478
|
)
|
1089
1479
|
|
1090
1480
|
price_band = price_band_pairs.get(symbol, None)
|
1091
1481
|
|
1092
1482
|
if odir == OrderDir.BUY:
|
1093
|
-
if
|
1483
|
+
if ticker.ask_price is None:
|
1094
1484
|
raise ValueError(
|
1095
1485
|
f"Failed to send market order with reason: no ask price for {symbol}"
|
1096
1486
|
)
|
1097
|
-
limit_price =
|
1487
|
+
limit_price = ticker.ask_price * (1 + fraction_through_market)
|
1098
1488
|
|
1099
|
-
if price_band and
|
1100
|
-
price_band_reference_price =
|
1489
|
+
if price_band and ticker.last_price:
|
1490
|
+
price_band_reference_price = ticker.last_price + price_band
|
1101
1491
|
limit_price = min(limit_price, price_band_reference_price)
|
1102
1492
|
|
1103
1493
|
else:
|
1104
|
-
if
|
1494
|
+
if ticker.bid_price is None:
|
1105
1495
|
raise ValueError(
|
1106
1496
|
f"Failed to send market order with reason: no bid price for {symbol}"
|
1107
1497
|
)
|
1108
|
-
limit_price =
|
1109
|
-
if price_band and
|
1110
|
-
price_band_reference_price =
|
1498
|
+
limit_price = ticker.bid_price * (1 - fraction_through_market)
|
1499
|
+
if price_band and ticker.last_price:
|
1500
|
+
price_band_reference_price = ticker.last_price - price_band
|
1111
1501
|
limit_price = min(limit_price, price_band_reference_price)
|
1112
1502
|
|
1113
1503
|
# Conservatively round price to nearest tick
|
@@ -1125,7 +1515,7 @@ class AsyncClient:
|
|
1125
1515
|
):
|
1126
1516
|
limit_price = tick_round_method(limit_price, tick_size)
|
1127
1517
|
|
1128
|
-
return await self.
|
1518
|
+
return await self.place_limit_order(
|
1129
1519
|
id=id,
|
1130
1520
|
symbol=symbol,
|
1131
1521
|
execution_venue=execution_venue,
|
@@ -1137,7 +1527,7 @@ class AsyncClient:
|
|
1137
1527
|
time_in_force=time_in_force,
|
1138
1528
|
)
|
1139
1529
|
|
1140
|
-
async def cancel_order(self, order_id:
|
1530
|
+
async def cancel_order(self, order_id: OrderId) -> Cancel:
|
1141
1531
|
"""
|
1142
1532
|
Cancels an order by order id.
|
1143
1533
|
|
@@ -1146,10 +1536,17 @@ class AsyncClient:
|
|
1146
1536
|
Returns:
|
1147
1537
|
the CancelFields object
|
1148
1538
|
"""
|
1149
|
-
|
1150
|
-
|
1539
|
+
grpc_client = await self.core()
|
1540
|
+
req = CancelOrderRequest(id=order_id)
|
1541
|
+
res = await grpc_client.unary_unary(req)
|
1542
|
+
return res
|
1151
1543
|
|
1152
|
-
async def cancel_all_orders(
|
1544
|
+
async def cancel_all_orders(
|
1545
|
+
self,
|
1546
|
+
account: Optional[AccountIdOrName] = None,
|
1547
|
+
execution_venue: Optional[str] = None,
|
1548
|
+
trader: Optional[TraderIdOrEmail] = None,
|
1549
|
+
) -> bool:
|
1153
1550
|
"""
|
1154
1551
|
Cancels all open orders.
|
1155
1552
|
|
@@ -1157,5 +1554,25 @@ class AsyncClient:
|
|
1157
1554
|
True if all orders were cancelled successfully
|
1158
1555
|
False if there was an error
|
1159
1556
|
"""
|
1160
|
-
|
1161
|
-
|
1557
|
+
|
1558
|
+
open_orders = await self.get_open_orders(
|
1559
|
+
account=account,
|
1560
|
+
venue=execution_venue,
|
1561
|
+
trader=trader,
|
1562
|
+
)
|
1563
|
+
outputs = await asyncio.gather(
|
1564
|
+
*(self.cancel_order(order.id) for order in open_orders)
|
1565
|
+
)
|
1566
|
+
|
1567
|
+
for cancel in outputs:
|
1568
|
+
if cancel.reject_reason is not None:
|
1569
|
+
return False
|
1570
|
+
return True
|
1571
|
+
grpc_client = await self.core()
|
1572
|
+
req = CancelAllOrdersRequest(
|
1573
|
+
account=account,
|
1574
|
+
execution_venue=execution_venue,
|
1575
|
+
trader=trader,
|
1576
|
+
)
|
1577
|
+
res = await grpc_client.unary_unary(req)
|
1578
|
+
return res
|