architect-py 5.1.5__py3-none-any.whl → 5.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. architect_py/__init__.py +24 -4
  2. architect_py/async_client.py +66 -57
  3. architect_py/async_cpty.py +422 -0
  4. architect_py/client.pyi +2 -34
  5. architect_py/grpc/models/Accounts/ResetPaperAccountRequest.py +59 -0
  6. architect_py/grpc/models/Accounts/ResetPaperAccountResponse.py +20 -0
  7. architect_py/grpc/models/Boss/OptionsTransactionsRequest.py +42 -0
  8. architect_py/grpc/models/Boss/OptionsTransactionsResponse.py +27 -0
  9. architect_py/grpc/models/OptionsMarketdata/OptionsChain.py +5 -5
  10. architect_py/grpc/models/OptionsMarketdata/OptionsChainGreeks.py +5 -5
  11. architect_py/grpc/models/OptionsMarketdata/OptionsChainGreeksRequest.py +5 -1
  12. architect_py/grpc/models/OptionsMarketdata/OptionsChainRequest.py +5 -1
  13. architect_py/grpc/models/OptionsMarketdata/OptionsContract.py +45 -0
  14. architect_py/grpc/models/OptionsMarketdata/OptionsContractGreeksRequest.py +40 -0
  15. architect_py/grpc/models/OptionsMarketdata/OptionsContractRequest.py +40 -0
  16. architect_py/grpc/models/OptionsMarketdata/OptionsExpirations.py +4 -1
  17. architect_py/grpc/models/OptionsMarketdata/OptionsExpirationsRequest.py +8 -1
  18. architect_py/grpc/models/OptionsMarketdata/OptionsGreeks.py +58 -0
  19. architect_py/grpc/models/OptionsMarketdata/OptionsWraps.py +28 -0
  20. architect_py/grpc/models/OptionsMarketdata/OptionsWrapsRequest.py +40 -0
  21. architect_py/grpc/models/__init__.py +11 -1
  22. architect_py/grpc/models/definitions.py +37 -86
  23. architect_py/grpc/orderflow.py +3 -7
  24. architect_py/grpc/server.py +1 -3
  25. architect_py/tests/test_order_entry.py +120 -1
  26. architect_py/tests/test_positions.py +208 -17
  27. {architect_py-5.1.5.dist-info → architect_py-5.2.0.dist-info}/METADATA +1 -1
  28. {architect_py-5.1.5.dist-info → architect_py-5.2.0.dist-info}/RECORD +41 -30
  29. examples/external_cpty.py +72 -66
  30. examples/funding_rate_mean_reversion_algo.py +4 -4
  31. examples/order_sending.py +3 -3
  32. examples/orderflow_channel.py +75 -56
  33. examples/stream_l1_marketdata.py +3 -1
  34. examples/stream_l2_marketdata.py +3 -1
  35. examples/tutorial_async.py +3 -2
  36. examples/tutorial_sync.py +4 -3
  37. scripts/add_imports_to_inits.py +6 -2
  38. scripts/generate_functions_md.py +2 -1
  39. {architect_py-5.1.5.dist-info → architect_py-5.2.0.dist-info}/WHEEL +0 -0
  40. {architect_py-5.1.5.dist-info → architect_py-5.2.0.dist-info}/licenses/LICENSE +0 -0
  41. {architect_py-5.1.5.dist-info → architect_py-5.2.0.dist-info}/top_level.txt +0 -0
examples/external_cpty.py CHANGED
@@ -1,77 +1,83 @@
1
- import time
2
- from concurrent import futures
3
- from decimal import Decimal
4
- from typing import Iterator
5
-
6
- import grpc
1
+ import asyncio
2
+ import logging
3
+ import random
4
+ import string
7
5
 
8
6
  from architect_py import (
7
+ Cancel,
9
8
  CptyLoginRequest,
10
9
  CptyLogoutRequest,
11
- ExecutionInfo,
12
- MinOrderQuantityUnit,
13
- SimpleDecimal,
14
- Unit,
15
- )
16
- from architect_py.grpc.models.Cpty.CptyResponse import Symbology
17
- from architect_py.grpc.models.Orderflow.OrderflowRequest import CancelOrder, PlaceOrder
18
- from architect_py.grpc.server import (
19
- CptyServicer,
20
- OrderflowServicer,
21
- add_CptyServicer_to_server,
22
- add_OrderflowServicer_to_server,
10
+ Order,
23
11
  )
