architect-py 3.2.1__py3-none-any.whl → 5.0.0b1__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 (171) hide show
  1. architect_py/__init__.py +8 -2
  2. architect_py/async_client.py +887 -575
  3. architect_py/client.py +36 -33
  4. architect_py/client_interface.py +62 -0
  5. architect_py/common_types/__init__.py +6 -0
  6. architect_py/common_types/order_dir.py +85 -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/client.py +3 -6
  10. architect_py/graphql_client/fragments.py +3 -6
  11. architect_py/graphql_client/get_fills_query.py +2 -1
  12. architect_py/graphql_client/search_symbols_query.py +2 -1
  13. architect_py/graphql_client/subscribe_orderflow.py +2 -1
  14. architect_py/graphql_client/subscribe_trades.py +2 -1
  15. architect_py/grpc/__init__.py +125 -0
  16. architect_py/grpc/client.py +86 -0
  17. architect_py/{grpc_client → grpc/models}/Accounts/AccountsRequest.py +2 -2
  18. architect_py/{grpc_client → grpc/models}/Accounts/AccountsResponse.py +1 -1
  19. architect_py/{grpc_client → grpc/models}/Accounts/__init__.py +1 -1
  20. architect_py/{grpc_client → grpc/models}/Algo/AlgoOrderForTwapAlgo.py +1 -1
  21. architect_py/{grpc_client → grpc/models}/Algo/CreateAlgoOrderRequestForTwapAlgo.py +2 -2
  22. architect_py/{grpc_client → grpc/models}/Algo/ModifyAlgoOrderRequestForTwapAlgo.py +2 -2
  23. architect_py/{grpc_client → grpc/models}/Algo/__init__.py +1 -1
  24. architect_py/grpc/models/Auth/CreateJwtRequest.py +47 -0
  25. architect_py/grpc/models/Auth/CreateJwtResponse.py +23 -0
  26. architect_py/{grpc_client/Cpty → grpc/models/Auth}/__init__.py +1 -1
  27. architect_py/grpc/models/Core/ConfigRequest.py +37 -0
  28. architect_py/grpc/models/Core/ConfigResponse.py +25 -0
  29. architect_py/{grpc_client/Folio → grpc/models/Core}/__init__.py +1 -1
  30. architect_py/{grpc_client → grpc/models}/Cpty/CptyRequest.py +5 -4
  31. architect_py/{grpc_client → grpc/models}/Cpty/CptyResponse.py +6 -6
  32. architect_py/grpc/models/Cpty/CptyStatus.py +48 -0
  33. architect_py/grpc/models/Cpty/CptyStatusRequest.py +45 -0
  34. architect_py/grpc/models/Cpty/CptysRequest.py +37 -0
  35. architect_py/grpc/models/Cpty/CptysResponse.py +27 -0
  36. architect_py/grpc/models/Cpty/__init__.py +2 -0
  37. architect_py/{grpc_client → grpc/models}/Folio/AccountHistoryRequest.py +2 -2
  38. architect_py/{grpc_client → grpc/models}/Folio/AccountHistoryResponse.py +1 -1
  39. architect_py/{grpc_client → grpc/models}/Folio/AccountSummariesRequest.py +2 -2
  40. architect_py/{grpc_client → grpc/models}/Folio/AccountSummariesResponse.py +1 -1
  41. architect_py/{grpc_client → grpc/models}/Folio/AccountSummary.py +1 -1
  42. architect_py/{grpc_client → grpc/models}/Folio/AccountSummaryRequest.py +2 -2
  43. architect_py/{grpc_client → grpc/models}/Folio/HistoricalFillsRequest.py +3 -3
  44. architect_py/{grpc_client → grpc/models}/Folio/HistoricalFillsResponse.py +1 -1
  45. architect_py/{grpc_client → grpc/models}/Folio/HistoricalOrdersRequest.py +3 -3
  46. architect_py/{grpc_client → grpc/models}/Folio/HistoricalOrdersResponse.py +1 -1
  47. architect_py/grpc/models/Folio/__init__.py +2 -0
  48. architect_py/{grpc_client → grpc/models}/Health/HealthCheckRequest.py +2 -2
  49. architect_py/{grpc_client → grpc/models}/Health/HealthCheckResponse.py +1 -1
  50. architect_py/grpc/models/Health/__init__.py +2 -0
  51. architect_py/{grpc_client → grpc/models}/Marketdata/Candle.py +1 -1
  52. architect_py/{grpc_client → grpc/models}/Marketdata/HistoricalCandlesRequest.py +11 -8
  53. architect_py/{grpc_client → grpc/models}/Marketdata/HistoricalCandlesResponse.py +1 -1
  54. architect_py/{grpc_client → grpc/models}/Marketdata/L1BookSnapshot.py +36 -3
  55. architect_py/{grpc_client → grpc/models}/Marketdata/L1BookSnapshotRequest.py +8 -3
  56. architect_py/{grpc_client → grpc/models}/Marketdata/L1BookSnapshotsRequest.py +6 -3
  57. architect_py/{grpc_client → grpc/models}/Marketdata/L2BookSnapshot.py +1 -1
  58. architect_py/{grpc_client → grpc/models}/Marketdata/L2BookSnapshotRequest.py +2 -2
  59. architect_py/{grpc_client → grpc/models}/Marketdata/Liquidation.py +2 -2
  60. architect_py/{grpc_client → grpc/models}/Marketdata/MarketStatus.py +1 -1
  61. architect_py/{grpc_client → grpc/models}/Marketdata/MarketStatusRequest.py +2 -2
  62. architect_py/{grpc_client → grpc/models}/Marketdata/SubscribeCandlesRequest.py +2 -2
  63. architect_py/{grpc_client → grpc/models}/Marketdata/SubscribeCurrentCandlesRequest.py +3 -4
  64. architect_py/{grpc_client → grpc/models}/Marketdata/SubscribeL1BookSnapshotsRequest.py +6 -3
  65. architect_py/{grpc_client → grpc/models}/Marketdata/SubscribeL2BookUpdatesRequest.py +2 -2
  66. architect_py/{grpc_client → grpc/models}/Marketdata/SubscribeLiquidationsRequest.py +2 -2
  67. architect_py/{grpc_client → grpc/models}/Marketdata/SubscribeManyCandlesRequest.py +2 -2
  68. architect_py/{grpc_client → grpc/models}/Marketdata/SubscribeTickersRequest.py +2 -2
  69. architect_py/{grpc_client → grpc/models}/Marketdata/SubscribeTradesRequest.py +2 -2
  70. architect_py/{grpc_client → grpc/models}/Marketdata/Ticker.py +14 -3
  71. architect_py/{grpc_client → grpc/models}/Marketdata/TickerRequest.py +2 -2
  72. architect_py/{grpc_client → grpc/models}/Marketdata/TickersRequest.py +2 -2
  73. architect_py/{grpc_client → grpc/models}/Marketdata/TickersResponse.py +1 -1
  74. architect_py/{grpc_client → grpc/models}/Marketdata/Trade.py +2 -2
  75. architect_py/grpc/models/Marketdata/__init__.py +2 -0
  76. architect_py/{grpc_client → grpc/models}/Oms/Cancel.py +1 -1
  77. architect_py/{grpc_client → grpc/models}/Oms/CancelAllOrdersRequest.py +2 -2
  78. architect_py/{grpc_client → grpc/models}/Oms/CancelAllOrdersResponse.py +1 -1
  79. architect_py/{grpc_client → grpc/models}/Oms/CancelOrderRequest.py +2 -2
  80. architect_py/{grpc_client → grpc/models}/Oms/OpenOrdersRequest.py +2 -2
  81. architect_py/{grpc_client → grpc/models}/Oms/OpenOrdersResponse.py +1 -1
  82. architect_py/{grpc_client → grpc/models}/Oms/Order.py +2 -2
  83. architect_py/{grpc_client → grpc/models}/Oms/PendingCancelsRequest.py +2 -2
  84. architect_py/{grpc_client → grpc/models}/Oms/PendingCancelsResponse.py +1 -1
  85. architect_py/{grpc_client → grpc/models}/Oms/PlaceOrderRequest.py +3 -3
  86. architect_py/grpc/models/Oms/__init__.py +2 -0
  87. architect_py/{grpc_client → grpc/models}/Orderflow/DropcopyRequest.py +2 -2
  88. architect_py/{grpc_client → grpc/models}/Orderflow/OrderflowRequest.py +2 -1
  89. architect_py/{grpc_client → grpc/models}/Orderflow/SubscribeOrderflowRequest.py +2 -2
  90. architect_py/grpc/models/Orderflow/__init__.py +2 -0
  91. architect_py/grpc/models/Symbology/DownloadProductCatalogRequest.py +42 -0
  92. architect_py/grpc/models/Symbology/DownloadProductCatalogResponse.py +27 -0
  93. architect_py/{grpc_client → grpc/models}/Symbology/PruneExpiredSymbolsRequest.py +2 -2
  94. architect_py/{grpc_client → grpc/models}/Symbology/PruneExpiredSymbolsResponse.py +1 -1
  95. architect_py/{grpc_client → grpc/models}/Symbology/SubscribeSymbology.py +1 -1
  96. architect_py/{grpc_client → grpc/models}/Symbology/SymbologyRequest.py +2 -2
  97. architect_py/{grpc_client → grpc/models}/Symbology/SymbologySnapshot.py +7 -2
  98. architect_py/{grpc_client → grpc/models}/Symbology/SymbologyUpdate.py +9 -2
  99. architect_py/{grpc_client → grpc/models}/Symbology/SymbolsRequest.py +2 -2
  100. architect_py/{grpc_client → grpc/models}/Symbology/SymbolsResponse.py +1 -1
  101. architect_py/grpc/models/Symbology/UploadProductCatalogRequest.py +49 -0
  102. architect_py/grpc/models/Symbology/UploadProductCatalogResponse.py +20 -0
  103. architect_py/{grpc_client → grpc/models}/Symbology/UploadSymbologyRequest.py +2 -2
  104. architect_py/{grpc_client → grpc/models}/Symbology/UploadSymbologyResponse.py +1 -1
  105. architect_py/grpc/models/Symbology/__init__.py +2 -0
  106. architect_py/grpc/models/__init__.py +2 -0
  107. architect_py/{grpc_client → grpc/models}/definitions.py +293 -223
  108. architect_py/grpc/resolve_endpoint.py +67 -0
  109. architect_py/{grpc_client/grpc_server.py → grpc/server.py} +13 -9
  110. architect_py/grpc/utils.py +32 -0
  111. architect_py/tests/conftest.py +86 -85
  112. architect_py/tests/test_book_building.py +49 -50
  113. architect_py/tests/test_marketdata.py +168 -0
  114. architect_py/tests/test_order_entry.py +37 -0
  115. architect_py/tests/test_orderflow.py +38 -0
  116. architect_py/tests/test_portfolio_management.py +23 -0
  117. architect_py/tests/test_rounding.py +28 -28
  118. architect_py/tests/test_symbology.py +37 -30
  119. architect_py/utils/nearest_tick.py +2 -5
  120. architect_py/utils/nearest_tick_2.py +1 -2
  121. architect_py/utils/orderbook.py +35 -0
  122. architect_py/utils/pandas.py +44 -0
  123. architect_py/utils/price_bands.py +0 -3
  124. architect_py/utils/symbol_parsing.py +29 -0
  125. architect_py-5.0.0b1.dist-info/METADATA +124 -0
  126. architect_py-5.0.0b1.dist-info/RECORD +184 -0
  127. {architect_py-3.2.1.dist-info → architect_py-5.0.0b1.dist-info}/WHEEL +2 -1
  128. architect_py-5.0.0b1.dist-info/top_level.txt +4 -0
  129. examples/__init__.py +0 -0
  130. examples/book_subscription.py +53 -0
  131. examples/candles.py +30 -0
  132. examples/common.py +107 -0
  133. examples/external_cpty.py +77 -0
  134. examples/funding_rate_mean_reversion_algo.py +192 -0
  135. examples/order_sending.py +92 -0
  136. examples/stream_l1_marketdata.py +25 -0
  137. examples/stream_l2_marketdata.py +40 -0
  138. examples/trades.py +21 -0
  139. examples/tutorial_async.py +84 -0
  140. examples/tutorial_sync.py +95 -0
  141. scripts/generate_functions_md.py +164 -0
  142. scripts/generate_sync_interface.py +207 -0
  143. scripts/postprocess_grpc.py +594 -0
  144. scripts/preprocess_grpc_schema.py +647 -0
  145. templates/exceptions.py +83 -0
  146. templates/juniper_base_client.py +371 -0
  147. architect_py/client_protocol.py +0 -52
  148. architect_py/grpc_client/Folio/AggregatedAccountSummariesRequest.py +0 -59
  149. architect_py/grpc_client/Folio/AggregatedAccountSummariesResponse.py +0 -27
  150. architect_py/grpc_client/Health/__init__.py +0 -2
  151. architect_py/grpc_client/Marketdata/__init__.py +0 -2
  152. architect_py/grpc_client/Oms/__init__.py +0 -2
  153. architect_py/grpc_client/Orderflow/__init__.py +0 -2
  154. architect_py/grpc_client/Symbology/__init__.py +0 -2
  155. architect_py/grpc_client/__init__.py +0 -2
  156. architect_py/grpc_client/grpc_client.py +0 -405
  157. architect_py/scalars.py +0 -172
  158. architect_py/tests/test_accounts.py +0 -31
  159. architect_py/tests/test_client.py +0 -29
  160. architect_py/tests/test_grpc_client.py +0 -30
  161. architect_py/tests/test_order_sending.py +0 -61
  162. architect_py/tests/test_snapshots.py +0 -52
  163. architect_py/tests/test_subscriptions.py +0 -129
  164. architect_py-3.2.1.dist-info/METADATA +0 -212
  165. architect_py-3.2.1.dist-info/RECORD +0 -146
  166. /architect_py/{grpc_client → grpc/models}/Marketdata/ArrayOfL1BookSnapshot.py +0 -0
  167. /architect_py/{grpc_client → grpc/models}/Marketdata/L2BookUpdate.py +0 -0
  168. /architect_py/{grpc_client → grpc/models}/Marketdata/TickerUpdate.py +0 -0
  169. /architect_py/{grpc_client → grpc/models}/Orderflow/Dropcopy.py +0 -0
  170. /architect_py/{grpc_client → grpc/models}/Orderflow/Orderflow.py +0 -0
  171. {architect_py-3.2.1.dist-info → architect_py-5.0.0b1.dist-info/licenses}/LICENSE +0 -0
