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
@@ -0,0 +1,41 @@
|
|
1
|
+
"""
|
2
|
+
import asyncio
|
3
|
+
|
4
|
+
import pytest
|
5
|
+
|
6
|
+
from architect_py.async_client import AsyncClient
|
7
|
+
from architect_py.grpc.models.Orderflow.OrderflowRequest import OrderflowRequest
|
8
|
+
|
9
|
+
|
10
|
+
class OrderflowAsyncIterator:
|
11
|
+
queue: list[OrderflowRequest]
|
12
|
+
condition: asyncio.Condition
|
13
|
+
|
14
|
+
def __init__(self):
|
15
|
+
self.queue: list[OrderflowRequest] = []
|
16
|
+
self.condition: asyncio.Condition = asyncio.Condition()
|
17
|
+
|
18
|
+
def __aiter__(self):
|
19
|
+
return self
|
20
|
+
|
21
|
+
async def __anext__(self) -> OrderflowRequest:
|
22
|
+
async with self.condition:
|
23
|
+
while not self.queue:
|
24
|
+
await self.condition.wait()
|
25
|
+
return self.queue.pop(0)
|
26
|
+
|
27
|
+
async def add_to_queue(self, item: OrderflowRequest):
|
28
|
+
async with self.condition:
|
29
|
+
self.queue.append(item)
|
30
|
+
self.condition.notify()
|
31
|
+
|
32
|
+
|
33
|
+
@pytest.mark.asyncio
|
34
|
+
@pytest.mark.timeout(10)
|
35
|
+
async def test_orderflow(async_client: AsyncClient):
|
36
|
+
oai = OrderflowAsyncIterator()
|
37
|
+
async for of in async_client.orderflow(oai):
|
38
|
+
assert of is not None
|
39
|
+
return
|
40
|
+
|
41
|
+
"""
|
@@ -0,0 +1,23 @@
|
|
1
|
+
import pytest
|
2
|
+
|
3
|
+
from architect_py import AsyncClient
|
4
|
+
|
5
|
+
|
6
|
+
@pytest.mark.asyncio
|
7
|
+
async def test_list_accounts(async_client: AsyncClient):
|
8
|
+
accounts = await async_client.list_accounts()
|
9
|
+
assert accounts is not None
|
10
|
+
assert len(accounts) > 0
|
11
|
+
|
12
|
+
|
13
|
+
@pytest.mark.asyncio
|
14
|
+
async def test_get_account_summary(async_client: AsyncClient):
|
15
|
+
accounts = await async_client.list_accounts()
|
16
|
+
assert accounts is not None
|
17
|
+
assert len(accounts) > 0
|
18
|
+
|
19
|
+
summary = await async_client.get_account_summary(account=accounts[0].account.name)
|
20
|
+
assert summary is not None
|
21
|
+
assert summary.balances is not None
|
22
|
+
assert len(summary.balances) > 0
|
23
|
+
assert summary.positions is not None
|
@@ -1,41 +1,41 @@
|
|
1
|
-
from decimal import Decimal
|
1
|
+
# from decimal import Decimal
|
2
2
|
|
3
|
-
from architect_py.utils.nearest_tick import TickRoundMethod
|
3
|
+
# from architect_py.utils.nearest_tick import TickRoundMethod
|
4
4
|
|
5
5
|
|
6
|
-
def test_rounding():
|
7
|
-
|
8
|
-
|
9
|
-
|
6
|
+
# def test_rounding():
|
7
|
+
# # Example usage
|
8
|
+
# value = Decimal("123.454")
|
9
|
+
# tick_size = Decimal("0.01")
|
10
10
|
|
11
|
-
|
12
|
-
|
11
|
+
# rounded_value = TickRoundMethod.ROUND(value, tick_size=tick_size)
|
12
|
+
# assert rounded_value == Decimal("123.45")
|
13
13
|
|
14
|
-
|
15
|
-
|
14
|
+
# rounded_ceil = TickRoundMethod.CEIL(value, tick_size)
|
15
|
+
# assert rounded_ceil == Decimal("123.46")
|
16
16
|
|
17
|
-
|
18
|
-
|
17
|
+
# rounded_floor = TickRoundMethod.FLOOR(value, tick_size)
|
18
|
+
# assert rounded_floor == Decimal("123.45")
|
19
19
|
|
20
|
-
|
21
|
-
|
20
|
+
# rounded_floor = TickRoundMethod.FLOOR(Decimal("123.459"), tick_size)
|
21
|
+
# assert rounded_floor == Decimal("123.45")
|
22
22
|
|
23
|
-
|
24
|
-
|
23
|
+
# rounded_toward_zero_pos = TickRoundMethod.TOWARD_ZERO(value, tick_size)
|
24
|
+
# assert rounded_toward_zero_pos == Decimal("123.45")
|
25
25
|
|
26
|
-
|
27
|
-
|
28
|
-
|
26
|
+
# value_negative = Decimal("-123.456")
|
27
|
+
# rounded_toward_zero_neg = TickRoundMethod.TOWARD_ZERO(value_negative, tick_size)
|
28
|
+
# assert rounded_toward_zero_neg == Decimal("-123.45")
|
29
29
|
|
30
|
-
|
31
|
-
|
30
|
+
# rounded_away_from_zero_pos = TickRoundMethod.AWAY_FROM_ZERO(value, tick_size)
|
31
|
+
# assert rounded_away_from_zero_pos == Decimal("123.46")
|
32
32
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
33
|
+
# rounded_away_from_zero_neg = TickRoundMethod.AWAY_FROM_ZERO(
|
34
|
+
# value_negative, tick_size
|
35
|
+
# )
|
36
|
+
# assert rounded_away_from_zero_neg == Decimal("-123.46")
|
37
37
|
|
38
38
|
|
39
|
-
if __name__ == "__main__":
|
40
|
-
|
41
|
-
|
39
|
+
# if __name__ == "__main__":
|
40
|
+
# test_rounding()
|
41
|
+
# print("rounding.py: All tests passed!")
|
@@ -2,32 +2,55 @@ import re
|
|
2
2
|
from datetime import datetime
|
3
3
|
|
4
4
|
import pytest
|
5
|
-
|
5
|
+
|
6
|
+
from architect_py import AsyncClient
|
6
7
|
|
7
8
|
|
8
9
|
@pytest.mark.asyncio
|
9
|
-
async def
|
10
|
-
|
11
|
-
|
10
|
+
async def test_list_symbols(async_client: AsyncClient):
|
11
|
+
symbols = await async_client.list_symbols()
|
12
|
+
assert len(symbols) > 0, "no symbols found"
|
13
|
+
|
14
|
+
|
15
|
+
@pytest.mark.asyncio
|
16
|
+
async def test_search_symbols_for_popular_CME_futures(async_client: AsyncClient):
|
17
|
+
"""
|
18
|
+
Test that we have a minimum expected number of futures
|
19
|
+
for popular CME series.
|
20
|
+
"""
|
12
21
|
popular_series = [("ES", 5), ("GC", 5), ("NQ", 5)]
|
13
22
|
for series, min_count in popular_series:
|
14
23
|
markets = await async_client.search_symbols(
|
15
24
|
execution_venue="CME", search_string=series
|
16
25
|
)
|
17
26
|
futures = [market for market in markets if market.startswith(f"{series} ")]
|
18
|
-
assert (
|
19
|
-
len(
|
20
|
-
)
|
27
|
+
assert len(futures) > min_count, (
|
28
|
+
f"not enough futures markets found in {series} series: {len(markets)}"
|
29
|
+
)
|
30
|
+
|
31
|
+
|
32
|
+
@pytest.mark.asyncio
|
33
|
+
async def test_cme_first_notice_date(async_client: AsyncClient):
|
34
|
+
# Find the nearest GC futures contract and check the first notice date
|
35
|
+
series_name = "GC CME Futures"
|
36
|
+
series = await async_client.get_futures_series(series_name)
|
37
|
+
assert len(series) > 0, "no futures markets found in GC series"
|
38
|
+
|
39
|
+
futures = await async_client.get_cme_futures_series(series_name)
|
40
|
+
exp_date, future = futures[0]
|
41
|
+
notice_date = await async_client.get_cme_first_notice_date(future)
|
42
|
+
assert notice_date is not None, "first notice date is None"
|
43
|
+
assert notice_date < exp_date, "first notice date is not before expiration"
|
21
44
|
|
22
45
|
|
23
46
|
@pytest.mark.asyncio
|
24
|
-
async def
|
47
|
+
async def test_get_cme_futures_series(async_client: AsyncClient):
|
25
48
|
series = await async_client.get_cme_futures_series("ES CME Futures")
|
26
49
|
assert len(series) > 0, "no futures markets found in ES series"
|
27
50
|
_, front_month_future = series[0]
|
28
|
-
assert re.match(
|
29
|
-
|
30
|
-
)
|
51
|
+
assert re.match(r"ES \d* CME Future", front_month_future), (
|
52
|
+
"front month future base name does not match regex"
|
53
|
+
)
|
31
54
|
|
32
55
|
|
33
56
|
@pytest.mark.asyncio
|
@@ -35,16 +58,14 @@ async def test_get_cme_future_from_root_month_year(async_client: AsyncClient):
|
|
35
58
|
# BTC futures are monthly. To avoid end-of-month weekend expiration,
|
36
59
|
# check the next month from the current date.
|
37
60
|
now = add_one_month_to_datetime(datetime.now())
|
38
|
-
|
39
61
|
month = now.month
|
40
62
|
year = now.year
|
41
63
|
future = await async_client.get_cme_future_from_root_month_year(
|
42
64
|
"BTC", month=month, year=year
|
43
65
|
)
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
), "future base name does not match regex"
|
66
|
+
assert re.match(f"BTC {year}{month:02d}[0-9]{{2}} CME Future", future), (
|
67
|
+
"future base name does not match regex"
|
68
|
+
)
|
48
69
|
|
49
70
|
|
50
71
|
def add_one_month_to_datetime(dt: datetime):
|
@@ -52,17 +73,3 @@ def add_one_month_to_datetime(dt: datetime):
|
|
52
73
|
return dt.replace(year=dt.year + 1, month=1)
|
53
74
|
else:
|
54
75
|
return dt.replace(month=dt.month + 1)
|
55
|
-
|
56
|
-
|
57
|
-
@pytest.mark.asyncio
|
58
|
-
async def test_cme_first_notice_date(async_client: AsyncClient):
|
59
|
-
# Find the nearest GC futures contract and check the first notice date
|
60
|
-
series_name = "GC CME Futures"
|
61
|
-
series = await async_client.get_future_series(series_name)
|
62
|
-
assert len(series) > 0, "no futures markets found in GC series"
|
63
|
-
|
64
|
-
futures = await async_client.get_cme_futures_series(series_name)
|
65
|
-
exp_date, future = futures[0]
|
66
|
-
notice_date = await async_client.get_cme_first_notice_date(future)
|
67
|
-
assert notice_date is not None, "first notice date is None"
|
68
|
-
assert notice_date < exp_date, "first notice date is not before expiration"
|
@@ -5,12 +5,10 @@ tick_size = get_tick_size(market_id, client)
|
|
5
5
|
nearest_tick(123.456, TickRoundMethod.ROUND, tick_size)
|
6
6
|
"""
|
7
7
|
|
8
|
+
import sys
|
8
9
|
from enum import Enum
|
9
|
-
|
10
10
|
from functools import partial
|
11
11
|
|
12
|
-
import sys
|
13
|
-
|
14
12
|
"""
|
15
13
|
This conditional import is to deal with
|
16
14
|
FutureWarning: functools.partial will be a method descriptor in future Python versions; wrap it in enum.member() if you want to preserve the old behavior
|
@@ -19,14 +17,13 @@ given on Python 3.12+
|
|
19
17
|
if sys.version_info >= (3, 11):
|
20
18
|
from .nearest_tick_2 import *
|
21
19
|
else:
|
22
|
-
|
23
20
|
from decimal import (
|
24
|
-
Decimal,
|
25
21
|
ROUND_CEILING,
|
26
22
|
ROUND_DOWN,
|
27
23
|
ROUND_FLOOR,
|
28
24
|
ROUND_HALF_UP,
|
29
25
|
ROUND_UP,
|
26
|
+
Decimal,
|
30
27
|
)
|
31
28
|
|
32
29
|
def round_method(value: Decimal, tick_size: Decimal) -> Decimal:
|
@@ -0,0 +1,35 @@
|
|
1
|
+
import bisect
|
2
|
+
from decimal import Decimal
|
3
|
+
|
4
|
+
|
5
|
+
def update_orderbook_side(
|
6
|
+
orderbook_side: list[list[Decimal]],
|
7
|
+
price: Decimal,
|
8
|
+
size: Decimal,
|
9
|
+
ascending: bool,
|
10
|
+
) -> None:
|
11
|
+
"""
|
12
|
+
Updates a sorted order list (either ascending for asks or descending for bids)
|
13
|
+
using binary search to insert, update, or remove the given price level.
|
14
|
+
"""
|
15
|
+
if ascending:
|
16
|
+
idx = bisect.bisect_left(orderbook_side, [price, Decimal(0)])
|
17
|
+
else:
|
18
|
+
lo, hi = 0, len(orderbook_side)
|
19
|
+
while lo < hi:
|
20
|
+
mid = (lo + hi) // 2
|
21
|
+
if orderbook_side[mid][0] > price:
|
22
|
+
lo = mid + 1
|
23
|
+
else:
|
24
|
+
hi = mid
|
25
|
+
idx = lo
|
26
|
+
|
27
|
+
if idx < len(orderbook_side) and orderbook_side[idx][0] == price:
|
28
|
+
if size.is_zero():
|
29
|
+
orderbook_side.pop(idx)
|
30
|
+
else:
|
31
|
+
# Update the size.
|
32
|
+
orderbook_side[idx][1] = size
|
33
|
+
else:
|
34
|
+
if not size.is_zero():
|
35
|
+
orderbook_side.insert(idx, [price, size])
|
@@ -0,0 +1,44 @@
|
|
1
|
+
from typing import List
|
2
|
+
|
3
|
+
import msgspec
|
4
|
+
import pandas as pd
|
5
|
+
|
6
|
+
from ..grpc import Candle
|
7
|
+
|
8
|
+
CANDLES_FIELD_MAP = {
|
9
|
+
"av": "sell_volume",
|
10
|
+
"bv": "buy_volume",
|
11
|
+
"s": "symbol",
|
12
|
+
"v": "volume",
|
13
|
+
"w": "width",
|
14
|
+
"ac": "ask_close",
|
15
|
+
"ah": "ask_high",
|
16
|
+
"al": "ask_low",
|
17
|
+
"ao": "ask_open",
|
18
|
+
"bc": "bid_close",
|
19
|
+
"bh": "bid_high",
|
20
|
+
"bl": "bid_low",
|
21
|
+
"bo": "bid_open",
|
22
|
+
"c": "close",
|
23
|
+
"h": "high",
|
24
|
+
"l": "low",
|
25
|
+
"mc": "mid_close",
|
26
|
+
"mh": "mid_high",
|
27
|
+
"ml": "mid_low",
|
28
|
+
"mo": "mid_open",
|
29
|
+
"o": "open",
|
30
|
+
}
|
31
|
+
|
32
|
+
|
33
|
+
def candles_to_dataframe(candles: List[Candle]) -> pd.DataFrame:
|
34
|
+
records = msgspec.to_builtins(candles)
|
35
|
+
df = pd.DataFrame.from_records(records)
|
36
|
+
df.rename(columns=CANDLES_FIELD_MAP, inplace=True)
|
37
|
+
df["timestamp"] = pd.to_datetime(
|
38
|
+
df["ts"] * 1_000_000_000 + df["tn"],
|
39
|
+
unit="ns",
|
40
|
+
utc=True,
|
41
|
+
)
|
42
|
+
df.style.hide(["tn", "ts"], axis=1)
|
43
|
+
df.set_index("timestamp", inplace=True)
|
44
|
+
return df
|
@@ -73,7 +73,6 @@ price_band_pairs: dict[str, Decimal] = {
|
|
73
73
|
"ZW": Decimal(0.0975),
|
74
74
|
"XW": Decimal(0.0975),
|
75
75
|
"CWD": Decimal(600),
|
76
|
-
"CWD": Decimal(300),
|
77
76
|
"ZWT": Decimal(0.02),
|
78
77
|
"KE": Decimal(0.0975),
|
79
78
|
"KWD": Decimal(600),
|
@@ -384,7 +383,6 @@ price_band_pairs: dict[str, Decimal] = {
|
|
384
383
|
"XUB": Decimal(0.2),
|
385
384
|
"ACB": Decimal(0.2),
|
386
385
|
"AEB": Decimal(0.2),
|
387
|
-
"FRC": Decimal(0.2),
|
388
386
|
"TFB": Decimal(0.2),
|
389
387
|
"USE": Decimal(0.2),
|
390
388
|
"RBM": Decimal(2000),
|
@@ -481,7 +479,6 @@ price_band_pairs: dict[str, Decimal] = {
|
|
481
479
|
"AUF": Decimal(0.001),
|
482
480
|
"UV": Decimal(2),
|
483
481
|
"AKR": Decimal(0.001),
|
484
|
-
"AKR": Decimal(0.001),
|
485
482
|
"EFF": Decimal(0.001),
|
486
483
|
"AUI": Decimal(2),
|
487
484
|
"AGT": Decimal(0.01),
|
@@ -0,0 +1,29 @@
|
|
1
|
+
"""
|
2
|
+
Utility functions for decoding and understanding Architect symbols.
|
3
|
+
"""
|
4
|
+
|
5
|
+
from datetime import date, datetime
|
6
|
+
from typing import Optional
|
7
|
+
|
8
|
+
|
9
|
+
def nominative_expiration(symbol: str) -> Optional[date]:
|
10
|
+
"""
|
11
|
+
For futures and options symbols, extract the expiration date.
|
12
|
+
|
13
|
+
Args:
|
14
|
+
symbol: e.g. "ES 20211217 CME Future" -> date(2021, 12, 17)
|
15
|
+
|
16
|
+
Returns:
|
17
|
+
The expiration date as a date object
|
18
|
+
None if the symbol is not a future or option
|
19
|
+
|
20
|
+
To get a more precise expiration time or certain rare situations
|
21
|
+
involving timezone skews, use get_product_info from AsyncClient
|
22
|
+
or Client instead, which looks up actual product facts from the
|
23
|
+
symbology service.
|
24
|
+
"""
|
25
|
+
try:
|
26
|
+
_, d, *_ = symbol.split(" ")
|
27
|
+
return datetime.strptime(d, "%Y%m%d").date()
|
28
|
+
except ValueError:
|
29
|
+
return None
|
@@ -0,0 +1,123 @@
|
|
1
|
+
Metadata-Version: 2.4
|
2
|
+
Name: architect-py
|
3
|
+
Version: 5.0.0b2
|
4
|
+
Summary: Python SDK for the Architect trading platform and brokerage.
|
5
|
+
Author-email: "Architect Financial Technologies, Inc." <hello@architect.co>
|
6
|
+
License-Expression: Apache-2.0
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
8
|
+
Classifier: Operating System :: OS Independent
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
10
|
+
Requires-Python: <4,>=3.10
|
11
|
+
Description-Content-Type: text/markdown
|
12
|
+
License-File: LICENSE
|
13
|
+
Requires-Dist: asyncio>=3
|
14
|
+
Requires-Dist: gql[httpx]<4,>=3.5.0
|
15
|
+
Requires-Dist: grpcio>=1.66.1
|
16
|
+
Requires-Dist: msgspec<0.20,>=0.19
|
17
|
+
Requires-Dist: pandas>=2
|
18
|
+
Requires-Dist: pydantic~=2.10
|
19
|
+
Requires-Dist: websockets>=11
|
20
|
+
Requires-Dist: dnspython>=2.0
|
21
|
+
Dynamic: license-file
|
22
|
+
|
23
|
+
# [](https://architect.co) architect_py
|
24
|
+
[](https://pypi.org/project/architect-py/)
|
25
|
+
|
26
|
+
A fully-featured Python SDK for trading on [Architect](https://architect.co).
|
27
|
+
|
28
|
+
Just some of the features of this SDK: symbology, portfolio management, order entry, advanced algos, and marketdata subscriptions.
|
29
|
+
|
30
|
+
Also, it is compatible with Jupyter notebooks! Check the [examples for an example notebook](examples/jupyter_example.ipynb).
|
31
|
+
|
32
|
+
## Installation
|
33
|
+
|
34
|
+
- pip: `pip install architect-py`
|
35
|
+
- poetry: `poetry add architect-py`
|
36
|
+
- uv: `uv add architect-py`
|
37
|
+
|
38
|
+
## API keys for the brokerage
|
39
|
+
|
40
|
+
API keys/secrets for the brokerage can be generated on the [user account page](https://app.architect.co/user/account).
|
41
|
+
|
42
|
+
|
43
|
+
## Example usage
|
44
|
+
|
45
|
+
`AsyncClient` and `Client` are the entryways into making calls to the Architect backend.
|
46
|
+
Note that the sync `Client` does not have access to any subscription functions, because they are async by nature.
|
47
|
+
|
48
|
+
|
49
|
+
```python
|
50
|
+
import asyncio
|
51
|
+
|
52
|
+
from architect_py.async_client import AsyncClient
|
53
|
+
from architect_py.scalars import TradableProduct
|
54
|
+
|
55
|
+
async def main():
|
56
|
+
c = await AsyncClient.connect(
|
57
|
+
host="<your installation domain>", # e.g. app.architect.co for the brokerage
|
58
|
+
api_key="<api key>",
|
59
|
+
api_secret="<api secret>"
|
60
|
+
paper_trading=True,
|
61
|
+
)
|
62
|
+
print(await c.who_am_i())
|
63
|
+
|
64
|
+
series = await async_client.get_cme_futures_series("ES CME Futures")
|
65
|
+
front_ES_future = series[0][1]
|
66
|
+
|
67
|
+
s = c.subscribe_trades_stream(front_ES_future)
|
68
|
+
async for trade in s:
|
69
|
+
print(trade)
|
70
|
+
|
71
|
+
asyncio.run(main())
|
72
|
+
```
|
73
|
+
|
74
|
+
```python
|
75
|
+
from architect_py.client import Client
|
76
|
+
|
77
|
+
def main():
|
78
|
+
c = Client(
|
79
|
+
host="<your installation domain>",
|
80
|
+
api_key="<api key>",
|
81
|
+
api_secret="<api secret>"
|
82
|
+
paper_trading=True,
|
83
|
+
)
|
84
|
+
print(c.who_am_i())
|
85
|
+
|
86
|
+
print(client.get_account_summaries())
|
87
|
+
|
88
|
+
print(client.search_symbols("ES"))
|
89
|
+
```
|
90
|
+
|
91
|
+
While the AsyncClient is the recommended way to use the Architect API, the Client instead without any familiarity with `async/await`.
|
92
|
+
The sync clients and async clients usage is identical, except one removes the `await` before the call. The only exception to this is that the sync client does not support any subscriptions, because they are inherently asynchronous.
|
93
|
+
|
94
|
+
Check the `examples` folder or the `architect_py/tests` folders for example usages.
|
95
|
+
|
96
|
+
|
97
|
+
## Method catalog
|
98
|
+
|
99
|
+
Check out the [FUNCTIONS.md](FUNCTIONS.md) file to see a catalog of methods.
|
100
|
+
|
101
|
+
---
|
102
|
+
|
103
|
+
|
104
|
+
### Running examples from this package
|
105
|
+
|
106
|
+
Clone this repository to run examples in the `examples` directory. This package
|
107
|
+
uses poetry for dependency management. To enter a poetry virtual environment, make
|
108
|
+
sure you have [poetry](https://python-poetry.org/docs/) installed and run the
|
109
|
+
following from the repository root.
|
110
|
+
|
111
|
+
```bash
|
112
|
+
poetry shell
|
113
|
+
poetry install --sync
|
114
|
+
|
115
|
+
export ARCHITECT_HOST="<your installation domain>"
|
116
|
+
export ARCHITECT_API_KEY="<api key>"
|
117
|
+
export ARCHITECT_API_SECRET="<api secret>"
|
118
|
+
|
119
|
+
python -m examples.trades
|
120
|
+
```
|
121
|
+
|
122
|
+
You can exit the poetry shell by running `exit`. Environment variables set
|
123
|
+
within the shell are not persisted.
|