12
+ from architect_py.async_cpty import *
13
+
14
+
15
+ class ExampleCpty(AsyncCpty):
16
+ def __init__(self):
17
+ super().__init__("EXAMPLE")
18
+
19
+ async def on_login(self, request: CptyLoginRequest):
20
+ print(
21
+ f"👋 got login request from trader={request.trader} and account={request.account}"
22
+ )
23
+
24
+ async def on_logout(self, request: CptyLogoutRequest):
25
+ print("👋 got logout request")
26
+
27
+ async def on_place_order(self, order: Order):
28
+ # pretend we're connected to something, ack the order within 1s,
29
+ # then randomly out or fill the order after 3s
30
+ print(f"🎟️ place order: {order}")
31
+ asyncio.create_task(mock_order_lifecycle(self, order))
24
32
 
33
+ async def on_cancel_order(
34
+ self, cancel: Cancel, _original_order: Optional[Order] = None
35
+ ):
36
+ self.reject_cancel(
37
+ cancel.cancel_id,
38
+ reject_reason="cancels always get rejected in this example",
39
+ )
40
+
41
+ async def get_open_orders(self) -> Sequence[Order]:
42
+ return []
43
+
44
+
45
+ def random_id(length=10):
46
+ CHARS = string.ascii_uppercase + string.digits
47
+ return "".join(random.choice(CHARS) for _ in range(length))
25
48
 
