architect-py 5.1.4rc1__py3-none-any.whl → 5.1.6__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 (49) hide show
  1. architect_py/__init__.py +24 -4
  2. architect_py/async_client.py +121 -67
  3. architect_py/client.pyi +16 -37
  4. architect_py/grpc/models/Accounts/ResetPaperAccountRequest.py +59 -0
  5. architect_py/grpc/models/Accounts/ResetPaperAccountResponse.py +20 -0
  6. architect_py/grpc/models/Boss/OptionsTransactionsRequest.py +42 -0
  7. architect_py/grpc/models/Boss/OptionsTransactionsResponse.py +27 -0
  8. architect_py/grpc/models/Core/ConfigResponse.py +7 -2
  9. architect_py/grpc/models/Folio/AccountSummary.py +7 -1
  10. architect_py/grpc/models/Marketdata/Candle.py +6 -0
  11. architect_py/grpc/models/Marketdata/L1BookSnapshot.py +6 -0
  12. architect_py/grpc/models/Marketdata/L2BookSnapshot.py +6 -0
  13. architect_py/grpc/models/Marketdata/Liquidation.py +6 -0
  14. architect_py/grpc/models/Marketdata/Ticker.py +6 -0
  15. architect_py/grpc/models/Marketdata/Trade.py +6 -0
  16. architect_py/grpc/models/OptionsMarketdata/OptionsChain.py +5 -5
  17. architect_py/grpc/models/OptionsMarketdata/OptionsChainGreeks.py +5 -5
  18. architect_py/grpc/models/OptionsMarketdata/OptionsChainGreeksRequest.py +5 -1
  19. architect_py/grpc/models/OptionsMarketdata/OptionsChainRequest.py +5 -1
  20. architect_py/grpc/models/OptionsMarketdata/OptionsContract.py +45 -0
  21. architect_py/grpc/models/OptionsMarketdata/OptionsContractGreeksRequest.py +40 -0
  22. architect_py/grpc/models/OptionsMarketdata/OptionsContractRequest.py +40 -0
  23. architect_py/grpc/models/OptionsMarketdata/OptionsExpirations.py +4 -1
  24. architect_py/grpc/models/OptionsMarketdata/OptionsExpirationsRequest.py +8 -1
  25. architect_py/grpc/models/OptionsMarketdata/OptionsGreeks.py +58 -0
  26. architect_py/grpc/models/OptionsMarketdata/OptionsWraps.py +28 -0
  27. architect_py/grpc/models/OptionsMarketdata/OptionsWrapsRequest.py +40 -0
  28. architect_py/grpc/models/__init__.py +11 -1
  29. architect_py/grpc/models/definitions.py +57 -86
  30. architect_py/grpc/orderflow.py +3 -7
  31. architect_py/grpc/server.py +1 -3
  32. architect_py/tests/test_order_entry.py +120 -1
  33. architect_py/tests/test_positions.py +364 -0
  34. architect_py/tests/test_rounding.py +28 -28
  35. architect_py/utils/pandas.py +50 -1
  36. {architect_py-5.1.4rc1.dist-info → architect_py-5.1.6.dist-info}/METADATA +1 -1
  37. {architect_py-5.1.4rc1.dist-info → architect_py-5.1.6.dist-info}/RECORD +49 -38
  38. examples/external_cpty.py +2 -1
  39. examples/funding_rate_mean_reversion_algo.py +4 -4
  40. examples/order_sending.py +3 -3
  41. examples/orderflow_channel.py +75 -56
  42. examples/stream_l1_marketdata.py +3 -1
  43. examples/stream_l2_marketdata.py +3 -1
  44. examples/tutorial_async.py +3 -2
  45. examples/tutorial_sync.py +4 -3
  46. scripts/generate_functions_md.py +2 -1
  47. {architect_py-5.1.4rc1.dist-info → architect_py-5.1.6.dist-info}/WHEEL +0 -0
  48. {architect_py-5.1.4rc1.dist-info → architect_py-5.1.6.dist-info}/licenses/LICENSE +0 -0
  49. {architect_py-5.1.4rc1.dist-info → architect_py-5.1.6.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,364 @@
1
+ import asyncio
2
+ from decimal import Decimal
3
+
4
+ import pytest
5
+
6
+ from architect_py import AsyncClient, OrderDir, OrderType, TradableProduct
7
+
8
+ ES_MULTIPLIER = Decimal("50.0") # ES futures multiplier
9
+
10
+
11
+ @pytest.mark.asyncio
12
+ async def test_paper_setup(async_client: AsyncClient):
13
+ accounts = await async_client.list_accounts()
14
+
15
+ assert len(accounts) == 1, (
16
+ f"Expected exactly one account in paper trading mode, got {len(accounts)}"
17
+ )
18
+
19
+ front_ES_future = await async_client.get_front_future("ES CME Futures", "CME")
20
+
21
+ product_info = await async_client.get_product_info(front_ES_future.base())
22
+ assert product_info is not None, (
23
+ f"Expected product info for {front_ES_future.base()} to be not None"
24
+ )
25
+ assert product_info.multiplier == ES_MULTIPLIER, (
26
+ f"Expected multiplier for {front_ES_future.base()} to be {ES_MULTIPLIER}, got {product_info.multiplier}"
27
+ )
28
+ await async_client.close()
29
+
30
+
31
+ @pytest.mark.asyncio
32
+ async def test_flattening_position(async_client: AsyncClient):
33
+ if not async_client.paper_trading:
34
+ return
35
+
36
+ front_ES_future = await async_client.get_front_future("ES CME Futures", "CME")
37
+ [account] = await async_client.list_accounts()
38
+ account_id = account.account.id
39
+
40
+ market_status = await async_client.get_market_status(front_ES_future, "CME")
41
+ if not market_status.is_trading:
42
+ await async_client.close()
43
+ pytest.skip(
44
+ f"Market for {front_ES_future} is not trading, skipping test_flattening_position"
45
+ )
46
+
47
+ await async_client.place_order(
48
+ symbol=front_ES_future,
49
+ venue="CME",
50
+ dir=OrderDir.BUY,
51
+ quantity=Decimal(value="100"),
52
+ account=account_id,
53
+ order_type=OrderType.MARKET,
54
+ )
55
+
56
+ positions = await async_client.get_positions(accounts=[account_id])
57
+
58
+ for tp, position in positions.items():
59
+ tradable_product = TradableProduct(tp)
60
+ product_info = await async_client.get_product_info(tradable_product.base())
61
+ assert product_info is not None, (
62
+ f"Expected product info for {tradable_product.base()} to be not None"
63
+ )
64
+ venue = product_info.primary_venue
65
+ assert venue is not None, (
66
+ f"Expected primary venue for {tradable_product.base()} to be not None"
67
+ )
68
+
69
+ market_status = await async_client.get_market_status(
70
+ symbol=tp,
71
+ venue=venue,
72
+ )
73
+ if not market_status.is_trading:
74
+ continue
75
+
76
+ if position != Decimal(0):
77
+ flatten_direction = OrderDir.SELL if position > Decimal(0) else OrderDir.BUY
78
+
79
+ await async_client.place_order(
80
+ symbol=tp,
81
+ dir=flatten_direction,
82
+ quantity=abs(position),
83
+ account=account_id,
84
+ order_type=OrderType.MARKET,
85
+ )
86
+
87
+ await asyncio.sleep(1.5) # wait for orders to be processed
88
+
89
+ positions = await async_client.get_positions(accounts=[account_id])
90
+ assert len(positions) == 0, (
91
+ f"Expected no positions in paper trading mode, got {len(positions)}"
92
+ )
93
+ await async_client.close()
94
+
95
+
96
+ @pytest.mark.asyncio
97
+ @pytest.mark.timeout(10)
98
+ async def test_paper_positions(async_client: AsyncClient):
99
+ venue = "CME"
100
+ if not async_client.paper_trading:
101
+ return
102
+
103
+ [account] = await async_client.list_accounts()
104
+ account_id = account.account.id
105
+ front_ES_future = await async_client.get_front_future("ES CME Futures", venue)
106
+ positions = await async_client.get_positions(accounts=[account_id])
107
+ ES_position = positions.get(front_ES_future)
108
+
109
+ market_status = await async_client.get_market_status(
110
+ symbol=front_ES_future,
111
+ venue=venue,
112
+ )
113
+ if not market_status.is_trading:
114
+ await async_client.close()
115
+ pytest.skip(
116
+ f"Market for {front_ES_future} is not trading, skipping test_paper_pnl"
117
+ )
118
+
119
+ # flatten position
120
+ if ES_position is not None:
121
+ flatten_direction = OrderDir.SELL if ES_position > Decimal(0) else OrderDir.BUY
122
+
123
+ order = await async_client.place_order(
124
+ symbol=front_ES_future,
125
+ venue=venue,
126
+ dir=flatten_direction,
127
+ quantity=Decimal(value="1"),
128
+ account=account_id,
129
+ order_type=OrderType.MARKET,
130
+ )
131
+ while True:
132
+ open_orders = await async_client.get_open_orders(order_ids=[order.id])
133
+ if not open_orders:
134
+ break
135
+ await asyncio.sleep(0.2)
136
+
137
+ fills = await async_client.get_fills(order_id=order.id)
138
+ assert len(fills.fills) == 1, "Expected exactly one fill for the order"
139
+ assert fills.fills[0].dir == flatten_direction, (
140
+ "Fill direction does not match order direction"
141
+ )
142
+
143
+ # go long
144
+ order = await async_client.place_order(
145
+ symbol=front_ES_future,
146
+ venue=venue,
147
+ dir=OrderDir.BUY,
148
+ quantity=Decimal(value="5"),
149
+ account=account_id,
150
+ order_type=OrderType.MARKET,
151
+ )
152
+ positions = await async_client.get_positions(accounts=[account_id])
153
+ assert positions.get(front_ES_future) == Decimal(5), (
154
+ f"Expected position in {front_ES_future} to be 5, got {positions.get(front_ES_future)}"
155
+ )
156
+
157
+ # go long to flat
158
+ order = await async_client.place_order(
159
+ symbol=front_ES_future,
160
+ venue=venue,
161
+ dir=OrderDir.SELL,
162
+ quantity=Decimal(value="5"),
163
+ account=account_id,
164
+ order_type=OrderType.MARKET,
165
+ )
166
+ positions = await async_client.get_positions(accounts=[account_id])
167
+ assert positions.get(front_ES_future) is None, (
168
+ f"Expected position in {front_ES_future} to be 0, got {positions.get(front_ES_future)}"
169
+ )
170
+
171
+ # go long
172
+ order = await async_client.place_order(
173
+ symbol=front_ES_future,
174
+ venue=venue,
175
+ dir=OrderDir.BUY,
176
+ quantity=Decimal(value="8"),
177
+ account=account_id,
178
+ order_type=OrderType.MARKET,
179
+ )
180
+ positions = await async_client.get_positions(accounts=[account_id])
181
+ assert positions.get(front_ES_future) == Decimal(8), (
182
+ f"Expected position in {front_ES_future} to be 8, got {positions.get(front_ES_future)}"
183
+ )
184
+
185
+ # go long to short
186
+ order = await async_client.place_order(
187
+ symbol=front_ES_future,
188
+ venue=venue,
189
+ dir=OrderDir.SELL,
190
+ quantity=Decimal(value="10"),
191
+ account=account_id,
192
+ order_type=OrderType.MARKET,
193
+ )
194
+ positions = await async_client.get_positions(accounts=[account_id])
195
+ assert positions.get(front_ES_future) == Decimal(-2), (
196
+ f"Expected position in {front_ES_future} to be -2, got {positions.get(front_ES_future)}"
197
+ )
198
+
199
+ # go flat
200
+ order = await async_client.place_order(
201
+ symbol=front_ES_future,
202
+ venue=venue,
203
+ dir=OrderDir.BUY,
204
+ quantity=Decimal(value="2"),
205
+ account=account_id,
206
+ order_type=OrderType.MARKET,
207
+ )
208
+ positions = await async_client.get_positions(accounts=[account_id])
209
+ assert positions.get(front_ES_future) is None, (
210
+ f"Expected position in {front_ES_future} to be 0, got {positions.get(front_ES_future)}"
211
+ )
212
+
213
+ # go short
214
+ order = await async_client.place_order(
215
+ symbol=front_ES_future,
216
+ venue=venue,
217
+ dir=OrderDir.SELL,
218
+ quantity=Decimal(value="5"),
219
+ account=account_id,
220
+ order_type=OrderType.MARKET,
221
+ )
222
+ positions = await async_client.get_positions(accounts=[account_id])
223
+ assert positions.get(front_ES_future) == Decimal(-5), (
224
+ f"Expected position in {front_ES_future} to be -5, got {positions.get(front_ES_future)}"
225
+ )
226
+
227
+ # go short to flat
228
+ order = await async_client.place_order(
229
+ symbol=front_ES_future,
230
+ venue=venue,
231
+ dir=OrderDir.BUY,
232
+ quantity=Decimal(value="5"),
233
+ account=account_id,
234
+ order_type=OrderType.MARKET,
235
+ )
236
+ positions = await async_client.get_positions(accounts=[account_id])
237
+ assert positions.get(front_ES_future) is None, (
238
+ f"Expected position in {front_ES_future} to be 0, got {positions.get(front_ES_future)}"
239
+ )
240
+
241
+ # go short
242
+ order = await async_client.place_order(
243
+ symbol=front_ES_future,
244
+ venue=venue,
245
+ dir=OrderDir.SELL,
246
+ quantity=Decimal(value="5"),
247
+ account=account_id,
248
+ order_type=OrderType.MARKET,
249
+ )
250
+ positions = await async_client.get_positions(accounts=[account_id])
251
+ assert positions.get(front_ES_future) == Decimal(-5), (
252
+ f"Expected position in {front_ES_future} to be -5, got {positions.get(front_ES_future)}"
253
+ )
254
+
255
+ # go short to long
256
+ order = await async_client.place_order(
257
+ symbol=front_ES_future,
258
+ venue=venue,
259
+ dir=OrderDir.BUY,
260
+ quantity=Decimal(value="10"),
261
+ account=account_id,
262
+ order_type=OrderType.MARKET,
263
+ )
264
+ positions = await async_client.get_positions(accounts=[account_id])
265
+ assert positions.get(front_ES_future) == Decimal(5), (
266
+ f"Expected position in {front_ES_future} to be 5, got {positions.get(front_ES_future)}"
267
+ )
268
+ await async_client.close()
269
+
270
+
271
+ @pytest.mark.asyncio
272
+ @pytest.mark.timeout(10)
273
+ async def test_paper_pnl(async_client: AsyncClient):
274
+ if not async_client.paper_trading:
275
+ return
276
+
277
+ [account] = await async_client.list_accounts()
278
+ account_id = account.account.id
279
+ front_ES_future = await async_client.get_front_future("ES CME Futures", "CME")
280
+ positions = await async_client.get_positions(accounts=[account_id])
281
+ ES_position = positions.get(front_ES_future)
282
+
283
+ market_status = await async_client.get_market_status(
284
+ symbol=front_ES_future,
285
+ venue="CME",
286
+ )
287
+ if not market_status.is_trading:
288
+ await async_client.close()
289
+ pytest.skip(
290
+ f"Market for {front_ES_future} is not trading, skipping test_paper_pnl"
291
+ )
292
+
293
+ # flatten position
294
+ if ES_position is not None:
295
+ flatten_direction = OrderDir.SELL if ES_position > Decimal(0) else OrderDir.BUY
296
+
297
+ quantity = abs(ES_position)
298
+
299
+ order = await async_client.place_order(
300
+ symbol=front_ES_future,
301
+ venue="CME",
302
+ dir=flatten_direction,
303
+ quantity=quantity,
304
+ account=account_id,
305
+ order_type=OrderType.MARKET,
306
+ )
307
+ while True:
308
+ open_orders = await async_client.get_open_orders(order_ids=[order.id])
309
+ if not open_orders:
310
+ break
311
+ await asyncio.sleep(0.2)
312
+
313
+ position = await async_client.get_positions(accounts=[account_id])
314
+ assert len(position) == 0, (
315
+ f"Expected no positions in paper trading mode, got {position}"
316
+ )
317
+
318
+ account_summary = await async_client.get_account_summary(account_id)
319
+ assert account_summary.purchasing_power is not None, (
320
+ "Expected purchasing power after trades to be set, got None"
321
+ )
322
+
323
+ pre_purchasing_power = account_summary.purchasing_power
324
+
325
+ quantity = Decimal(value="7")
326
+
327
+ sell_order = await async_client.place_order(
328
+ symbol=front_ES_future,
329
+ venue="CME",
330
+ dir=OrderDir.SELL,
331
+ quantity=quantity,
332
+ account=account_id,
333
+ order_type=OrderType.MARKET,
334
+ )
335
+
336
+ buy_order = await async_client.place_order(
337
+ symbol=front_ES_future,
338
+ venue="CME",
339
+ dir=OrderDir.BUY,
340
+ quantity=quantity,
341
+ account=account_id,
342
+ order_type=OrderType.MARKET,
343
+ )
344
+ await asyncio.sleep(1.5) # wait for order to be processed
345
+
346
+ sell_fill = await async_client.get_fills(order_id=sell_order.id)
347
+ sell_fill_price = sell_fill.fills[0].price
348
+
349
+ buy_fill = await async_client.get_fills(order_id=buy_order.id)
350
+ buy_fill_price = buy_fill.fills[0].price
351
+
352
+ pnl = (sell_fill_price - buy_fill_price) * ES_MULTIPLIER * quantity
353
+
354
+ account_summary = await async_client.get_account_summary(account_id)
355
+ assert account_summary.purchasing_power is not None, (
356
+ "Expected purchasing power after trades to be set, got None"
357
+ )
358
+ post_purchasing_power = account_summary.purchasing_power
359
+
360
+ assert post_purchasing_power == pre_purchasing_power + pnl, (
361
+ f"Expected purchasing power to be {pre_purchasing_power + pnl}, got {post_purchasing_power}.\n"
362
+ f"Buy fill price: {buy_fill_price}, Sell fill price: {sell_fill_price}, quantity: {quantity}, pnl: {pnl}, pre_purchasing_power: {pre_purchasing_power}, post_purchasing_power: {post_purchasing_power}"
363
+ )
364
+ await async_client.close()
@@ -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!")
@@ -4,7 +4,7 @@ import msgspec
4
4
  import pandas as pd
5
5
 
6
6
  if TYPE_CHECKING:
7
- from .. import Candle
7
+ from .. import Candle, Ticker
8
8
 
9
9
  CANDLES_FIELD_MAP = {
10
10
  "av": "sell_volume",
@@ -43,3 +43,52 @@ def candles_to_dataframe(candles: List["Candle"]) -> pd.DataFrame:
43
43
  df.style.hide(["tn", "ts"], axis=1)
44
44
  df.set_index("timestamp", inplace=True)
45
45
  return df
46
+
47
+
48
+ def tickers_to_dataframe(tickers: List["Ticker"]) -> pd.DataFrame:
49
+ records = msgspec.to_builtins(tickers)
50
+ df = pd.DataFrame.from_records(records)
51
+ df.rename(
52
+ columns={
53
+ "s": "symbol",
54
+ "ve": "venue",
55
+ "ap": "ask_price",
56
+ "as": "ask_size",
57
+ "bp": "bid_price",
58
+ "bs": "bid_size",
59
+ "dividend": "dividend",
60
+ "dividend_yield": "dividend_yield",
61
+ "eps_adj": "eps_adj",
62
+ "fr": "funding_rate",
63
+ "ft": "next_funding_time",
64
+ "h": "high_24h",
65
+ "ip": "index_price",
66
+ "isp": "indicative_settlement_price",
67
+ "l": "low_24h",
68
+ "market_cap": "market_cap",
69
+ "mp": "mark_price",
70
+ "o": "open_24h",
71
+ "oi": "open_interest",
72
+ "p": "last_price",
73
+ "price_to_earnings": "price_to_earnings",
74
+ "q": "last_size",
75
+ "sd": "last_settlement_date",
76
+ "shares_outstanding_weighted_adj": "shares_outstanding_weighted_adj",
77
+ "sp": "last_settlement_price",
78
+ "v": "volume_24h",
79
+ "vm": "volume_30d",
80
+ "xh": "session_high",
81
+ "xl": "session_low",
82
+ "xo": "session_open",
83
+ "xv": "session_volume",
84
+ },
85
+ inplace=True,
86
+ )
87
+ df["timestamp"] = pd.to_datetime(
88
+ df["ts"] * 1_000_000_000 + df["tn"],
89
+ unit="ns",
90
+ utc=True,
91
+ )
92
+ df.style.hide(["tn", "ts"], axis=1)
93
+ df.set_index("symbol", inplace=True)
94
+ return df
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: architect-py
3
- Version: 5.1.4rc1
3
+ Version: 5.1.6
4
4
  Summary: Python SDK for the Architect trading platform and brokerage.
5
5
  Author-email: "Architect Financial Technologies, Inc." <hello@architect.co>
6
6
  License-Expression: Apache-2.0