architect-py 5.1.4rc1__py3-none-any.whl → 5.1.5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
architect_py/__init__.py CHANGED
@@ -1,6 +1,6 @@
1
1
  # ruff: noqa:I001
2
2
 
3
- __version__ = "5.1.4rc1"
3
+ __version__ = "5.1.5"
4
4
 
5
5
  from .utils.nearest_tick import TickRoundMethod
6
6
  from .async_client import AsyncClient
@@ -44,7 +44,7 @@ from architect_py.grpc.orderflow import OrderflowChannel
44
44
  from architect_py.grpc.resolve_endpoint import PAPER_GRPC_PORT, resolve_endpoint
45
45
  from architect_py.utils.nearest_tick import TickRoundMethod
46
46
  from architect_py.utils.orderbook import update_orderbook_side
47
- from architect_py.utils.pandas import candles_to_dataframe
47
+ from architect_py.utils.pandas import candles_to_dataframe, tickers_to_dataframe
48
48
  from architect_py.utils.price_bands import price_band_pairs
49
49
  from architect_py.utils.symbol_parsing import nominative_expiration
50
50
 
@@ -890,6 +890,37 @@ class AsyncClient:
890
890
  res: Ticker = await grpc_client.unary_unary(req)
891
891
  return res
892
892
 