26
- class MockCptyServicer(CptyServicer, OrderflowServicer):
27
- def Cpty(self, request_iterator: Iterator, context):
28
- context.set_code(grpc.StatusCode.OK)
29
- context.send_initial_metadata({})
30
- # send symbology
31
- yield Symbology(
32
- execution_info={
33
- "FOO Crypto/USD": {
34
- "MOCK": ExecutionInfo(
35
- execution_venue="MOCK",
36
- exchange_symbol=None,
37
- tick_size=SimpleDecimal(Decimal("0.01")),
38
- step_size=Decimal("0.1"),
39
- min_order_quantity=Decimal(0),
40
- min_order_quantity_unit=MinOrderQuantityUnit(Unit.base),
41
- is_delisted=False,
42
- initial_margin=None,
43
- maintenance_margin=None,
44
- ),
45
- }
46
- }
49
+
50
+ async def mock_order_lifecycle(cpty: ExampleCpty, order: Order):
51
+ await asyncio.sleep(1)
52
+ exchange_order_id = random_id()
53
+ print(f"🎟️ ack order: {order.id} with exchange_order_id={exchange_order_id}")
54
+ cpty.ack_order(order.id, exchange_order_id=exchange_order_id)
55
+
56
+ await asyncio.sleep(3)
57
+ if random.random() < 0.5:
58
+ now = datetime.now()
59
+ print(f"🎟️ fill order: {order.id}")
60
+ cpty.fill_order(
61
+ order_id=order.id,
62
+ exchange_fill_id=random_id(),
63
+ dir=order.dir,
64
+ price=Decimal(13.37),
65
+ quantity=order.quantity,
66
+ symbol=order.symbol,
67
+ trade_time=now,
68
+ account=order.account,
69
+ is_taker=True,
47
70
  )
48
- for req in request_iterator:
49
- if isinstance(req, CptyLoginRequest):
50
- print("login message received", req)
51
- elif isinstance(req, CptyLogoutRequest):
52
- print("logout message received", req)
53
- elif isinstance(req, PlaceOrder):
54
- print("place_order message received", req)
55
- elif isinstance(req, CancelOrder):
56
- print("cancel_order message received", req)
57
-
58
- def SubscribeOrderflow(self, request, context):
59
- context.set_code(grpc.StatusCode.OK)
60
- context.send_initial_metadata({})
61
- time.sleep(100)
62
-
63
-
64
- def serve():
65
- thread_pool = futures.ThreadPoolExecutor(max_workers=10)
66
- server = grpc.server(thread_pool)
67
- servicer = MockCptyServicer()
68
- add_CptyServicer_to_server(servicer, server)
69
- add_OrderflowServicer_to_server(servicer, server)
70
- server.add_insecure_port("[::]:50051")
71
- server.start()
72
- print("server started on port 50051")
73
- server.wait_for_termination()
71
+ else:
72
+ print(f"🎟️ out order: {order.id}")
73
+ cpty.out_order(order.id, canceled=False)
74
+
75
+
76
+ async def serve():
77
+ cpty = ExampleCpty()
78
+ await cpty.serve("[::]:50051")
74
79
 
75
80
 
76
81
  if __name__ == "__main__":
77
- serve()
82
+ logging.basicConfig(level=logging.DEBUG)
83
+ asyncio.run(serve())
@@ -151,14 +151,14 @@ async def print_info(c: AsyncClient):
151
151
  async def main():
152
152
  c = await connect_async_client()
153
153
 
154
- orderflow_manager = await c.orderflow()
155
- await orderflow_manager.start()
154
+ orderflow_channel = await c.orderflow()
155
+ await orderflow_channel.start()
156
156
 
157
157
  await asyncio.gather(
158
158
  update_marketdata(c),
159
- step_to_target_position(c, orderflow_manager),
159
+ step_to_target_position(c, orderflow_channel),
160
160
  print_info(c),
161
- subscribe_and_print_orderflow(c, orderflow_manager),
161
+ subscribe_and_print_orderflow(c, orderflow_channel),
162
162
  )
163
163
  await c.close()
164
164
 
examples/order_sending.py CHANGED
@@ -22,7 +22,7 @@ async def search_symbol(c: AsyncClient) -> tuple[str, TradableProduct]:
22
22
  async def test_send_order(client: AsyncClient, account: str):
23
23
  venue, symbol = await search_symbol(client)
24
24
 
25
- snapshot = await client.get_market_snapshot(symbol=symbol, venue=venue)
25
+ snapshot = await client.get_l1_book_snapshot(symbol=symbol, venue=venue)
26
26
  if snapshot is None:
27
27
  return ValueError(f"Market snapshot for {symbol} is None")
28
28
 
@@ -34,7 +34,7 @@ async def test_send_order(client: AsyncClient, account: str):
34
34
  d = datetime.now(tz=timezone.utc) + timedelta(days=1)
35
35
  gtd = TimeInForce.GTD(d)
36
36
 
37
- order = await client.place_limit_order(
37
+ order = await client.place_order(
38
38
  symbol=symbol,
39
39
  dir=OrderDir.BUY,
40
40
  quantity=best_bid_quantity,
@@ -82,7 +82,7 @@ async def send_NQ_buy_for_mid(client: AsyncClient, account: str):
82
82
  if snapshot is None or snapshot.best_ask is None or snapshot.best_bid is None:
83
83
  return ValueError(f"Market snapshot for {NQ_lead_future} is None")
84
84
 
85
- order = await client.place_limit_order(
85
+ order = await client.place_order(
86
86
  symbol=NQ_lead_future,
87
87
  dir=OrderDir.BUY,
88
88
  quantity=Decimal(1),
@@ -1,56 +1,75 @@
1
- # """
2
- # Example of using a bidirectional orderflow channel with the Architect OEMS.
3
-
4
- # This connection style is ~equivalent to having a websocket.
5
-
6
- # This code example sends a series of orders to Architect while concurrently
7
- # listening to orderflow events. Compare to `examples/orderflow_streaming.py`,
8
- # which accomplishes the same thing but using a separate asyncio task.
9
- # """
10
-
11
- # import asyncio
12
- # from decimal import Decimal
13
-
14
- # from architect_py import AsyncClient, OrderDir, PlaceOrderRequest
15
- # from architect_py.grpc.models.Orderflow.OrderflowRequest import PlaceOrder
16
-
17
- # from .config import connect_async_client
18
-
19
-
20
- # async def send_orders(client: AsyncClient):
21
- # symbol = await client.get_front_future("ES CME Futures")
22
- # print(f"symbol={symbol}")
23
-
24
- # while True:
25
- # await asyncio.sleep(1)
26
- # snap = await client.get_l1_book_snapshot(symbol, "CME")
27
- # if snap.best_ask is not None:
28
- # limit_price = snap.best_ask[0]
29
- # print(f"\nPlacing buy order at {limit_price}\n")
30
- # try:
31
- # req = PlaceOrderRequest.new(
32
- # symbol=symbol,
33
- # execution_venue="CME",
34
- # dir=OrderDir.BUY,
35
- # quantity=Decimal(1),
36
- # limit_price=limit_price,
37
- # post_only=True,
38
- # time_in_force="DAY",
39
- # order_type="LIMIT",
40
- # )
41
- # print(f"req={req}")
42
- # yield PlaceOrder(req)
43
- # except Exception as e:
44
- # print(f"Error placing order: {e}")
45
- # else:
46
- # print("\nNo ask price from snapshot, doing nothing\n")
47
-
48
-
49
- # async def main():
50
- # client = await connect_async_client()
51
-
52
- # async for event in client.orderflow(send_orders(client)):
53
- # print(f" --> {event}")
54
-
55
-
56
- # asyncio.run(main())
1
+ """
2
+ Example of using a bidirectional orderflow channel with the Architect OEMS.
3
+
4
+ This connection style is ~equivalent to having a websocket.
5
+
6
+ This code example sends a series of orders to Architect while concurrently
7
+ listening to orderflow events. Compare to `examples/orderflow_streaming.py`,
8
+ which accomplishes the same thing but using a separate asyncio task.
9
+
10
+
11
+ See funding_rate_mean_reversion_algo.py for a more complete example of
12
+ using the orderflow channel to implement a trading strategy.
13
+ """
14
+
15
+ import asyncio
16
+ from decimal import Decimal
17
+
18
+ from architect_py import (
19
+ AsyncClient,
20
+ OrderDir,
21
+ OrderType,
22
+ TimeInForce,
23
+ )
24
+ from architect_py.grpc.orderflow import OrderflowChannel, PlaceOrder
25
+
26
+ from .config import connect_async_client
27
+
28
+
29
+ async def send_orders(client: AsyncClient, orderflow_channel: OrderflowChannel):
30
+ symbol = await client.get_front_future("ES CME Futures")
31
+ print(f"symbol={symbol}")
32
+
33
+ while True:
34
+ await asyncio.sleep(1)
35
+ snap = await client.get_l1_book_snapshot(symbol, "CME")
36
+ if snap.best_ask is not None:
37
+ limit_price = snap.best_ask[0]
38
+ print(f"\nPlacing buy order at {limit_price}\n")
39
+ try:
40
+ # can also CancelOrder
41
+ req = PlaceOrder.new(
42
+ symbol=symbol,
43
+ execution_venue="CME",
44
+ dir=OrderDir.BUY,
45
+ quantity=Decimal(1),
46
+ limit_price=limit_price,
47
+ post_only=True,
48
+ time_in_force=TimeInForce.DAY,
49
+ order_type=OrderType.LIMIT,
50
+ )
51
+ print(f"req={req}")
52
+ await orderflow_channel.send(req)
53
+ except Exception as e:
54
+ print(f"Error placing order: {e}")
55
+ else:
56
+ print("\nNo ask price from snapshot, doing nothing\n")
57
+
58
+
59
+ async def read_events(orderflow_channel: OrderflowChannel):
60
+ async for event in orderflow_channel:
61
+ print(f" --> {event}")
62
+
63
+
64
+ async def main():
65
+ client = await connect_async_client()
66
+
67
+ orderflow_channel = await client.orderflow()
68
+
69
+ await asyncio.gather(
70
+ send_orders(client, orderflow_channel),
71
+ read_events(orderflow_channel),
72
+ )
73
+
74
+
75
+ asyncio.run(main())
@@ -8,8 +8,10 @@ from .config import connect_async_client
8
8
  async def main():
9
9
  c: AsyncClient = await connect_async_client()
10
10
 
11
+ ES_front_future = await c.get_front_future("ES CME Futures")
12
+
11
13
  async for snap in c.stream_l1_book_snapshots(
12
- symbols=[TradableProduct("ES 20250620 CME Future/USD")],
14
+ symbols=[TradableProduct(ES_front_future)],
13
15
  venue="CME",
14
16
  ):
15
17
  best_bid_s = "<no bid>"
@@ -29,7 +29,9 @@ async def print_l2_book(c: AsyncClient, symbol: TradableProduct, venue: str):
29
29
 
30
30
  async def main():
31
31
  c: AsyncClient = await connect_async_client()
32
- market_symbol = TradableProduct("ES 20250620 CME Future/USD")
32
+
33
+ ES_front_future = await c.get_front_future("ES CME Futures")
34
+ market_symbol = TradableProduct(ES_front_future)
33
35
  venue = "CME"
34
36
  await print_l2_book(c, market_symbol, venue=venue)
35
37
 
@@ -1,7 +1,7 @@
1
1
  import asyncio
2
2
  from decimal import Decimal
3
3
 
4
- from architect_py import OrderDir, OrderStatus, TradableProduct
4
+ from architect_py import OrderDir, OrderStatus, OrderType, TradableProduct
5
5
 
6
6
  from .config import connect_async_client
7
7
 
@@ -47,10 +47,11 @@ async def main():
47
47
  )
48
48
  == "y"
49
49
  ):
50
- order = await c.place_limit_order(
50
+ order = await c.place_order(
51
51
  symbol=market,
52
52
  execution_venue=execution_venue,
53
53
  dir=OrderDir.BUY,
54
+ order_type=OrderType.LIMIT,
54
55
  quantity=quantity,
55
56
  limit_price=limit_price,
56
57
  account=str(account_id),
examples/tutorial_sync.py CHANGED
@@ -2,7 +2,7 @@ import pprint
2
2
  import time
3
3
  from decimal import Decimal
4
4
 
5
- from architect_py import OrderDir, OrderStatus
5
+ from architect_py import OrderDir, OrderStatus, OrderType
6
6
  from architect_py.utils.nearest_tick import TickRoundMethod
7
7
 
8
8
  from .config import connect_client
@@ -32,7 +32,7 @@ pprint.pp(product_info)
32
32
  # and other ticker info for the given symbol.
33
33
  # this function is an alias for get_l1_book_snapshot
34
34
  print(f"\nMarket snapshot for {product_info.symbol}:")
35
- market_snapshot = c.get_market_snapshot(symbol=symbol, venue=venue)
35
+ market_snapshot = c.get_l1_book_snapshot(symbol=symbol, venue=venue)
36
36
 
37
37
  pprint.pp(market_snapshot)
38
38
 
@@ -66,10 +66,11 @@ order = None
66
66
  if confirm(
67
67
  f"Place a limit order to BUY 1 {symbol} LIMIT {limit_price} on account {account.account.name}?"
68
68
  ):
69
- order = c.send_limit_order(
69
+ order = c.place_order(
70
70
  symbol=symbol,
71
71
  execution_venue=venue,
72
72
  dir=OrderDir.BUY,
73
+ order_type=OrderType.LIMIT,
73
74
  quantity=best_bid_quantity,
74
75
  limit_price=limit_price,
75
76
  account=account.account.name,
@@ -115,7 +115,9 @@ def main(architect_path: Path, output: Path):
115
115
 
116
116
  unique = sorted(set(model_all))
117
117
  model_lines.append("")
118
- model_lines.append(f"__all__ = [{', '.join(f'"{n}"' for n in model_all)}]")
118
+
119
+ model_lines_all_list = ", ".join(f'"{n}"' for n in model_all)
120
+ model_lines.append(f"__all__ = [{model_lines_all_list}]")
119
121
  model_lines.append("")
120
122
 
121
123
  (models_root / "__init__.py").write_text("\n".join(model_lines))
@@ -123,7 +125,9 @@ def main(architect_path: Path, output: Path):
123
125
  # 3) write __all__
124
126
  unique = sorted(set(all_names))
125
127
  import_lines.append("") # blank line
126
- import_lines.append(f"__all__ = [{', '.join(f'"{n}"' for n in unique)}]")
128
+
129
+ import_lines_all_list = ", ".join(f'"{n}"' for n in unique)
130
+ import_lines.append(f"__all__ = [{import_lines_all_list}]")
127
131
 
128
132
  import_lines.append("") # blank line
129
133
 
@@ -129,6 +129,7 @@ def main(filename):
129
129
  )
130
130
  # Alternatively, sort by section name alphabetically:
131
131
  # sorted_sections = sorted(grouped.items())
132
+ print("# Client Methods")
132
133
  for section, funcs in sorted_sections:
133
134
  emoji = emoji_dict.get(section, "")
134
135
  if emoji == "":
@@ -138,7 +139,7 @@ def main(filename):
138
139
  # Sort functions by their line number
139
140
  for name, summary, lineno in sorted(funcs, key=lambda x: x[2]):
140
141
  # Exclude private methods
141
- if name.startswith("__"):
142
+ if name.startswith("_"):
142
143
  continue
143
144
  # Only output if summary is non-empty
144
145
  if summary: