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.
Files changed (205) hide show
  1. architect_py/__init__.py +15 -2
  2. architect_py/async_client.py +1060 -643
  3. architect_py/client.py +25 -26
  4. architect_py/client_interface.py +63 -0
  5. architect_py/common_types/__init__.py +6 -0
  6. architect_py/common_types/order_dir.py +91 -0
  7. architect_py/common_types/scalars.py +25 -0
  8. architect_py/common_types/tradable_product.py +59 -0
  9. architect_py/graphql_client/__init__.py +2 -0
  10. architect_py/graphql_client/client.py +3 -6
  11. architect_py/graphql_client/enums.py +5 -0
  12. architect_py/graphql_client/fragments.py +3 -6
  13. architect_py/graphql_client/get_fills_query.py +2 -1
  14. architect_py/graphql_client/search_symbols_query.py +2 -1
  15. architect_py/graphql_client/subscribe_orderflow.py +2 -1
  16. architect_py/graphql_client/subscribe_trades.py +2 -1
  17. architect_py/grpc/__init__.py +143 -0
  18. architect_py/grpc/client.py +94 -0
  19. architect_py/{grpc_client → grpc/models}/Accounts/AccountsRequest.py +6 -3
  20. architect_py/{grpc_client → grpc/models}/Accounts/AccountsResponse.py +1 -1
  21. architect_py/{grpc_client → grpc/models}/Accounts/__init__.py +1 -1
  22. architect_py/grpc/models/Algo/AlgoOrder.py +114 -0
  23. architect_py/grpc/models/Algo/AlgoOrderRequest.py +46 -0
  24. architect_py/grpc/models/Algo/AlgoOrdersRequest.py +72 -0
  25. architect_py/grpc/models/Algo/AlgoOrdersResponse.py +27 -0
  26. architect_py/grpc/models/Algo/CreateAlgoOrderRequest.py +56 -0
  27. architect_py/grpc/models/Algo/PauseAlgoRequest.py +42 -0
  28. architect_py/grpc/models/Algo/PauseAlgoResponse.py +20 -0
  29. architect_py/grpc/models/Algo/StartAlgoRequest.py +42 -0
  30. architect_py/grpc/models/Algo/StartAlgoResponse.py +20 -0
  31. architect_py/grpc/models/Algo/StopAlgoRequest.py +42 -0
  32. architect_py/grpc/models/Algo/StopAlgoResponse.py +20 -0
  33. architect_py/{grpc_client → grpc/models}/Algo/__init__.py +1 -1
  34. architect_py/grpc/models/Auth/CreateJwtRequest.py +47 -0
  35. architect_py/grpc/models/Auth/CreateJwtResponse.py +23 -0
  36. architect_py/{grpc_client/Cpty → grpc/models/Auth}/__init__.py +1 -1
  37. architect_py/grpc/models/Boss/DepositsRequest.py +40 -0
  38. architect_py/grpc/models/Boss/DepositsResponse.py +27 -0
  39. architect_py/grpc/models/Boss/RqdAccountStatisticsRequest.py +42 -0
  40. architect_py/grpc/models/Boss/RqdAccountStatisticsResponse.py +25 -0
  41. architect_py/grpc/models/Boss/StatementUrlRequest.py +40 -0
  42. architect_py/grpc/models/Boss/StatementUrlResponse.py +23 -0
  43. architect_py/grpc/models/Boss/StatementsRequest.py +40 -0
  44. architect_py/grpc/models/Boss/StatementsResponse.py +27 -0
  45. architect_py/grpc/models/Boss/WithdrawalsRequest.py +40 -0
  46. architect_py/grpc/models/Boss/WithdrawalsResponse.py +27 -0
  47. architect_py/{grpc_client/Folio → grpc/models/Boss}/__init__.py +1 -1
  48. architect_py/grpc/models/Core/ConfigRequest.py +37 -0
  49. architect_py/grpc/models/Core/ConfigResponse.py +25 -0
  50. architect_py/grpc/models/Core/__init__.py +2 -0
  51. architect_py/{grpc_client → grpc/models}/Cpty/CptyRequest.py +2 -2
  52. architect_py/{grpc_client → grpc/models}/Cpty/CptyResponse.py +3 -3
  53. architect_py/{grpc_client → grpc/models}/Cpty/CptyStatus.py +1 -1
  54. architect_py/{grpc_client → grpc/models}/Cpty/CptyStatusRequest.py +2 -2
  55. architect_py/{grpc_client → grpc/models}/Cpty/CptysRequest.py +2 -2
  56. architect_py/{grpc_client → grpc/models}/Cpty/CptysResponse.py +1 -1
  57. architect_py/grpc/models/Cpty/__init__.py +2 -0
  58. architect_py/{grpc_client → grpc/models}/Folio/AccountHistoryRequest.py +2 -2
  59. architect_py/{grpc_client → grpc/models}/Folio/AccountHistoryResponse.py +1 -1
  60. architect_py/{grpc_client → grpc/models}/Folio/AccountSummariesRequest.py +2 -2
  61. architect_py/{grpc_client → grpc/models}/Folio/AccountSummariesResponse.py +1 -1
  62. architect_py/{grpc_client → grpc/models}/Folio/AccountSummary.py +1 -1
  63. architect_py/{grpc_client → grpc/models}/Folio/AccountSummaryRequest.py +2 -2
  64. architect_py/{grpc_client → grpc/models}/Folio/HistoricalFillsRequest.py +7 -4
  65. architect_py/{grpc_client → grpc/models}/Folio/HistoricalFillsResponse.py +1 -1
  66. architect_py/{grpc_client → grpc/models}/Folio/HistoricalOrdersRequest.py +3 -3
  67. architect_py/{grpc_client → grpc/models}/Folio/HistoricalOrdersResponse.py +1 -1
  68. architect_py/grpc/models/Folio/__init__.py +2 -0
  69. architect_py/{grpc_client → grpc/models}/Health/HealthCheckRequest.py +2 -2
  70. architect_py/{grpc_client → grpc/models}/Health/HealthCheckResponse.py +1 -1
  71. architect_py/grpc/models/Health/__init__.py +2 -0
  72. architect_py/{grpc_client → grpc/models}/Marketdata/Candle.py +1 -1
  73. architect_py/{grpc_client → grpc/models}/Marketdata/HistoricalCandlesRequest.py +11 -8
  74. architect_py/{grpc_client → grpc/models}/Marketdata/HistoricalCandlesResponse.py +1 -1
  75. architect_py/{grpc_client → grpc/models}/Marketdata/L1BookSnapshot.py +52 -5
  76. architect_py/{grpc_client → grpc/models}/Marketdata/L1BookSnapshotRequest.py +8 -3
  77. architect_py/{grpc_client → grpc/models}/Marketdata/L1BookSnapshotsRequest.py +6 -3
  78. architect_py/{grpc_client → grpc/models}/Marketdata/L2BookSnapshot.py +1 -1
  79. architect_py/{grpc_client → grpc/models}/Marketdata/L2BookSnapshotRequest.py +2 -2
  80. architect_py/{grpc_client → grpc/models}/Marketdata/Liquidation.py +2 -2
  81. architect_py/{grpc_client → grpc/models}/Marketdata/MarketStatus.py +1 -1
  82. architect_py/{grpc_client → grpc/models}/Marketdata/MarketStatusRequest.py +2 -2
  83. architect_py/{grpc_client → grpc/models}/Marketdata/SubscribeCandlesRequest.py +2 -2
  84. architect_py/{grpc_client → grpc/models}/Marketdata/SubscribeCurrentCandlesRequest.py +3 -4
  85. architect_py/{grpc_client → grpc/models}/Marketdata/SubscribeL1BookSnapshotsRequest.py +6 -3
  86. architect_py/{grpc_client → grpc/models}/Marketdata/SubscribeL2BookUpdatesRequest.py +2 -2
  87. architect_py/{grpc_client → grpc/models}/Marketdata/SubscribeLiquidationsRequest.py +2 -2
  88. architect_py/{grpc_client → grpc/models}/Marketdata/SubscribeManyCandlesRequest.py +2 -2
  89. architect_py/{grpc_client → grpc/models}/Marketdata/SubscribeTickersRequest.py +2 -2
  90. architect_py/{grpc_client → grpc/models}/Marketdata/SubscribeTradesRequest.py +2 -2
  91. architect_py/{grpc_client → grpc/models}/Marketdata/Ticker.py +1 -1
  92. architect_py/{grpc_client → grpc/models}/Marketdata/TickerRequest.py +2 -2
  93. architect_py/{grpc_client → grpc/models}/Marketdata/TickersRequest.py +2 -2
  94. architect_py/{grpc_client → grpc/models}/Marketdata/TickersResponse.py +1 -1
  95. architect_py/{grpc_client → grpc/models}/Marketdata/Trade.py +2 -2
  96. architect_py/grpc/models/Marketdata/__init__.py +2 -0
  97. architect_py/grpc/models/Oms/Cancel.py +90 -0
  98. architect_py/{grpc_client → grpc/models}/Oms/CancelAllOrdersRequest.py +2 -2
  99. architect_py/{grpc_client → grpc/models}/Oms/CancelAllOrdersResponse.py +1 -1
  100. architect_py/{grpc_client → grpc/models}/Oms/CancelOrderRequest.py +2 -2
  101. architect_py/{grpc_client → grpc/models}/Oms/OpenOrdersRequest.py +2 -2
  102. architect_py/{grpc_client → grpc/models}/Oms/OpenOrdersResponse.py +1 -1
  103. architect_py/{grpc_client → grpc/models}/Oms/Order.py +6 -13
  104. architect_py/{grpc_client → grpc/models}/Oms/PendingCancelsRequest.py +2 -2
  105. architect_py/{grpc_client → grpc/models}/Oms/PendingCancelsResponse.py +1 -1
  106. architect_py/{grpc_client → grpc/models}/Oms/PlaceOrderRequest.py +16 -23
  107. architect_py/grpc/models/Oms/__init__.py +2 -0
  108. architect_py/grpc/models/OptionsMarketdata/OptionsChain.py +30 -0
  109. architect_py/grpc/models/OptionsMarketdata/OptionsChainGreeks.py +30 -0
  110. architect_py/grpc/models/OptionsMarketdata/OptionsChainGreeksRequest.py +47 -0
  111. architect_py/grpc/models/OptionsMarketdata/OptionsChainRequest.py +45 -0
  112. architect_py/grpc/models/OptionsMarketdata/OptionsExpirations.py +29 -0
  113. architect_py/grpc/models/OptionsMarketdata/OptionsExpirationsRequest.py +42 -0
  114. architect_py/grpc/models/OptionsMarketdata/__init__.py +2 -0
  115. architect_py/{grpc_client → grpc/models}/Orderflow/DropcopyRequest.py +2 -2
  116. architect_py/{grpc_client → grpc/models}/Orderflow/OrderflowRequest.py +1 -1
  117. architect_py/{grpc_client → grpc/models}/Orderflow/SubscribeOrderflowRequest.py +2 -2
  118. architect_py/grpc/models/Orderflow/__init__.py +2 -0
  119. architect_py/grpc/models/Symbology/DownloadProductCatalogRequest.py +42 -0
  120. architect_py/grpc/models/Symbology/DownloadProductCatalogResponse.py +27 -0
  121. architect_py/grpc/models/Symbology/ExecutionInfoRequest.py +47 -0
  122. architect_py/grpc/models/Symbology/ExecutionInfoResponse.py +27 -0
  123. architect_py/{grpc_client → grpc/models}/Symbology/PruneExpiredSymbolsRequest.py +2 -2
  124. architect_py/{grpc_client → grpc/models}/Symbology/PruneExpiredSymbolsResponse.py +1 -1
  125. architect_py/{grpc_client → grpc/models}/Symbology/SubscribeSymbology.py +1 -1
  126. architect_py/{grpc_client → grpc/models}/Symbology/SymbologyRequest.py +2 -2
  127. architect_py/{grpc_client → grpc/models}/Symbology/SymbologySnapshot.py +7 -2
  128. architect_py/{grpc_client → grpc/models}/Symbology/SymbologyUpdate.py +9 -2
  129. architect_py/{grpc_client → grpc/models}/Symbology/SymbolsRequest.py +2 -2
  130. architect_py/{grpc_client → grpc/models}/Symbology/SymbolsResponse.py +1 -1
  131. architect_py/grpc/models/Symbology/UploadProductCatalogRequest.py +49 -0
  132. architect_py/grpc/models/Symbology/UploadProductCatalogResponse.py +20 -0
  133. architect_py/{grpc_client → grpc/models}/Symbology/UploadSymbologyRequest.py +2 -2
  134. architect_py/{grpc_client → grpc/models}/Symbology/UploadSymbologyResponse.py +1 -1
  135. architect_py/grpc/models/Symbology/__init__.py +2 -0
  136. architect_py/grpc/models/__init__.py +2 -0
  137. architect_py/{grpc_client → grpc/models}/definitions.py +690 -841
  138. architect_py/grpc/resolve_endpoint.py +70 -0
  139. architect_py/{grpc_client/grpc_server.py → grpc/server.py} +9 -6
  140. architect_py/grpc/utils.py +32 -0
  141. architect_py/internal_utils/__init__.py +0 -0
  142. architect_py/internal_utils/no_pandas.py +3 -0
  143. architect_py/tests/conftest.py +91 -87
  144. architect_py/tests/test_book_building.py +49 -50
  145. architect_py/tests/test_marketdata.py +168 -0
  146. architect_py/tests/test_order_entry.py +37 -0
  147. architect_py/tests/test_orderflow.py +41 -0
  148. architect_py/tests/test_portfolio_management.py +23 -0
  149. architect_py/tests/test_rounding.py +28 -28
  150. architect_py/tests/test_symbology.py +37 -30
  151. architect_py/utils/nearest_tick.py +2 -5
  152. architect_py/utils/nearest_tick_2.py +1 -2
  153. architect_py/utils/orderbook.py +35 -0
  154. architect_py/utils/pandas.py +44 -0
  155. architect_py/utils/price_bands.py +0 -3
  156. architect_py/utils/symbol_parsing.py +29 -0
  157. architect_py-5.0.0b2.dist-info/METADATA +123 -0
  158. architect_py-5.0.0b2.dist-info/RECORD +214 -0
  159. {architect_py-3.2.2.dist-info → architect_py-5.0.0b2.dist-info}/WHEEL +2 -1
  160. architect_py-5.0.0b2.dist-info/top_level.txt +4 -0
  161. examples/__init__.py +0 -0
  162. examples/book_subscription.py +52 -0
  163. examples/candles.py +30 -0
  164. examples/common.py +116 -0
  165. examples/external_cpty.py +77 -0
  166. examples/funding_rate_mean_reversion_algo.py +186 -0
  167. examples/order_sending.py +91 -0
  168. examples/stream_l1_marketdata.py +25 -0
  169. examples/stream_l2_marketdata.py +38 -0
  170. examples/trades.py +21 -0
  171. examples/tutorial_async.py +86 -0
  172. examples/tutorial_sync.py +95 -0
  173. scripts/generate_functions_md.py +166 -0
  174. scripts/generate_sync_interface.py +226 -0
  175. scripts/postprocess_grpc.py +604 -0
  176. scripts/preprocess_grpc_schema.py +708 -0
  177. templates/exceptions.py +83 -0
  178. templates/juniper_base_client.py +371 -0
  179. architect_py/client_protocol.py +0 -53
  180. architect_py/grpc_client/Algo/AlgoOrderForTwapAlgo.py +0 -61
  181. architect_py/grpc_client/Algo/CreateAlgoOrderRequestForTwapAlgo.py +0 -59
  182. architect_py/grpc_client/Algo/ModifyAlgoOrderRequestForTwapAlgo.py +0 -45
  183. architect_py/grpc_client/Health/__init__.py +0 -2
  184. architect_py/grpc_client/Marketdata/__init__.py +0 -2
  185. architect_py/grpc_client/Oms/Cancel.py +0 -42
  186. architect_py/grpc_client/Oms/__init__.py +0 -2
  187. architect_py/grpc_client/Orderflow/__init__.py +0 -2
  188. architect_py/grpc_client/Symbology/__init__.py +0 -2
  189. architect_py/grpc_client/__init__.py +0 -2
  190. architect_py/grpc_client/grpc_client.py +0 -413
  191. architect_py/scalars.py +0 -172
  192. architect_py/tests/test_accounts.py +0 -31
  193. architect_py/tests/test_client.py +0 -29
  194. architect_py/tests/test_grpc_client.py +0 -30
  195. architect_py/tests/test_order_sending.py +0 -65
  196. architect_py/tests/test_snapshots.py +0 -52
  197. architect_py/tests/test_subscriptions.py +0 -126
  198. architect_py-3.2.2.dist-info/METADATA +0 -191
  199. architect_py-3.2.2.dist-info/RECORD +0 -148
  200. /architect_py/{grpc_client → grpc/models}/Marketdata/ArrayOfL1BookSnapshot.py +0 -0
  201. /architect_py/{grpc_client → grpc/models}/Marketdata/L2BookUpdate.py +0 -0
  202. /architect_py/{grpc_client → grpc/models}/Marketdata/TickerUpdate.py +0 -0
  203. /architect_py/{grpc_client → grpc/models}/Orderflow/Dropcopy.py +0 -0
  204. /architect_py/{grpc_client → grpc/models}/Orderflow/Orderflow.py +0 -0
  205. {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
- # Example usage
8
- value = Decimal("123.454")
9
- tick_size = Decimal("0.01")
6
+ # def test_rounding():
7
+ # # Example usage
8
+ # value = Decimal("123.454")
9
+ # tick_size = Decimal("0.01")
10
10
 
11
- rounded_value = TickRoundMethod.ROUND(value, tick_size=tick_size)
12
- assert rounded_value == Decimal("123.45")
11
+ # rounded_value = TickRoundMethod.ROUND(value, tick_size=tick_size)
12
+ # assert rounded_value == Decimal("123.45")
13
13
 
14
- rounded_ceil = TickRoundMethod.CEIL(value, tick_size)
15
- assert rounded_ceil == Decimal("123.46")
14
+ # rounded_ceil = TickRoundMethod.CEIL(value, tick_size)
15
+ # assert rounded_ceil == Decimal("123.46")
16
16
 
17
- rounded_floor = TickRoundMethod.FLOOR(value, tick_size)
18
- assert rounded_floor == Decimal("123.45")
17
+ # rounded_floor = TickRoundMethod.FLOOR(value, tick_size)
18
+ # assert rounded_floor == Decimal("123.45")
19
19
 
20
- rounded_floor = TickRoundMethod.FLOOR(Decimal("123.459"), tick_size)
21
- assert rounded_floor == Decimal("123.45")
20
+ # rounded_floor = TickRoundMethod.FLOOR(Decimal("123.459"), tick_size)
21
+ # assert rounded_floor == Decimal("123.45")
22
22
 
23
- rounded_toward_zero_pos = TickRoundMethod.TOWARD_ZERO(value, tick_size)
24
- assert rounded_toward_zero_pos == Decimal("123.45")
23
+ # rounded_toward_zero_pos = TickRoundMethod.TOWARD_ZERO(value, tick_size)
24
+ # assert rounded_toward_zero_pos == Decimal("123.45")
25
25
 
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")
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
- rounded_away_from_zero_pos = TickRoundMethod.AWAY_FROM_ZERO(value, tick_size)
31
- assert rounded_away_from_zero_pos == Decimal("123.46")
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
- 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")
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
- test_rounding()
41
- print("rounding.py: All tests passed!")
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
- from architect_py.async_client import AsyncClient
5
+
6
+ from architect_py import AsyncClient
6
7
 
7
8
 
8
9
  @pytest.mark.asyncio
9
- async def test_futures_series_populated(async_client: AsyncClient):
10
- # list of popular CME futures series and the minimum
11
- # number of futures we expect to see per series
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(futures) > min_count
20
- ), f"not enough futures markets found in {series} series: {len(markets)}"
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 test_search_for_es_front_month(async_client: AsyncClient):
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
- r"ES \d* CME Future", front_month_future
30
- ), "front month future base name does not match regex"
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
- assert re.match(
46
- f"BTC {year}{month:02d}[0-9]{{2}} CME Future", future
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:
@@ -8,14 +8,13 @@ nearest_tick(123.456, TickRoundMethod.ROUND, tick_size)
8
8
  """
9
9
 
10
10
  from decimal import (
11
- Decimal,
12
11
  ROUND_CEILING,
13
12
  ROUND_DOWN,
14
13
  ROUND_FLOOR,
15
14
  ROUND_HALF_UP,
16
15
  ROUND_UP,
16
+ Decimal,
17
17
  )
18
-
19
18
  from enum import Enum, member
20
19
 
21
20
 
@@ -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
+ # [![Architect](https://avatars.githubusercontent.com/u/116864654?s=29&v=2)](https://architect.co) architect_py
24
+ [![PyPI version](https://img.shields.io/pypi/v/architect-py.svg)](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.