893
+ async def get_tickers(
894
+ self,
895
+ *,
896
+ venue: Venue,
897
+ symbols: Optional[Sequence[TradableProduct | str]] = None,
898
+ include_options: bool = False,
899
+ sort_by: Optional[SortTickersBy | str] = None,
900
+ offset: Optional[int] = None,
901
+ limit: Optional[int] = None,
902
+ as_dataframe: bool = False,
903
+ ) -> Union[Sequence[Ticker], pd.DataFrame]:
904
+ """
905
+ Gets the tickers for a list of symbols.
906
+ """
907
+ grpc_client = await self.marketdata(venue)
908
+ sort_by = SortTickersBy(sort_by) if sort_by else None
909
+ symbols = [str(symbol) for symbol in symbols] if symbols else None
910
+ req = TickersRequest.new(
911
+ offset=offset,
912
+ include_options=include_options,
913
+ sort_by=sort_by,
914
+ limit=limit,
915
+ symbols=symbols,
916
+ venue=venue,
917
+ )
918
+ res: TickersResponse = await grpc_client.unary_unary(req)
919
+ if as_dataframe:
920
+ return tickers_to_dataframe(res.tickers)
921
+ else:
922
+ return res.tickers
923
+
893
924
  async def stream_l1_book_snapshots(
894
925
  self,
895
926
  symbols: Sequence[TradableProduct | str],
@@ -1151,6 +1182,27 @@ class AsyncClient:
1151
1182
  res = await grpc_client.unary_unary(req)
1152
1183
  return res
1153
1184
 
1185
+ async def get_positions(
1186
+ self,
1187
+ accounts: Optional[list[str]] = None,
1188
+ trader: Optional[str] = None,
1189
+ ) -> dict[str, Decimal]:
1190
+ """
1191
+ Get positions for the specified symbols.
1192
+
1193
+ Args:
1194
+ symbols: list of symbol strings
1195
+ """
1196
+ account_summaries = await self.get_account_summaries(
1197
+ accounts=accounts, trader=trader
1198
+ )
1199
+ positions: dict[str, Decimal] = {}
1200
+ for summary in account_summaries:
1201
+ for symbol, summary in summary.positions.items():
1202
+ for pos in summary:
1203
+ positions[symbol] = positions.get(symbol, Decimal(0)) + pos.quantity
1204
+ return positions
1205
+
1154
1206
  async def get_account_summaries(
1155
1207
  self,
1156
1208
  accounts: Optional[list[str]] = None,
@@ -1531,7 +1583,7 @@ class AsyncClient:
1531
1583
  execution_venue: Optional[str] = None,
1532
1584
  dir: OrderDir,
1533
1585
  quantity: Decimal,
1534
- limit_price: Decimal,
1586
+ limit_price: Optional[Decimal] = None,
1535
1587
  order_type: OrderType = OrderType.LIMIT,
1536
1588
  time_in_force: TimeInForce = TimeInForce.DAY,
1537
1589
  price_round_method: Optional[TickRoundMethod] = None,
@@ -1563,7 +1615,7 @@ class AsyncClient:
1563
1615
  While technically optional, for most order types, the account is required
1564
1616
  trader: the trader to send the order for, defaults to the user's trader
1565
1617
  for when sending order for another user, not relevant for vast majority of users
1566
- post_only: whether the order should be post only, not supported by all exchanges
1618
+ post_only: whether the order should be post only, NOT SUPPORTED BY ALL EXCHANGES (e.g. CME)
1567
1619
  trigger_price: the trigger price for the order, only relevant for stop / take_profit orders
1568
1620
  stop_loss_price: the stop loss price for a bracket order.
1569
1621
  profit_price: the take profit price for a bracket order.
@@ -1576,14 +1628,7 @@ class AsyncClient:
1576
1628
  grpc_client = await self.core()
1577
1629
  assert quantity > 0, "quantity must be positive"
1578
1630
 
1579
- if dir is None:
1580
- if "odir" in kwargs and isinstance(kwargs["odir"], OrderDir):
1581
- logging.warning("odir is deprecated, use dir instead")
1582
- dir = kwargs["odir"]
1583
- else:
1584
- raise ValueError("dir is required")
1585
-
1586
- if price_round_method is not None:
1631
+ if limit_price is not None and price_round_method is not None:
1587
1632
  if execution_venue is None:
1588
1633
  product_info = await self.get_product_info(symbol)
1589
1634
  if product_info is None:
architect_py/client.pyi CHANGED
@@ -14,7 +14,7 @@ from architect_py.grpc.orderflow import OrderflowChannel as OrderflowChannel
14
14
  from architect_py.grpc.resolve_endpoint import PAPER_GRPC_PORT as PAPER_GRPC_PORT, resolve_endpoint as resolve_endpoint
15
15
  from architect_py.utils.nearest_tick import TickRoundMethod as TickRoundMethod
16
16
  from architect_py.utils.orderbook import update_orderbook_side as update_orderbook_side
17
- from architect_py.utils.pandas import candles_to_dataframe as candles_to_dataframe
17
+ from architect_py.utils.pandas import candles_to_dataframe as candles_to_dataframe, tickers_to_dataframe as tickers_to_dataframe
18
18
  from architect_py.utils.price_bands import price_band_pairs as price_band_pairs
19
19
  from architect_py.utils.symbol_parsing import nominative_expiration as nominative_expiration
20
20
  from datetime import date, datetime
@@ -353,6 +353,10 @@ class Client:
353
353
  """
354
354
  Gets the ticker for a symbol.
355
355
  """
356
+ def get_tickers(self, *, venue: Venue, symbols: Sequence[TradableProduct | str] | None = None, include_options: bool = False, sort_by: SortTickersBy | str | None = None, offset: int | None = None, limit: int | None = None, as_dataframe: bool = False) -> Sequence[Ticker] | pd.DataFrame:
357
+ """
358
+ Gets the tickers for a list of symbols.
359
+ """
356
360
  def list_accounts(self) -> list[AccountWithPermissions]:
357
361
  """
358
362
  List accounts for the user that the API key belongs to.
@@ -370,6 +374,13 @@ class Client:
370
374
  account: account uuid or name
371
375
  Examples: "00000000-0000-0000-0000-000000000000", "STONEX:000000/JDoe"
372
376
  '''
377
+ def get_positions(self, accounts: list[str] | None = None, trader: str | None = None) -> dict[str, Decimal]:
378
+ """
379
+ Get positions for the specified symbols.
380
+
381
+ Args:
382
+ symbols: list of symbol strings
383
+ """
373
384
  def get_account_summaries(self, accounts: list[str] | None = None, trader: str | None = None) -> list[AccountSummary]:
374
385
  """
375
386
  Get account summaries for accounts matching the filters.
@@ -489,7 +500,7 @@ class Client:
489
500
  trigger_price=trigger_price,
490
501
  )
491
502
  """
492
- def place_order(self, *, id: OrderId | None = None, symbol: TradableProduct | str, execution_venue: str | None = None, dir: OrderDir, quantity: Decimal, limit_price: Decimal, order_type: OrderType = ..., time_in_force: TimeInForce = ..., price_round_method: TickRoundMethod | None = None, account: str | None = None, trader: str | None = None, post_only: bool | None = None, trigger_price: Decimal | None = None, stop_loss: TriggerLimitOrderType | None = None, take_profit_price: Decimal | None = None, **kwargs: Any) -> Order:
503
+ def place_order(self, *, id: OrderId | None = None, symbol: TradableProduct | str, execution_venue: str | None = None, dir: OrderDir, quantity: Decimal, limit_price: Decimal | None = None, order_type: OrderType = ..., time_in_force: TimeInForce = ..., price_round_method: TickRoundMethod | None = None, account: str | None = None, trader: str | None = None, post_only: bool | None = None, trigger_price: Decimal | None = None, stop_loss: TriggerLimitOrderType | None = None, take_profit_price: Decimal | None = None, **kwargs: Any) -> Order:
493
504
  '''
494
505
  Sends a regular order.
495
506
 
@@ -510,7 +521,7 @@ class Client:
510
521
  While technically optional, for most order types, the account is required
511
522
  trader: the trader to send the order for, defaults to the user\'s trader
512
523
  for when sending order for another user, not relevant for vast majority of users
513
- post_only: whether the order should be post only, not supported by all exchanges
524
+ post_only: whether the order should be post only, NOT SUPPORTED BY ALL EXCHANGES (e.g. CME)
514
525
  trigger_price: the trigger price for the order, only relevant for stop / take_profit orders
515
526
  stop_loss_price: the stop loss price for a bracket order.
516
527
  profit_price: the take profit price for a bracket order.
@@ -3,23 +3,28 @@
3
3
 
4
4
  from __future__ import annotations
5
5
 
6
- from typing import Dict
6
+ from typing import Dict, Optional
7
7
 
8
8
  from msgspec import Struct
9
9
 
10
10
 
11
11
  class ConfigResponse(Struct, omit_defaults=True):
12
12
  marketdata: Dict[str, str]
13
+ symbology: Optional[str] = None
13
14
 
14
15
  # Constructor that takes all field titles as arguments for convenience
15
16
  @classmethod
16
17
  def new(
17
18
  cls,
18
19
  marketdata: Dict[str, str],
20
+ symbology: Optional[str] = None,
19
21
  ):
20
22
  return cls(
21
23
  marketdata,
24
+ symbology,
22
25
  )
23
26
 
24
27
  def __str__(self) -> str:
25
- return f"ConfigResponse(marketdata={self.marketdata})"
28
+ return (
29
+ f"ConfigResponse(marketdata={self.marketdata},symbology={self.symbology})"
30
+ )
@@ -15,7 +15,13 @@ from .. import definitions
15
15
  class AccountSummary(Struct, omit_defaults=True):
16
16
  account: str
17
17
  balances: Dict[str, Decimal]
18
- positions: Dict[str, List[definitions.AccountPosition]]
18
+ positions: Annotated[
19
+ Dict[str, List[definitions.AccountPosition]],
20
+ Meta(description="map from TradableProduct to a list of AccountPosition"),
21
+ ]
22
+ """
23
+ map from TradableProduct to a list of AccountPosition
24
+ """
19
25
  timestamp: datetime
20
26
  cash_excess: Optional[
21
27
  Annotated[Optional[Decimal], Meta(description="Cash available to withdraw.")]
@@ -136,10 +136,16 @@ class Candle(Struct, omit_defaults=True):
136
136
 
137
137
  @property
138
138
  def datetime(self) -> datetime:
139
+ """
140
+ Convenience property to get the timestamp as a datetime object in UTC.
141
+ """
139
142
  return datetime.fromtimestamp(self.ts, tz=timezone.utc)
140
143
 
141
144
  @property
142
145
  def datetime_local(self) -> datetime:
146
+ """
147
+ Convenience property to get the timestamp as a datetime object in local time.
148
+ """
143
149
  return datetime.fromtimestamp(self.ts)
144
150
 
145
151
  @property
@@ -103,10 +103,16 @@ class L1BookSnapshot(Struct, omit_defaults=True):
103
103
 
104
104
  @property
105
105
  def datetime(self) -> datetime:
106
+ """
107
+ Convenience property to get the timestamp as a datetime object in UTC.
108
+ """
106
109
  return datetime.fromtimestamp(self.ts, tz=timezone.utc)
107
110
 
108
111
  @property
109
112
  def datetime_local(self) -> datetime:
113
+ """
114
+ Convenience property to get the timestamp as a datetime object in local time.
115
+ """
110
116
  return datetime.fromtimestamp(self.ts)
111
117
 
112
118
  @property
@@ -100,8 +100,14 @@ class L2BookSnapshot(Struct, omit_defaults=True):
100
100
 
101
101
  @property
102
102
  def datetime(self) -> datetime:
103
+ """
104
+ Convenience property to get the timestamp as a datetime object in UTC.
105
+ """
103
106
  return datetime.fromtimestamp(self.ts, tz=timezone.utc)
104
107
 
105
108
  @property
106
109
  def datetime_local(self) -> datetime:
110
+ """
111
+ Convenience property to get the timestamp as a datetime object in local time.
112
+ """
107
113
  return datetime.fromtimestamp(self.ts)
@@ -93,8 +93,14 @@ class Liquidation(Struct, omit_defaults=True):
93
93
 
94
94
  @property
95
95
  def datetime(self) -> datetime:
96
+ """
97
+ Convenience property to get the timestamp as a datetime object in UTC.
98
+ """
96
99
  return datetime.fromtimestamp(self.ts, tz=timezone.utc)
97
100
 
98
101
  @property
99
102
  def datetime_local(self) -> datetime:
103
+ """
104
+ Convenience property to get the timestamp as a datetime object in local time.
105
+ """
100
106
  return datetime.fromtimestamp(self.ts)
@@ -155,10 +155,16 @@ class Ticker(Struct, omit_defaults=True):
155
155
 
156
156
  @property
157
157
  def datetime(self) -> datetime:
158
+ """
159
+ Convenience property to get the timestamp as a datetime object in UTC.
160
+ """
158
161
  return datetime.fromtimestamp(self.ts, tz=timezone.utc)
159
162
 
160
163
  @property
161
164
  def datetime_local(self) -> datetime:
165
+ """
166
+ Convenience property to get the timestamp as a datetime object in local time.
167
+ """
162
168
  return datetime.fromtimestamp(self.ts)
163
169
 
164
170
  @property
@@ -85,10 +85,16 @@ class Trade(Struct, omit_defaults=True):
85
85
 
86
86
  @property
87
87
  def datetime(self) -> datetime:
88
+ """
89
+ Convenience property to get the timestamp as a datetime object in UTC.
90
+ """
88
91
  return datetime.fromtimestamp(self.ts, tz=timezone.utc)
89
92
 
90
93
  @property
91
94
  def datetime_local(self) -> datetime:
95
+ """
96
+ Convenience property to get the timestamp as a datetime object in local time.
97
+ """
92
98
  return datetime.fromtimestamp(self.ts)
93
99
 
94
100
  @property
@@ -422,10 +422,16 @@ class L2BookDiff(Struct, omit_defaults=True):
422
422
 
423
423
  @property
424
424
  def datetime(self) -> datetime:
425
+ """
426
+ Convenience property to get the timestamp as a datetime object in UTC.
427
+ """
425
428
  return datetime.fromtimestamp(self.ts, tz=timezone.utc)
426
429
 
427
430
  @property
428
431
  def datetime_local(self) -> datetime:
432
+ """
433
+ Convenience property to get the timestamp as a datetime object in local time.
434
+ """
429
435
  return datetime.fromtimestamp(self.ts)
430
436
 
431
437
 
@@ -1854,6 +1860,20 @@ class Fill(Struct, omit_defaults=True):
1854
1860
  def trade_time(self, value: int) -> None:
1855
1861
  self.ts = value
1856
1862
 
1863
+ @property
1864
+ def datetime(self) -> datetime:
1865
+ """
1866
+ Convenience property to get the timestamp as a datetime object in UTC.
1867
+ """
1868
+ return datetime.fromtimestamp(self.ts, tz=timezone.utc)
1869
+
1870
+ @property
1871
+ def datetime_local(self) -> datetime:
1872
+ """
1873
+ Convenience property to get the timestamp as a datetime object in local time.
1874
+ """
1875
+ return datetime.fromtimestamp(self.ts)
1876
+
1857
1877
  @property
1858
1878
  def execution_venue(self) -> str:
1859
1879
  return self.x
@@ -0,0 +1,173 @@
1
+ import asyncio
2
+ from decimal import Decimal
3
+
4
+ import pytest
5
+
6
+ from architect_py.async_client import AsyncClient, OrderDir, OrderType
7
+
8
+
9
+ @pytest.mark.asyncio
10
+ @pytest.mark.timeout(10)
11
+ async def test_positions(async_client: AsyncClient):
12
+ if not async_client.paper_trading:
13
+ return
14
+
15
+ accounts = await async_client.list_accounts()
16
+
17
+ assert len(accounts) == 1, (
18
+ f"Expected exactly one account in paper trading mode, got {len(accounts)}"
19
+ )
20
+ account_id = accounts[0].account.id
21
+ front_ES_future = await async_client.get_front_future("ES CME Futures", "CME")
22
+ positions = await async_client.get_positions(accounts=[account_id])
23
+ ES_position = positions.get(front_ES_future)
24
+
25
+ # flatten position
26
+ if ES_position is not None:
27
+ flatten_direction = OrderDir.SELL if ES_position > Decimal(0) else OrderDir.BUY
28
+
29
+ order = await async_client.place_order(
30
+ symbol=front_ES_future,
31
+ venue="CME",
32
+ dir=flatten_direction,
33
+ quantity=Decimal(value="1"),
34
+ account=account_id,
35
+ order_type=OrderType.MARKET,
36
+ )
37
+ while True:
38
+ open_orders = await async_client.get_open_orders(order_ids=[order.id])
39
+ if not open_orders:
40
+ break
41
+ await asyncio.sleep(0.2)
42
+
43
+ fills = await async_client.get_fills(order_id=order.id)
44
+ assert len(fills.fills) == 1, "Expected exactly one fill for the order"
45
+ assert fills.fills[0].dir == flatten_direction, (
46
+ "Fill direction does not match order direction"
47
+ )
48
+
49
+ # go long
50
+ order = await async_client.place_order(
51
+ symbol=front_ES_future,
52
+ venue="CME",
53
+ dir=OrderDir.BUY,
54
+ quantity=Decimal(value="5"),
55
+ account=account_id,
56
+ order_type=OrderType.MARKET,
57
+ )
58
+ positions = await async_client.get_positions(accounts=[account_id])
59
+ assert positions.get(front_ES_future) == Decimal(5), (
60
+ f"Expected position in {front_ES_future} to be 5, got {positions.get(front_ES_future)}"
61
+ )
62
+
63
+ # go long to flat
64
+ order = await async_client.place_order(
65
+ symbol=front_ES_future,
66
+ venue="CME",
67
+ dir=OrderDir.SELL,
68
+ quantity=Decimal(value="5"),
69
+ account=account_id,
70
+ order_type=OrderType.MARKET,
71
+ )
72
+ positions = await async_client.get_positions(accounts=[account_id])
73
+ assert positions.get(front_ES_future) is None, (
74
+ f"Expected position in {front_ES_future} to be 0, got {positions.get(front_ES_future)}"
75
+ )
76
+
77
+ # go long
78
+ order = await async_client.place_order(
79
+ symbol=front_ES_future,
80
+ venue="CME",
81
+ dir=OrderDir.BUY,
82
+ quantity=Decimal(value="8"),
83
+ account=account_id,
84
+ order_type=OrderType.MARKET,
85
+ )
86
+ positions = await async_client.get_positions(accounts=[account_id])
87
+ assert positions.get(front_ES_future) == Decimal(8), (
88
+ f"Expected position in {front_ES_future} to be 8, got {positions.get(front_ES_future)}"
89
+ )
90
+
91
+ # go long to short
92
+ order = await async_client.place_order(
93
+ symbol=front_ES_future,
94
+ venue="CME",
95
+ dir=OrderDir.SELL,
96
+ quantity=Decimal(value="10"),
97
+ account=account_id,
98
+ order_type=OrderType.MARKET,
99
+ )
100
+ positions = await async_client.get_positions(accounts=[account_id])
101
+ assert positions.get(front_ES_future) == Decimal(-2), (
102
+ f"Expected position in {front_ES_future} to be -2, got {positions.get(front_ES_future)}"
103
+ )
104
+
105
+ # go flat
106
+ order = await async_client.place_order(
107
+ symbol=front_ES_future,
108
+ venue="CME",
109
+ dir=OrderDir.BUY,
110
+ quantity=Decimal(value="2"),
111
+ account=account_id,
112
+ order_type=OrderType.MARKET,
113
+ )
114
+ positions = await async_client.get_positions(accounts=[account_id])
115
+ assert positions.get(front_ES_future) is None, (
116
+ f"Expected position in {front_ES_future} to be 0, got {positions.get(front_ES_future)}"
117
+ )
118
+
119
+ # go short
120
+ order = await async_client.place_order(
121
+ symbol=front_ES_future,
122
+ venue="CME",
123
+ dir=OrderDir.SELL,
124
+ quantity=Decimal(value="5"),
125
+ account=account_id,
126
+ order_type=OrderType.MARKET,
127
+ )
128
+ positions = await async_client.get_positions(accounts=[account_id])
129
+ assert positions.get(front_ES_future) == Decimal(-5), (
130
+ f"Expected position in {front_ES_future} to be -5, got {positions.get(front_ES_future)}"
131
+ )
132
+
133
+ # go short to flat
134
+ order = await async_client.place_order(
135
+ symbol=front_ES_future,
136
+ venue="CME",
137
+ dir=OrderDir.BUY,
138
+ quantity=Decimal(value="5"),
139
+ account=account_id,
140
+ order_type=OrderType.MARKET,
141
+ )
142
+ positions = await async_client.get_positions(accounts=[account_id])
143
+ assert positions.get(front_ES_future) is None, (
144
+ f"Expected position in {front_ES_future} to be 0, got {positions.get(front_ES_future)}"
145
+ )
146
+
147
+ # go short
148
+ order = await async_client.place_order(
149
+ symbol=front_ES_future,
150
+ venue="CME",
151
+ dir=OrderDir.SELL,
152
+ quantity=Decimal(value="5"),
153
+ account=account_id,
154
+ order_type=OrderType.MARKET,
155
+ )
156
+ positions = await async_client.get_positions(accounts=[account_id])
157
+ assert positions.get(front_ES_future) == Decimal(-5), (
158
+ f"Expected position in {front_ES_future} to be -5, got {positions.get(front_ES_future)}"
159
+ )
160
+
161
+ # go short to long
162
+ order = await async_client.place_order(
163
+ symbol=front_ES_future,
164
+ venue="CME",
165
+ dir=OrderDir.BUY,
166
+ quantity=Decimal(value="10"),
167
+ account=account_id,
168
+ order_type=OrderType.MARKET,
169
+ )
170
+ positions = await async_client.get_positions(accounts=[account_id])
171
+ assert positions.get(front_ES_future) == Decimal(5), (
172
+ f"Expected position in {front_ES_future} to be 5, got {positions.get(front_ES_future)}"
173
+ )
@@ -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.5
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
@@ -1,7 +1,7 @@
1
- architect_py/__init__.py,sha256=0Uioyr5yLNKBcAFs-5GM69vfhriy3dEgTc4CvWXpNQU,16669
2
- architect_py/async_client.py,sha256=u6NKAi2XEn6FgVbrVhbTRAS-k01KG0CymH336Vaj5F8,64255
1
+ architect_py/__init__.py,sha256=jrffx1fAChnV8djCoFdEmsTwyHTCkw0_P3RvSimjcx8,16666
2
+ architect_py/async_client.py,sha256=jU_P4McMXGsKhUIov0CEXG27qi4SdfIVZkU3uFc-md8,65827
3
3
  architect_py/client.py,sha256=y8w17ZLo_Y2-knH-46qqVGlSJyQHB9qwOPodI9pzN-Q,5192
4
- architect_py/client.pyi,sha256=AAcbKshjX9CA1PSRTdd0wNWHYhxqGMZWVHR2q2T_9WQ,25434
4
+ architect_py/client.pyi,sha256=w8yM9u2TQvm12DHoEunq0CKTQ0EVOqrkh_iuNMf1Dk8,26113
5
5
  architect_py/common_types/__init__.py,sha256=fzOdIlKGWVN9V2Onc5z1v2bpvtZ4H9RSFA9ymJcBi3k,197
6
6
  architect_py/common_types/order_dir.py,sha256=ebyWTcXzJWrotkc2D9wNGc6JXbE5I3NLLuAz3I7FTZ8,2191
7
7
  architect_py/common_types/time_in_force.py,sha256=gEDYcNp014Eeb98zJDytiV0hGxHu_QsQndeM6Hk0Wa8,3132
@@ -29,7 +29,7 @@ architect_py/grpc/resolve_endpoint.py,sha256=r_PBWANIJJ47N5uyPcnefZ21ZE1-mzgACfC
29
29
  architect_py/grpc/server.py,sha256=Abmdfe1eYbctVgzoJYBBBLpd7UD70FbYQLtJImSyRzs,1934
30
30
  architect_py/grpc/utils.py,sha256=5sykLExUNZbcQHcxLCCM9DdOOiJJZcpputGrDtaMifY,667
31
31
  architect_py/grpc/models/__init__.py,sha256=DVsP-OURNPSlFKWxQGShF7ytvOUJc2_fQ-ng5kOh1X8,9366
32
- architect_py/grpc/models/definitions.py,sha256=abSEQX32IinuM7ZQnhC6USBWAy-JU8-JgajtYSQx1EA,82191
32
+ architect_py/grpc/models/definitions.py,sha256=fWVidMJ4pEzP7Z6bLhyYlSYxGttfH40Vgki1P9b47ow,82836
33
33
  architect_py/grpc/models/Accounts/AccountsRequest.py,sha256=1a88cltSebOb53EdJ0hKEGR7FlmBiibrCtGzLTKqDBY,1524
34
34
  architect_py/grpc/models/Accounts/AccountsResponse.py,sha256=DlXbkd3UbRybblBAfokw-K6nRvLNZgqz7cc0EKiW1zI,636
35
35
  architect_py/grpc/models/Accounts/__init__.py,sha256=sIyaEvJdP-VmGTGPPqZuRjKn4bc7NUClJ76Gd5uq-5s,57
@@ -64,7 +64,7 @@ architect_py/grpc/models/Boss/WithdrawalsRequest.py,sha256=876d2gsGbSTPZ74wCoe0n
64
64
  architect_py/grpc/models/Boss/WithdrawalsResponse.py,sha256=th6r28rjNBUuMBs95lwf9pxaxFtrwKJ_VAq7egwMVM8,632
65
65
  architect_py/grpc/models/Boss/__init__.py,sha256=sIyaEvJdP-VmGTGPPqZuRjKn4bc7NUClJ76Gd5uq-5s,57
66
66
  architect_py/grpc/models/Core/ConfigRequest.py,sha256=9taH97J4b-_Co7d-o_zHOPg_vckc_InvnADXYpd_MlM,809
67
- architect_py/grpc/models/Core/ConfigResponse.py,sha256=pQimTNwdE-FL2Z1Pq6_YRPmagt-ZgJoub9P1lpj0gVk,556
67
+ architect_py/grpc/models/Core/ConfigResponse.py,sha256=fjOEuwTyawepSaVGfLXqhmo_1ssDsDbyYkxje_SwBlU,717
68
68
  architect_py/grpc/models/Core/RestartCptyRequest.py,sha256=Hl_uSGkMFE9yPonClcNE24neeib8LP3fHH6UwpI_WSQ,916
69
69
  architect_py/grpc/models/Core/RestartCptyResponse.py,sha256=aCyJfucfFGHieGURjEejeT9HPoaKJ2xCGPVqw5pcCJs,427
70
70
  architect_py/grpc/models/Core/__init__.py,sha256=sIyaEvJdP-VmGTGPPqZuRjKn4bc7NUClJ76Gd5uq-5s,57
@@ -79,7 +79,7 @@ architect_py/grpc/models/Folio/AccountHistoryRequest.py,sha256=62zM93rALjckq8y5s
79
79
  architect_py/grpc/models/Folio/AccountHistoryResponse.py,sha256=3CUKPrqbUpH1MRYFZi2Q4dfN-nzcM6OxM_FA5pA_OyQ,622
80
80
  architect_py/grpc/models/Folio/AccountSummariesRequest.py,sha256=epYjLFc1cMblp04li_WSCIcl4bFJEjDedJNO9D2x1bg,1595
81
81
  architect_py/grpc/models/Folio/AccountSummariesResponse.py,sha256=YoJddUl0TB1pkoI7C_avt94RSL9ZCf2u8_kOYpewRGI,678
82
- architect_py/grpc/models/Folio/AccountSummary.py,sha256=MtIzJ4v78gD1jZPoa4ooV6H3sctfOf1FV2vgiUiXDp4,3633
82
+ architect_py/grpc/models/Folio/AccountSummary.py,sha256=2upTyjDCpkxIXZ40DjUmZFaBBnmIbhF1htijOoCc2D4,3816
83
83
  architect_py/grpc/models/Folio/AccountSummaryRequest.py,sha256=qu9f-liMEOqxq8LM2h9EosCj2vtTJwduUKjiwYO8GTo,1002
84
84
  architect_py/grpc/models/Folio/HistoricalFillsRequest.py,sha256=ybd6vIO1xkiovUW9Q-Pl4K21kfmFX8uMOoE7LZqsOLU,2241
85
85
  architect_py/grpc/models/Folio/HistoricalFillsResponse.py,sha256=MmHFvt-FuLgw93fHN8lfcyMsG2qHys1gUD0gq0QZGyM,775
@@ -90,16 +90,16 @@ architect_py/grpc/models/Health/HealthCheckRequest.py,sha256=uKxA8HbItw-MF-LfHk7
90
90
  architect_py/grpc/models/Health/HealthCheckResponse.py,sha256=0yh75XgiyrJdNet4xx5_u7gyW6Qx8fwTPO9h6E_HVWU,755
91
91
  architect_py/grpc/models/Health/__init__.py,sha256=sIyaEvJdP-VmGTGPPqZuRjKn4bc7NUClJ76Gd5uq-5s,57
92
92
  architect_py/grpc/models/Marketdata/ArrayOfL1BookSnapshot.py,sha256=Os13kKncZgwoCqrorTspvcj8EO7u4Xr3fNQV8mWONFM,329
93
- architect_py/grpc/models/Marketdata/Candle.py,sha256=VDGh7cDiDhu91dAxARLMJkE2CqJvOLL4m1Z8u-cK9KE,8259
93
+ architect_py/grpc/models/Marketdata/Candle.py,sha256=GIidzpVm9CwxbkIS3cJiRB_WaWQ3vwoGr_g6dSaDl9I,8472
94
94
  architect_py/grpc/models/Marketdata/HistoricalCandlesRequest.py,sha256=uJvsHOn2gXD9w4ybk1iXu4Z1LJ4g5g89y_DIwAMUChY,1587
95
95
  architect_py/grpc/models/Marketdata/HistoricalCandlesResponse.py,sha256=_LGtTw6dVMtF0U6N7l68IbxDDMcooOEW7YsJaoLtniY,604
96
- architect_py/grpc/models/Marketdata/L1BookSnapshot.py,sha256=T4dNmwvAPy0XZ-yZIufhNi4Zyhx8uT1--XPHsX6KvvI,3661
96
+ architect_py/grpc/models/Marketdata/L1BookSnapshot.py,sha256=tM-2KEhy3M8hxwfDpPLDmNYIh_cfblWZkRVk_A1k6Ww,3874
97
97
  architect_py/grpc/models/Marketdata/L1BookSnapshotRequest.py,sha256=9TxfqAivsmZgmIuIemmX6A9bTvMvVU6rWYDGi86gZZg,1072
98
98
  architect_py/grpc/models/Marketdata/L1BookSnapshotsRequest.py,sha256=TFGnuPfTcHMSO849WnEPj1a52RsVReAEWqQ9Fb3La1g,1189
99
- architect_py/grpc/models/Marketdata/L2BookSnapshot.py,sha256=MEyDSZ6LfAtyujGLRZ6pts5o7UNIp8A9_kqV6K7Y-k8,2572
99
+ architect_py/grpc/models/Marketdata/L2BookSnapshot.py,sha256=bvJJPfXMhS68J8M2U6Yb-yvLJuCLy_dUF_viG0udBWM,2785
100
100
  architect_py/grpc/models/Marketdata/L2BookSnapshotRequest.py,sha256=9qRKbwY2KLtW2F-40XOvRfT73kVPTymL0Q3JCR2EbtU,1072
101
101
  architect_py/grpc/models/Marketdata/L2BookUpdate.py,sha256=i-kWdJZvAoYEI280TfC3sTH46VIpMoj8N2UxWAkTDLs,2602
102
- architect_py/grpc/models/Marketdata/Liquidation.py,sha256=cLKfosZl0gdVuL3qoFuJ32UB7Xjk9vVhOxsoc_QNLEw,2420
102
+ architect_py/grpc/models/Marketdata/Liquidation.py,sha256=QFco-zA3xUKstAbFkJ4qn_uluQ8XK6yDApvQULsSl3Y,2633
103
103
  architect_py/grpc/models/Marketdata/MarketStatus.py,sha256=4Kt2z16t7dpjpiELWshJyyH1b-D07YdQchjGMvZkSRM,977
104
104
  architect_py/grpc/models/Marketdata/MarketStatusRequest.py,sha256=ajyI4UlvFusyM0743dukT4KFZTlp9iUh0lTGWl6n7nw,1056
105
105
  architect_py/grpc/models/Marketdata/SubscribeCandlesRequest.py,sha256=ck5pQx54uymlpR-jxFpxcW0LPDLU7R8GvqLqF-7GmoU,1508
@@ -110,12 +110,12 @@ architect_py/grpc/models/Marketdata/SubscribeLiquidationsRequest.py,sha256=6BhC4
110
110
  architect_py/grpc/models/Marketdata/SubscribeManyCandlesRequest.py,sha256=pel2GGysDsJXjPY7rkyqqyGS3MPl13YezJS7apihiFc,1512
111
111
  architect_py/grpc/models/Marketdata/SubscribeTickersRequest.py,sha256=7g2LBAYd97OJ9FrxpUvZKO7hSMng-K4KfnsN08O4XSM,1437
112
112
  architect_py/grpc/models/Marketdata/SubscribeTradesRequest.py,sha256=7P8FyNx6wijNUBGry0vaMhaEKuG1ik8kTibJBvdol2k,1299
113
- architect_py/grpc/models/Marketdata/Ticker.py,sha256=O4kJK1RyThYgfpvIr9mgRWAAkYgwwAKgOhEhbfDo9b4,11592
113
+ architect_py/grpc/models/Marketdata/Ticker.py,sha256=ti8Z3HEePBGk66DUwgZjKMpBhHrimT_Rv4VYaC7f3rs,11805
114
114
  architect_py/grpc/models/Marketdata/TickerRequest.py,sha256=Ay--5JKgCfdvlVWD2H6YSa_66NC3Dt6c-XK8JkbWhus,1008
115
115
  architect_py/grpc/models/Marketdata/TickerUpdate.py,sha256=sJ4wvCeGckMV30HwAcAsEMQbCzjN31OxF19q70jdxok,437
116
116
  architect_py/grpc/models/Marketdata/TickersRequest.py,sha256=x6UlQdAisnXgs3vbghQY3nuiFccSU3ueU0YJVAKaKUs,2359
117
117
  architect_py/grpc/models/Marketdata/TickersResponse.py,sha256=CLzKx-ItwH9-Qq8YruFhXh7TmtHwzNRMEOPJ9LQD9co,574
118
- architect_py/grpc/models/Marketdata/Trade.py,sha256=RKZq_HUDLxIE41caDrwf99V0c48kH2pm3XRCn5RLcEQ,2476
118
+ architect_py/grpc/models/Marketdata/Trade.py,sha256=NZ-pM4MUNzQvq5RwYOS0_xgmDI17AxdM4t8NKdPnGu8,2689
119
119
  architect_py/grpc/models/Marketdata/__init__.py,sha256=sIyaEvJdP-VmGTGPPqZuRjKn4bc7NUClJ76Gd5uq-5s,57
120
120
  architect_py/grpc/models/Oms/Cancel.py,sha256=P550abgbBqVbC3UE7YaOaEytF__DsTYWsepNvkHaAQE,2357
121
121
  architect_py/grpc/models/Oms/CancelAllOrdersRequest.py,sha256=oCRbluj6nyoDCHQszPDRIBt4ygFyO7QHZhCf8T8-fYM,1474
@@ -166,16 +166,17 @@ architect_py/tests/test_marketdata.py,sha256=W26OrL51ONAclBjBcm7trS1QPXtLLjdgnsb
166
166
  architect_py/tests/test_order_entry.py,sha256=5HDjzNJOC7lSx4driP4mDJr9HuR2cFTwO8s1haGXl9E,1284
167
167
  architect_py/tests/test_orderflow.py,sha256=b4iohhs7YUoJMevlUfLQyIoVqjam7pl0BPs0dSfZhqM,3951
168
168
  architect_py/tests/test_portfolio_management.py,sha256=Q4pburTDJ53hrq2_aRbNAOG3nwbCEsgZQGbI_AMHLxE,709
169
- architect_py/tests/test_rounding.py,sha256=cAQ1-tWOVgxENt0Fzs9YcFDdDDYmCtOHvAA_w76wy9g,1417
169
+ architect_py/tests/test_positions.py,sha256=zVO6qmYVn7heQt2C17deYUUCAmJ-u6cnekTmqKm8Vh0,5925
170
+ architect_py/tests/test_rounding.py,sha256=qjuPIdX6TbfPtfrzotZx6-Aodf4et7j3AswgQ7DQtm4,1363
170
171
  architect_py/tests/test_symbology.py,sha256=74fbUgoycuViMHHnurE2Dnfao75wWu_cmQMyU5XQcdY,3436
171
172
  architect_py/tests/test_sync_client.py,sha256=teaHrp-CMpKIDsGPdnyxvmuW_a3hgFftnsnPsFHz9Tw,946
172
173
  architect_py/utils/nearest_tick.py,sha256=i1cCGMSi-sP4Grbp0RCwEsoLzMWN9iC6gPMBm2daDWM,4810
173
174
  architect_py/utils/nearest_tick_2.py,sha256=f-o6b73Mo8epCIaOYBS9F0k_6UHUDSVG1N_VWg7iFBU,3641
174
175
  architect_py/utils/orderbook.py,sha256=JM02NhHbmK3sNaS2Ara8FBY4TvKvtMIzJW1oVd8KC3s,1004
175
- architect_py/utils/pandas.py,sha256=QHz2ynj4T92FobuzRaNoH3ypArHoSDCiGtZ3PVXJ2vo,1017
176
+ architect_py/utils/pandas.py,sha256=Jyiimf6Y5FbTLotUhSIgOnRHMGz7ZvAqNSCHEwZ9eQU,2599
176
177
  architect_py/utils/price_bands.py,sha256=j7ioSA3dx025CD5E2Vg7XQvmjPvxQb-gzQBfQTovpTw,21874
177
178
  architect_py/utils/symbol_parsing.py,sha256=OjJzk2c6QU2s0aJMSyVEzlWD5Vy-RlakJVW7jNHVDJk,845
178
- architect_py-5.1.4rc1.dist-info/licenses/LICENSE,sha256=6P0_5gYN8iPWPZeqA9nxiO3tRQmcSA1ijAVR7C8j1SI,11362
179
+ architect_py-5.1.5.dist-info/licenses/LICENSE,sha256=6P0_5gYN8iPWPZeqA9nxiO3tRQmcSA1ijAVR7C8j1SI,11362
179
180
  examples/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
180
181
  examples/book_subscription.py,sha256=1WFQN_QCE8cRS_CIv2k0NxqpK37fA9-Ja2Kfxs8vsb8,1461
181
182
  examples/candles.py,sha256=T71TsxbfXCT6mrJZmTgdTKesJFdQhYP_4AsiNK-8KyQ,756
@@ -199,7 +200,7 @@ scripts/preprocess_grpc_schema.py,sha256=p9LdoMZzixBSsVx7Dy3_8uJzOy_QwCoVMkAABQK
199
200
  scripts/prune_graphql_schema.py,sha256=hmfw5FD_iKGKMFkq6H1neZiXXtljFFrOwi2fiusTWE4,6210
200
201
  templates/exceptions.py,sha256=tIHbiO5Q114h9nPwJXsgHvW_bERLwxuNp9Oj41p6t3A,2379
201
202
  templates/juniper_base_client.py,sha256=B8QF4IFSwqBK5UY2aFPbSdYnX9bcwnlxLK4ojPRaW0E,12705
202
- architect_py-5.1.4rc1.dist-info/METADATA,sha256=Gd5QTs95v6beJuMU2dT-kJHkEeZWs0jmpd-i8Q4-3dk,2641
203
- architect_py-5.1.4rc1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
204
- architect_py-5.1.4rc1.dist-info/top_level.txt,sha256=UjtO97OACFQ9z5MzS-X2wBlt5Ovk1vxakQPKfokI454,40
205
- architect_py-5.1.4rc1.dist-info/RECORD,,
203
+ architect_py-5.1.5.dist-info/METADATA,sha256=mUUxF_AZbqF0dHqRcn1mivlye9rkUi1KMogf-Qqw9jQ,2638
204
+ architect_py-5.1.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
205
+ architect_py-5.1.5.dist-info/top_level.txt,sha256=UjtO97OACFQ9z5MzS-X2wBlt5Ovk1vxakQPKfokI454,40
206
+ architect_py-5.1.5.dist-info/RECORD,,