@@ -0,0 +1,92 @@
1
+ import asyncio
2
+ import logging
3
+ from decimal import Decimal
4
+
5
+ from architect_py.async_client import AsyncClient
6
+ from architect_py.graphql_client.enums import OrderType, TimeInForce
7
+ from architect_py.scalars import OrderDir, TradableProduct
8
+
9
+ LOGGER = logging.getLogger(__name__)
10
+
11
+ api_key = None
12
+ api_secret = None
13
+ HOST = None
14
+ ACCOUNT = None
15
+
16
+
17
+ if api_key is None or api_secret is None or HOST is None or ACCOUNT is None:
18
+ raise ValueError(
19
+ "Please set the api_key, api_secret, HOST, and ACCOUNT variables before running this script"
20
+ )
21
+
22
+
23
+ client = AsyncClient(host=HOST, api_key=api_key, api_secret=api_secret)
24
+
25
+
26
+ async def search_symbol() -> tuple[str, TradableProduct]:
27
+ venue = "CME"
28
+ markets = await client.search_symbols(
29
+ search_string="ES",
30
+ execution_venue=venue,
31
+ )
32
+ market = markets[0]
33
+ return venue, market
34
+
35
+
36
+ async def test_send_order():
37
+ venue, symbol = await search_symbol()
38
+
39
+ snapshot = await client.get_market_snapshot(symbol=symbol, venue=venue)
40
+ if snapshot is None:
41
+ return ValueError(f"Market snapshot for {symbol} is None")
42
+
43
+ if snapshot.ask_price is None or snapshot.bid_price is None:
44
+ return ValueError(f"Market snapshot for {symbol} is None")
45
+
46
+ order = await client.send_limit_order(
47
+ symbol=symbol,
48
+ odir=OrderDir.BUY,
49
+ quantity=Decimal(1),
50
+ order_type=OrderType.LIMIT,
51
+ execution_venue="CME",
52
+ post_only=True,
53
+ limit_price=snapshot.bid_price
54
+ - (snapshot.ask_price - snapshot.bid_price) * Decimal(10),
55
+ account=ACCOUNT,
56
+ time_in_force=TimeInForce.IOC,
57
+ )
58
+ logging.critical(f"ORDER TEST: {order}")
59
+
60
+ assert order is not None
61
+
62
+ cancel = await client.cancel_order(order.id)
63
+
64
+ return cancel
65
+
66
+
67
+ async def test_cancel_all_orders():
68
+ await client.cancel_all_orders()
69
+
70
+
71
+ async def test_send_market_pro_order():
72
+ venue, symbol = await search_symbol()
73
+ print(symbol)
74
+
75
+ await client.send_market_pro_order(
76
+ symbol=symbol,
77
+ execution_venue=venue,
78
+ odir=OrderDir.BUY,
79
+ quantity=Decimal(1),
80
+ account=ACCOUNT,
81
+ time_in_force=TimeInForce.IOC,
82
+ )
83
+
84
+
85
+ async def main():
86
+ await test_send_market_pro_order()
87
+
88
+
89
+ if __name__ == "__main__":
90
+ loop = asyncio.new_event_loop()
91
+ asyncio.set_event_loop(loop)
92
+ loop.run_until_complete(main())
@@ -0,0 +1,25 @@
1
+ import asyncio
2
+
3
+ from architect_py.async_client import AsyncClient
4
+ from architect_py.scalars import TradableProduct
5
+
6
+ from .common import connect_async_client
7
+
8
+
9
+ async def main():
10
+ c: AsyncClient = await connect_async_client()
11
+
12
+ async for snap in c.subscribe_l1_book_stream(
13
+ symbols=[TradableProduct("ES 20250620 CME Future/USD")],
14
+ venue="CME",
15
+ ):
16
+ best_bid_s = "<no bid>"
17
+ best_ask_s = "<no ask>"
18
+ if snap.best_bid:
19
+ best_bid_s = f"{snap.best_bid[1]} x {snap.best_bid[0]}" # size x price
20
+ if snap.best_ask:
21
+ best_ask_s = f"{snap.best_ask[0]} x {snap.best_ask[1]}" # price x size
22
+ print(f"{snap.symbol} {snap.timestamp} {best_bid_s} {best_ask_s}")
23
+
24
+
25
+ asyncio.run(main())
@@ -0,0 +1,40 @@
1
+ import asyncio
2
+
3
+ from architect_py.async_client import AsyncClient
4
+ from architect_py.scalars import TradableProduct
5
+
6
+ from .common import connect_async_client
7
+
8
+ buy_columns = "{:>15} {:>15}"
9
+ sell_columns = "{:<15} {:<15}"
10
+ green = "\033[32m"
11
+ red = "\033[31m"
12
+ normal = "\033[0m"
13
+
14
+
15
+ async def print_l2_book(c: AsyncClient, symbol: TradableProduct, venue: str):
16
+ book = await c.subscribe_l2_book(symbol, venue)
17
+ while True:
18
+ print(f"book timestamp: {book.timestamp}")
19
+ print((buy_columns + " " + sell_columns).format("Size", "Bid", "Ask", "Size"))
20
+ for i in range(min(20, len(book.bids), len(book.asks))):
21
+ b = book.bids[i]
22
+ s = book.asks[i]
23
+ print(
24
+ (green + buy_columns).format(b[1], b[0]),
25
+ (red + sell_columns).format(s[0], s[1]),
26
+ )
27
+ print(normal)
28
+ await asyncio.sleep(1)
29
+
30
+
31
+ async def main():
32
+ c: AsyncClient = await connect_async_client()
33
+ endpoint = "app.architect.co" # one example of alternative can be "binance.marketdata.architect.co"
34
+ await c.grpc_client.change_channel(endpoint)
35
+ market_symbol = TradableProduct("ES 20250620 CME Future/USD")
36
+ venue = "CME"
37
+ await print_l2_book(c, market_symbol, venue=venue)
38
+
39
+
40
+ asyncio.run(main())
examples/trades.py ADDED
@@ -0,0 +1,21 @@
1
+ import asyncio
2
+
3
+ from architect_py.async_client import AsyncClient
4
+ from architect_py.graphql_client.exceptions import GraphQLClientHttpError
5
+ from architect_py.scalars import TradableProduct
6
+
7
+ from .common import connect_async_client
8
+
9
+
10
+ async def main():
11
+ c: AsyncClient = await connect_async_client()
12
+ market_id = TradableProduct("BTC Crypto", "USD")
13
+ try:
14
+ async for trade in c.subscribe_trades_stream(market_id, venue="COINBASE"):
15
+ print(trade)
16
+ except GraphQLClientHttpError as e:
17
+ print(e.status_code)
18
+ print(e.response.json())
19
+
20
+
21
+ asyncio.run(main())
@@ -0,0 +1,84 @@
1
+ import asyncio
2
+ from decimal import Decimal
3
+
4
+ from architect_py.async_client import OrderDir
5
+ from architect_py.graphql_client.enums import OrderStatus
6
+ from architect_py.scalars import TradableProduct
7
+ from examples.common import connect_async_client
8
+
9
+
10
+ async def main():
11
+ c = await connect_async_client()
12
+
13
+ market = TradableProduct("ES 20281215 CME Future/US")
14
+ execution_venue = "CME"
15
+
16
+ # Get market snapshot for a single market
17
+ # Market snapshots tell you the current best bid, best ask,
18
+ # and other ticker info for the given symbol.
19
+ print()
20
+ print(f"Market snapshot for {market}")
21
+ market_snapshot = await c.get_market_snapshot(symbol=market, venue=execution_venue)
22
+ print(f"Best bid: {market_snapshot.bid_price}")
23
+ print(f"Best ask: {market_snapshot.ask_price}")
24
+
25
+ # List your FCM accounts
26
+ print()
27
+ print("Your FCM accounts:")
28
+ accounts = await c.list_accounts()
29
+ for account in accounts:
30
+ print(f"{account.account.name}")
31
+
32
+ account_id = accounts[0].account.id
33
+
34
+ # Place a limit order $100 below the best bid
35
+ best_bid = market_snapshot.bid_price
36
+ if best_bid is None:
37
+ raise ValueError("No bid price available")
38
+ limit_price = best_bid - Decimal(100)
39
+ quantity = Decimal(1)
40
+ account = accounts[0]
41
+ order = None
42
+ print()
43
+ if (
44
+ input(
45
+ f"Place a limit order to BUY 1 LIMIT {limit_price} on account {account.account.name}? [y/N]"
46
+ )
47
+ == "y"
48
+ ):
49
+ order = await c.send_limit_order(
50
+ symbol=market,
51
+ execution_venue=execution_venue,
52
+ odir=OrderDir.BUY,
53
+ quantity=quantity,
54
+ limit_price=limit_price,
55
+ account=str(account_id),
56
+ )
57
+ else:
58
+ raise ValueError("Order was not placed")
59
+ print(f"Order placed with ID: {order.id}")
60
+
61
+ # Poll order status until rejected or fully executed
62
+ # After 5 seconds, cancel the order
63
+ i = 0
64
+ while order.status is OrderStatus.OPEN:
65
+ await asyncio.sleep(1)
66
+ print(f"...order state: {order.status}")
67
+ order = await c.get_order(order.id)
68
+ assert order is not None
69
+ i += 1
70
+ if i == 5:
71
+ print("Canceling order")
72
+ await c.cancel_order(order.id)
73
+
74
+ if order.status is OrderStatus.REJECTED:
75
+ print(f"Order was rejected: {order.reject_reason}")
76
+ elif order.status is OrderStatus.CANCELED:
77
+ print("Order was canceled")
78
+ elif order.status is OrderStatus.OUT:
79
+ print(f"Order was filled for qty: {order.filled_quantity}")
80
+ print(f"Average execution price: {order.average_fill_price}")
81
+
82
+
83
+ if __name__ == "__main__":
84
+ asyncio.run(main())
@@ -0,0 +1,95 @@
1
+ import pprint
2
+ import time
3
+ from decimal import Decimal
4
+
5
+ from architect_py.graphql_client.enums import OrderStatus
6
+ from architect_py.scalars import OrderDir
7
+ from architect_py.utils.nearest_tick import TickRoundMethod
8
+
9
+ from .common import confirm, connect_client, print_book, print_open_orders
10
+
11
+ c = connect_client()
12
+
13
+ venue = "CME"
14
+
15
+ # find ES markets
16
+ symbols = c.search_symbols(execution_venue=venue, search_string="ES")
17
+ print("\nFound markets:")
18
+
19
+ for s in symbols:
20
+ print(f" • {s}")
21
+
22
+ symbol = symbols[0]
23
+
24
+ # Lookup information about a single market
25
+ print(f"\nDetails for {symbols[0]}:")
26
+ product_info = c.get_product_info(symbol)
27
+ assert product_info is not None
28
+ pprint.pp(product_info)
29
+
30
+ # Get market snapshot for a single market
31
+ # Market snapshots tell you the current best bid, best ask,
32
+ # and other ticker info for the given symbol.
33
+ # this function is an alias for get_l1_book_snapshot
34
+ print(f"\nMarket snapshot for {product_info.symbol}:")
35
+ market_snapshot = c.get_market_snapshot(symbol=symbol, venue=venue)
36
+
37
+ pprint.pp(market_snapshot)
38
+
39
+ # Get L2 snapshot for a single market
40
+ print(f"\nL2 snapshot for {product_info.symbol}:")
41
+ start = time.perf_counter()
42
+ book = c.get_l2_book_snapshot(symbol=symbol, venue=venue)
43
+ elapsed = time.perf_counter() - start
44
+ print(f"Got book snapshot in {elapsed:.4f} seconds\n")
45
+ print_book(book)
46
+
47
+ # Get your accounts
48
+ print("\nYour accounts:")
49
+ accounts = c.list_accounts()
50
+ for account in accounts:
51
+ print(f" • {account.account.name}")
52
+
53
+ # Check your open orders
54
+ print("\nCurrent open orders:")
55
+ orders = c.get_open_orders()
56
+ print_open_orders(orders)
57
+
58
+ # Place a limit order 20% below the best bid
59
+ best_bid = market_snapshot.bid_price
60
+ assert best_bid is not None
61
+ limit_price = best_bid * Decimal(0.8)
62
+ quantity = Decimal(1)
63
+ account = accounts[0]
64
+ order = None
65
+
66
+ if confirm(
67
+ f"Place a limit order to BUY 1 {symbol} LIMIT {limit_price} on account {account.account.name}?"
68
+ ):
69
+ order = c.send_limit_order(
70
+ symbol=symbol,
71
+ execution_venue=venue,
72
+ odir=OrderDir.BUY,
73
+ quantity=quantity,
74
+ limit_price=limit_price,
75
+ account=account.account.name,
76
+ price_round_method=TickRoundMethod.ROUND,
77
+ )
78
+ assert order is not None
79
+ print(f"\nOrder placed with ID: {order.id}")
80
+
81
+ # Poll order status until rejected or fully executed
82
+ while order.status is OrderStatus.OPEN:
83
+ time.sleep(1)
84
+ print(f"...order state: {order.status}")
85
+ order = c.get_order(order.id)
86
+ assert order is not None
87
+
88
+ # Print final order state
89
+ if order.status is OrderStatus.REJECTED:
90
+ print(f"Order was rejected: {order.reject_reason}")
91
+ elif order.status is OrderStatus.CANCELED:
92
+ print("Order was canceled")
93
+ elif order.status is OrderStatus.OUT:
94
+ print(f"Order was filled for qty: {order.filled_quantity}")
95
+ print(f"Average execution price: {order.average_fill_price}")
@@ -0,0 +1,164 @@
1
+ import ast
2
+ import re
3
+ import sys
4
+ from collections import defaultdict
5
+
6
+
7
+ def extract_sections(source_lines):
8
+ """
9
+ Look for section header blocks that look like:
10
+
11
+ # ------------------------------------------------------------
12
+ # Some Section Name
13
+ # ------------------------------------------------------------
14
+
15
+ Returns a list of tuples (section_name, lineno) where lineno is the
16
+ line number (1-indexed) of the section title.
17
+ """
18
+ sections = []
19
+ # Regex for a dashed line
20
+ dashed_re = re.compile(r"^\s*#\s*-{10,}\s*$")
21
+ # Regex for a section title line (should start with "#")
22
+ title_re = re.compile(r"^\s*#\s*(.+\S)\s*$")
23
+
24
+ i = 0
25
+ while i < len(source_lines):
26
+ if dashed_re.match(source_lines[i]):
27
+ if i + 1 < len(source_lines) and title_re.match(source_lines[i + 1]):
28
+ groups = title_re.match(source_lines[i + 1])
29
+ if groups is not None:
30
+ section_name = groups.group(1).strip()
31
+ else:
32
+ continue
33
+ # Check that the next line (i+2) is also a dashed line
34
+ if i + 2 < len(source_lines) and dashed_re.match(source_lines[i + 2]):
35
+ sections.append(
36
+ (section_name, i + 2)
37
+ ) # record line number of closing dashed line, or i+1 as desired
38
+ i += 3
39
+ continue
40
+ i += 1
41
+ return sections
42
+
43
+
44
+ def find_section_for_lineno(lineno, sections):
45
+ """
46
+ Given a line number (1-indexed) and a list of sections (name, lineno),
47
+ return the name of the last section that occurs before the given line number.
48
+ If none found, return "No Section".
49
+ """
50
+ candidate = None
51
+ for name, sec_line in sections:
52
+ if sec_line < lineno:
53
+ candidate = name
54
+ else:
55
+ break
56
+ return candidate if candidate is not None else "No Section"
57
+
58
+
59
+ def get_asyncclient_methods(filename):
60
+ with open(filename, "r", encoding="utf-8") as f:
61
+ source = f.read()
62
+ source_lines = source.splitlines()
63
+ # Extract sections from source lines
64
+ sections = extract_sections(source_lines)
65
+
66
+ tree = ast.parse(source, filename)
67
+ methods = [] # list of tuples (section, func_name, doc_summary, lineno)
68
+
69
+ # Walk the AST to find the class AsyncClient
70
+ for node in ast.walk(tree):
71
+ if isinstance(node, ast.ClassDef) and node.name == "AsyncClient":
72
+ for item in node.body:
73
+ if isinstance(item, (ast.FunctionDef, ast.AsyncFunctionDef)):
74
+ # Exclude typing overloads
75
+ is_fn_overload = False
76
+ for decorator in item.decorator_list:
77
+ decorator_name = ""
78
+ if isinstance(decorator, ast.Call):
79
+ decorator_name = (
80
+ decorator.func.attr
81
+ if isinstance(decorator.func, ast.Attribute)
82
+ else decorator.func.id
83
+ )
84
+ else:
85
+ decorator_name = (
86
+ decorator.attr
87
+ if isinstance(decorator, ast.Attribute)
88
+ else decorator.id
89
+ )
90
+ if decorator_name == "overload":
91
+ is_fn_overload = True
92
+
93
+ if is_fn_overload:
94
+ continue
95
+
96
+ # Get docstring using ast.get_docstring
97
+ doc = ast.get_docstring(item)
98
+ if doc:
99
+ # Extract the first non-empty line from the docstring
100
+ first_line = next(
101
+ (line.strip() for line in doc.splitlines() if line.strip()),
102
+ "",
103
+ )
104
+ else:
105
+ first_line = ""
106
+ section = find_section_for_lineno(item.lineno, sections)
107
+ methods.append((section, item.name, first_line, item.lineno))
108
+ break # found our class, stop searching
109
+ return methods
110
+
111
+
112
+ def group_methods_by_section(methods):
113
+ grouped = defaultdict(list)
114
+ for section, name, summary, lineno in methods:
115
+ grouped[section].append((name, summary, lineno))
116
+ # Optionally sort sections by the lowest lineno
117
+ return dict(grouped)
118
+
119
+
120
+ def main(filename):
121
+ methods = get_asyncclient_methods(filename)
122
+ grouped = group_methods_by_section(methods)
123
+ # For predictable order, sort sections by the minimum line number among its methods
124
+ sorted_sections = sorted(
125
+ grouped.items(),
126
+ key=lambda item: min(m[2] for m in grouped[item[0]]) if grouped[item[0]] else 0,
127
+ )
128
+ # Alternatively, sort by section name alphabetically:
129
+ # sorted_sections = sorted(grouped.items())
130
+ for section, funcs in sorted_sections:
131
+ emoji = emoji_dict.get(section, "")
132
+ if emoji == "":
133
+ raise ValueError(f'Section "{section}" does not have an emoji.')
134
+ print(f"### {emoji} {section}")
135
+ print()
136
+ # Sort functions by their line number
137
+ for name, summary, lineno in sorted(funcs, key=lambda x: x[2]):
138
+ # Exclude private methods
139
+ if name.startswith("__"):
140
+ continue
141
+ # Only output if summary is non-empty
142
+ if summary:
143
+ if summary.startswith("@deprecated"):
144
+ continue
145
+ print(f"- **`{name}`**: {summary}")
146
+ else:
147
+ print(f"- **`{name}`**")
148
+ print("\n---\n")
149
+
150
+
151
+ emoji_dict: dict[str, str] = {
152
+ "Initialization and configuration": "🚀",
153
+ "Symbology": "🔍",
154
+ "Portfolio management": "💹",
155
+ "Order management": "📝",
156
+ "Order entry": "📣",
157
+ "Marketdata": "🧮",
158
+ }
159
+
160
+ if __name__ == "__main__":
161
+ if len(sys.argv) < 2:
162
+ print("Usage: python extract_methods.py <filename>")
163
+ else:
164
+ main(sys.argv[1])