tpluspy 0.3.0__tar.gz → 0.3.2__tar.gz

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 (136) hide show
  1. {tpluspy-0.3.0 → tpluspy-0.3.2}/PKG-INFO +2 -2
  2. {tpluspy-0.3.0 → tpluspy-0.3.2}/README.md +1 -1
  3. {tpluspy-0.3.0 → tpluspy-0.3.2}/setup.py +1 -1
  4. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/_cli/markets.py +7 -1
  5. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/client/market_data.py +22 -20
  6. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/client/oms/oms_admin.py +3 -0
  7. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/client/orderbook.py +8 -1
  8. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/model/klines.py +45 -12
  9. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/model/replace_order.py +4 -1
  10. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/utils/replace_order.py +1 -5
  11. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/utils/signing.py +3 -6
  12. {tpluspy-0.3.0 → tpluspy-0.3.2}/tpluspy.egg-info/PKG-INFO +2 -2
  13. {tpluspy-0.3.0 → tpluspy-0.3.2}/pyproject.toml +0 -0
  14. {tpluspy-0.3.0 → tpluspy-0.3.2}/setup.cfg +0 -0
  15. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/__init__.py +0 -0
  16. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/_cli/__init__.py +0 -0
  17. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/_cli/__main__.py +0 -0
  18. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/_cli/_context.py +0 -0
  19. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/_cli/accounts.py +0 -0
  20. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/_cli/assets.py +0 -0
  21. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/_cli/balance.py +0 -0
  22. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/_cli/debug.py +0 -0
  23. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/_cli/decimals.py +0 -0
  24. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/_cli/deposit.py +0 -0
  25. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/_cli/env.py +0 -0
  26. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/_cli/orders.py +0 -0
  27. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/_cli/params.py +0 -0
  28. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/_cli/settle.py +0 -0
  29. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/_cli/sign.py +0 -0
  30. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/_cli/stream.py +0 -0
  31. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/_cli/trades.py +0 -0
  32. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/_cli/vaults.py +0 -0
  33. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/_cli/withdraw.py +0 -0
  34. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/_cli/withdrawal.py +0 -0
  35. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/asset_metadata.py +0 -0
  36. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/cli_tools/__init__.py +0 -0
  37. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/cli_tools/options.py +0 -0
  38. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/cli_tools/output.py +0 -0
  39. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/client/__init__.py +0 -0
  40. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/client/api.py +0 -0
  41. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/client/auth.py +0 -0
  42. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/client/base.py +0 -0
  43. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/client/blockchain.py +0 -0
  44. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/client/clearingengine/__init__.py +0 -0
  45. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/client/clearingengine/admin.py +0 -0
  46. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/client/clearingengine/admin_settlement.py +0 -0
  47. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/client/clearingengine/assetregistry.py +0 -0
  48. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/client/clearingengine/base.py +0 -0
  49. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/client/clearingengine/cross_venue.py +0 -0
  50. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/client/clearingengine/decimal.py +0 -0
  51. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/client/clearingengine/vault.py +0 -0
  52. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/client/oms/__init__.py +0 -0
  53. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/client/oms/assetregistry.py +0 -0
  54. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/client/withdrawal.py +0 -0
  55. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/constants.py +0 -0
  56. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/evm/__init__.py +0 -0
  57. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/evm/abi.py +0 -0
  58. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/evm/address.py +0 -0
  59. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/evm/constants.py +0 -0
  60. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/evm/contracts.py +0 -0
  61. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/evm/dev/__init__.py +0 -0
  62. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/evm/dev/contracts.py +0 -0
  63. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/evm/dev/env.py +0 -0
  64. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/evm/dev/token.py +0 -0
  65. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/evm/exceptions.py +0 -0
  66. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/evm/managers/__init__.py +0 -0
  67. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/evm/managers/chaindata.py +0 -0
  68. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/evm/managers/credential_manager.py +0 -0
  69. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/evm/managers/deposit.py +0 -0
  70. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/evm/managers/evm.py +0 -0
  71. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/evm/managers/registry.py +0 -0
  72. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/evm/managers/settle.py +0 -0
  73. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/evm/managers/vault.py +0 -0
  74. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/evm/managers/withdraw.py +0 -0
  75. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/evm/utils.py +0 -0
  76. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/exceptions.py +0 -0
  77. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/logger.py +0 -0
  78. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/managers/__init__.py +0 -0
  79. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/managers/base.py +0 -0
  80. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/model/__init__.py +0 -0
  81. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/model/approval.py +0 -0
  82. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/model/asset_identifier.py +0 -0
  83. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/model/batch_order.py +0 -0
  84. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/model/cancel_order.py +0 -0
  85. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/model/chain_address.py +0 -0
  86. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/model/close_all_positions_preview.py +0 -0
  87. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/model/config.py +0 -0
  88. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/model/interest_rates.py +0 -0
  89. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/model/limit_order.py +0 -0
  90. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/model/market.py +0 -0
  91. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/model/market_order.py +0 -0
  92. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/model/multisig.py +0 -0
  93. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/model/order.py +0 -0
  94. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/model/order_id.py +0 -0
  95. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/model/order_trigger.py +0 -0
  96. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/model/orderbook.py +0 -0
  97. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/model/pagination.py +0 -0
  98. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/model/position.py +0 -0
  99. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/model/risk_parameters.py +0 -0
  100. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/model/settlement.py +0 -0
  101. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/model/signed_message.py +0 -0
  102. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/model/trades.py +0 -0
  103. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/model/types.py +0 -0
  104. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/model/user_event.py +0 -0
  105. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/model/user_inventory.py +0 -0
  106. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/model/user_margin.py +0 -0
  107. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/model/user_simulated_margin.py +0 -0
  108. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/model/user_solvency.py +0 -0
  109. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/model/withdrawal.py +0 -0
  110. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/py.typed +0 -0
  111. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/types.py +0 -0
  112. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/utils/__init__.py +0 -0
  113. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/utils/address.py +0 -0
  114. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/utils/amount.py +0 -0
  115. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/utils/bytes32.py +0 -0
  116. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/utils/decimals.py +0 -0
  117. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/utils/domain.py +0 -0
  118. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/utils/hex.py +0 -0
  119. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/utils/limit_order.py +0 -0
  120. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/utils/market_order.py +0 -0
  121. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/utils/operator.py +0 -0
  122. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/utils/serializers.py +0 -0
  123. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/utils/timeout.py +0 -0
  124. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/utils/user/__init__.py +0 -0
  125. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/utils/user/decrypt.py +0 -0
  126. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/utils/user/ed_keyfile.py +0 -0
  127. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/utils/user/manager.py +0 -0
  128. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/utils/user/model.py +0 -0
  129. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/utils/user/token_cache.py +0 -0
  130. {tpluspy-0.3.0 → tpluspy-0.3.2}/tplus/utils/user/validate.py +0 -0
  131. {tpluspy-0.3.0 → tpluspy-0.3.2}/tpluspy.egg-info/SOURCES.txt +0 -0
  132. {tpluspy-0.3.0 → tpluspy-0.3.2}/tpluspy.egg-info/dependency_links.txt +0 -0
  133. {tpluspy-0.3.0 → tpluspy-0.3.2}/tpluspy.egg-info/entry_points.txt +0 -0
  134. {tpluspy-0.3.0 → tpluspy-0.3.2}/tpluspy.egg-info/not-zip-safe +0 -0
  135. {tpluspy-0.3.0 → tpluspy-0.3.2}/tpluspy.egg-info/requires.txt +0 -0
  136. {tpluspy-0.3.0 → tpluspy-0.3.2}/tpluspy.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tpluspy
3
- Version: 0.3.0
3
+ Version: 0.3.2
4
4
  Summary: tpluspy: Client utilities for interacting with tplus
5
5
  Home-page: https://github.com/tpluslabs/tpluspy
6
6
  Author: TPlus Labs
@@ -264,7 +264,7 @@ async for trade in client.stream_finalized_trades():
264
264
  # Other available streams:
265
265
  # client.stream_orders() -> OrderEvent
266
266
  # client.stream_all_trades() -> TradeEvent
267
- # client.stream_klines(asset_id) -> KlineUpdate
267
+ # client.stream_klines(asset_id) -> Timebar
268
268
  ```
269
269
 
270
270
  See `examples/websocket_usage.py` for a runnable demonstration using `asyncio.gather` to run multiple streams concurrently.
@@ -193,7 +193,7 @@ async for trade in client.stream_finalized_trades():
193
193
  # Other available streams:
194
194
  # client.stream_orders() -> OrderEvent
195
195
  # client.stream_all_trades() -> TradeEvent
196
- # client.stream_klines(asset_id) -> KlineUpdate
196
+ # client.stream_klines(asset_id) -> Timebar
197
197
  ```
198
198
 
199
199
  See `examples/websocket_usage.py` for a runnable demonstration using `asyncio.gather` to run multiple streams concurrently.
@@ -8,7 +8,7 @@ long_description = (here / "README.md").read_text(encoding="utf-8")
8
8
 
9
9
  setup(
10
10
  name="tpluspy",
11
- version="0.3.0",
11
+ version="0.3.2",
12
12
  description="""tpluspy: Client utilities for interacting with tplus""",
13
13
  long_description=long_description,
14
14
  long_description_content_type="text/markdown",
@@ -120,6 +120,7 @@ def _depth(cli_ctx: CLIContext, asset_id: str):
120
120
  @click.option("--page", type=int)
121
121
  @click.option("--limit", type=int)
122
122
  @click.option("--end-timestamp-ns", "end_timestamp_ns", type=int)
123
+ @click.option("--interval", help="Bucket width, e.g. 1, 5m, 4h, 1D, 1W or 1M (a month).")
123
124
  @pass_cli_context
124
125
  def _klines(
125
126
  cli_ctx: CLIContext,
@@ -129,6 +130,7 @@ def _klines(
129
130
  page: int | None,
130
131
  limit: int | None,
131
132
  end_timestamp_ns: int | None,
133
+ interval: str | None,
132
134
  ):
133
135
  """Get candlestick data for ASSET_ID."""
134
136
  from tplus.model.asset_identifier import AssetIdentifier
@@ -136,7 +138,11 @@ def _klines(
136
138
  client = cli_ctx.market_data_client()
137
139
  klines = asyncio.run(
138
140
  client.get_klines(
139
- AssetIdentifier(asset_id), page=page, limit=limit, end_timestamp_ns=end_timestamp_ns
141
+ AssetIdentifier(asset_id),
142
+ page=page,
143
+ limit=limit,
144
+ end_timestamp_ns=end_timestamp_ns,
145
+ interval=interval,
140
146
  )
141
147
  )
142
148
  if output_format == "raw":
@@ -7,7 +7,13 @@ from tplus.client.auth import AuthenticatedClient
7
7
  from tplus.client.base import page_params
8
8
  from tplus.exceptions import NotFoundError
9
9
  from tplus.model.asset_identifier import AssetIdentifier
10
- from tplus.model.klines import KlinesPage, KlineUpdate, parse_kline_update, parse_klines_page
10
+ from tplus.model.klines import (
11
+ Interval,
12
+ KlinesPage,
13
+ Timebar,
14
+ parse_klines_page,
15
+ parse_timebars,
16
+ )
11
17
  from tplus.model.order import OrderResponse, parse_orders
12
18
  from tplus.model.orderbook import OrderBook, OrderBookDiff
13
19
  from tplus.model.trades import (
@@ -25,17 +31,6 @@ from tplus.types import UserType
25
31
  DEFAULT_BASE_URL = "http://localhost:8011"
26
32
 
27
33
 
28
- def _pagination(page: int | None, limit: int | None) -> dict[str, Any]:
29
- params: dict[str, Any] = {}
30
- if page:
31
- params["page"] = page
32
-
33
- if limit:
34
- params["limit"] = limit
35
-
36
- return params
37
-
38
-
39
34
  class MarketDataClient(AuthenticatedClient):
40
35
  """Klines, order-book depth, public trades and 24h tickers (REST + WS streams).
41
36
 
@@ -63,12 +58,19 @@ class MarketDataClient(AuthenticatedClient):
63
58
  page: int | None = None,
64
59
  limit: int | None = None,
65
60
  end_timestamp_ns: int | None = None,
61
+ interval: Interval | str | None = None,
66
62
  ) -> KlinesPage:
67
- """A page of k-line (candlestick) data for `asset_id`, with pagination metadata."""
68
- params = _pagination(page, limit)
69
- if end_timestamp_ns:
70
- params["end_timestamp_ns"] = end_timestamp_ns
63
+ """A page of k-line (candlestick) data for `asset_id`, with pagination metadata.
71
64
 
65
+ `interval` asks for wider candlesticks, e.g. `Interval.HOUR_4`. A raw string is
66
+ passed through, so the suffixed forms (`4h`, `1d`) also work.
67
+ """
68
+ params = page_params(
69
+ page,
70
+ limit,
71
+ end_timestamp_ns=end_timestamp_ns,
72
+ interval=str(interval) if interval is not None else None,
73
+ )
72
74
  response = await self._get(f"/klines/{asset_id}", params=params, requires_auth=False)
73
75
  if not isinstance(response, dict | list):
74
76
  raise ValueError(f"Invalid response from get_klines: {response}")
@@ -93,7 +95,7 @@ class MarketDataClient(AuthenticatedClient):
93
95
 
94
96
  async def get_trades(self, page: int | None = None, limit: int | None = None) -> list[Trade]:
95
97
  """Confirmed trades across all markets."""
96
- response = await self._get("/trades", params=_pagination(page, limit), requires_auth=False)
98
+ response = await self._get("/trades", params=page_params(page, limit), requires_auth=False)
97
99
  if not isinstance(response, list):
98
100
  raise ValueError(f"Invalid response from get_trades: {response}")
99
101
 
@@ -104,7 +106,7 @@ class MarketDataClient(AuthenticatedClient):
104
106
  ) -> list[Trade]:
105
107
  """Confirmed trades for `asset_id`."""
106
108
  response = await self._get(
107
- f"/trades/{asset_id}", params=_pagination(page, limit), requires_auth=False
109
+ f"/trades/{asset_id}", params=page_params(page, limit), requires_auth=False
108
110
  )
109
111
  if not isinstance(response, list):
110
112
  raise ValueError(f"Invalid response from get_trades_for_asset: {response}")
@@ -328,9 +330,9 @@ class MarketDataClient(AuthenticatedClient):
328
330
  async for diff in self._stream_ws(path, lambda d: OrderBookDiff(**d), requires_auth=False):
329
331
  yield diff
330
332
 
331
- async def stream_klines(self, asset_id: AssetIdentifier) -> AsyncIterator[KlineUpdate]:
333
+ async def stream_klines(self, asset_id: AssetIdentifier) -> AsyncIterator[Timebar]:
332
334
  """Candlestick (kline) updates for `asset_id`."""
333
335
  async for kline in self._stream_ws(
334
- f"/klines/diff/{asset_id}", parse_kline_update, requires_auth=False
336
+ f"/klines/diff/{asset_id}", parse_timebars, requires_auth=False
335
337
  ):
336
338
  yield kline
@@ -8,6 +8,7 @@ class OmsAdminClient(AuthenticatedClient):
8
8
  solvency_verifier: str,
9
9
  auto_reduce_enabled: bool,
10
10
  interest_reservation_enabled: bool | None = None,
11
+ liquidation_monitor_enabled: bool | None = None,
11
12
  ):
12
13
  config = {
13
14
  "solvency_verifier": solvency_verifier,
@@ -15,6 +16,8 @@ class OmsAdminClient(AuthenticatedClient):
15
16
  }
16
17
  if interest_reservation_enabled is not None:
17
18
  config["interest_reservation_enabled"] = interest_reservation_enabled
19
+ if liquidation_monitor_enabled is not None:
20
+ config["liquidation_monitor_enabled"] = liquidation_monitor_enabled
18
21
 
19
22
  await self._post(
20
23
  "admin/settings/modify",
@@ -770,6 +770,10 @@ class OrderBookClient(AuthenticatedClient):
770
770
  content["order"].get("base_asset"), str
771
771
  ):
772
772
  asset_id = content["order"]["base_asset"]
773
+ elif isinstance(content.get("request"), dict) and isinstance(
774
+ content["request"].get("base_asset"), str
775
+ ):
776
+ asset_id = content["request"]["base_asset"]
773
777
  if asset_id is None:
774
778
  raise ValueError("WS control payload missing asset_id")
775
779
  key = f"{response_variant}:{asset_id}:{expected_order_id}"
@@ -1123,7 +1127,10 @@ class OrderBookClient(AuthenticatedClient):
1123
1127
  return parsed_data
1124
1128
 
1125
1129
  async def set_mds_export(self, enabled: bool, user: UserType | None = None) -> dict[str, Any]:
1126
- """Set whether the user's trade history is exported to the market-data service."""
1130
+ """Set whether the user's trade history is exported to the market-data service.
1131
+
1132
+ Export is on by default; pass ``enabled=False`` to opt out.
1133
+ """
1127
1134
  public_key = self._validate_user_public_key(user=user)
1128
1135
  return await self._put(
1129
1136
  f"/account/{public_key}/mds-export",
@@ -1,17 +1,50 @@
1
1
  import datetime
2
2
  from decimal import Decimal
3
+ from enum import Enum
3
4
  from typing import Any
4
5
 
5
6
  from pydantic import BaseModel
6
7
 
7
8
 
8
- class KlineUpdate(BaseModel):
9
- """Represents a single K-line (candlestick) update from the WebSocket stream."""
9
+ class Interval(str, Enum):
10
+ """Kline bucket width, mirroring `orderbook_messages::interval::Interval`.
11
+
12
+ Values are the canonical spellings the server round-trips; it also accepts the
13
+ suffixed forms (`5m`, `4h`, `1d`) on the wire. Only `1M` is case-sensitive: it is a
14
+ month, while `1m` is a minute.
15
+ """
16
+
17
+ SEC_1 = "1S"
18
+ SEC_5 = "5S"
19
+ SEC_15 = "15S"
20
+ SEC_30 = "30S"
21
+ MIN_1 = "1"
22
+ MIN_3 = "3"
23
+ MIN_5 = "5"
24
+ MIN_15 = "15"
25
+ MIN_30 = "30"
26
+ HOUR_1 = "60"
27
+ HOUR_2 = "120"
28
+ HOUR_4 = "240"
29
+ HOUR_6 = "360"
30
+ HOUR_8 = "480"
31
+ HOUR_12 = "720"
32
+ DAY_1 = "1D"
33
+ DAY_3 = "3D"
34
+ WEEK_1 = "1W"
35
+ MONTH_1 = "1M"
36
+
37
+ def __str__(self) -> str:
38
+ return self.value
39
+
40
+
41
+ class Timebar(BaseModel):
42
+ """One candlestick, mirroring `orderbook_messages::market_data::Timebar`."""
10
43
 
11
44
  open: Decimal
12
- high: Decimal
13
- low: Decimal
14
45
  close: Decimal
46
+ low: Decimal
47
+ high: Decimal
15
48
  volume: Decimal
16
49
 
17
50
  open_timestamp_ns: int
@@ -35,7 +68,7 @@ class KlineUpdate(BaseModel):
35
68
  class KlinesPage(BaseModel):
36
69
  """One page of klines plus pagination metadata (the `Page<Timebar>` envelope)."""
37
70
 
38
- items: list[KlineUpdate]
71
+ items: list[Timebar]
39
72
  page: int
40
73
  limit: int
41
74
  total_pages: int
@@ -44,11 +77,11 @@ class KlinesPage(BaseModel):
44
77
  next_page: int | None = None
45
78
 
46
79
 
47
- def parse_kline_update(data: list[dict[str, Any]]) -> list[KlineUpdate]:
48
- """Parses a list of kline dictionaries into a List of KlineUpdate object."""
80
+ def parse_timebars(data: list[dict[str, Any]]) -> list[Timebar]:
81
+ """Parses a list of kline dictionaries into Timebar objects."""
49
82
  try:
50
83
  return [
51
- KlineUpdate(
84
+ Timebar(
52
85
  open=Decimal(item["open"]),
53
86
  high=Decimal(item["high"]),
54
87
  low=Decimal(item["low"]),
@@ -60,14 +93,14 @@ def parse_kline_update(data: list[dict[str, Any]]) -> list[KlineUpdate]:
60
93
  for item in data
61
94
  ]
62
95
  except (KeyError, ValueError, TypeError) as e:
63
- print(f"Error parsing KlineUpdate: {e}. Data: {data}")
64
- raise ValueError(f"Invalid KlineUpdate data received: {data}") from e
96
+ print(f"Error parsing Timebar: {e}. Data: {data}")
97
+ raise ValueError(f"Invalid Timebar data received: {data}") from e
65
98
 
66
99
 
67
100
  def parse_klines_page(data: dict[str, Any] | list[dict[str, Any]]) -> KlinesPage:
68
101
  """Parse the `/klines` page envelope, tolerating a bare list from older servers."""
69
102
  if isinstance(data, list):
70
- items = parse_kline_update(data)
103
+ items = parse_timebars(data)
71
104
  count = len(items)
72
105
  return KlinesPage(
73
106
  items=items,
@@ -79,7 +112,7 @@ def parse_klines_page(data: dict[str, Any] | list[dict[str, Any]]) -> KlinesPage
79
112
  )
80
113
 
81
114
  return KlinesPage(
82
- items=parse_kline_update(data.get("items", [])),
115
+ items=parse_timebars(data.get("items", [])),
83
116
  page=int(data.get("page", 0)),
84
117
  limit=int(data.get("limit", 0)),
85
118
  total_pages=int(data.get("total_pages", 0)),
@@ -1,8 +1,9 @@
1
1
  from typing import Any
2
2
 
3
- from pydantic import BaseModel, model_serializer
3
+ from pydantic import BaseModel, Field, model_serializer
4
4
 
5
5
  from tplus.model.asset_identifier import AssetIdentifier
6
+ from tplus.model.multisig import AdditionalSigner
6
7
  from tplus.model.order_id import UserOrderId
7
8
  from tplus.model.order_trigger import TriggerAbove, TriggerBelow
8
9
 
@@ -44,6 +45,7 @@ class ReplaceOrderRequestPayload(BaseModel):
44
45
  user_id: str # Added user_id field
45
46
  signature: list[int] # Signature of the 'request' (ReplaceOrderDetails)
46
47
  post_sign_timestamp: int
48
+ additional_signers: list[AdditionalSigner] = Field(default_factory=list)
47
49
 
48
50
  @model_serializer
49
51
  def serialize_model(self) -> dict[str, Any]:
@@ -55,4 +57,5 @@ class ReplaceOrderRequestPayload(BaseModel):
55
57
  "signer": self.user_id, # Added user_id to serialization
56
58
  "signature": self.signature,
57
59
  "post_sign_timestamp": self.post_sign_timestamp,
60
+ "additional_signers": self.additional_signers,
58
61
  }
@@ -56,15 +56,11 @@ def create_replace_order_ob_request_payload(
56
56
  sign_payload_json.replace(" ", "").replace("\r", "").replace("\n", "")
57
57
  )
58
58
  signature, additional_signers = signer.signing_parts(compact_sign_payload_json)
59
- if additional_signers:
60
- raise ValueError(
61
- "Order replacement does not support additional signers until its wire format "
62
- "can identify replacement co-signatures."
63
- )
64
59
 
65
60
  return ReplaceOrderRequestPayload(
66
61
  request=replace_details,
67
62
  user_id=signer.public_key,
68
63
  signature=signature,
69
64
  post_sign_timestamp=time.time_ns(),
65
+ additional_signers=additional_signers,
70
66
  )
@@ -22,12 +22,9 @@ def create_cancel_order_ob_request_payload(
22
22
  compact_sign_payload_json = (
23
23
  sign_payload_json.replace(" ", "").replace("\r", "").replace("\n", "")
24
24
  )
25
- signature, additional_signers = signer.signing_parts(compact_sign_payload_json)
26
- if additional_signers:
27
- raise ValueError(
28
- "Order cancellation does not support additional signers until its wire format "
29
- "can identify cancellation co-signatures."
30
- )
25
+ # Delegated cancels are authorized by the authenticated OMS session and account
26
+ # ownership. They do not reach the clearing engine, so no delegated signature is sent.
27
+ signature, _ = signer.signing_parts(compact_sign_payload_json)
31
28
 
32
29
  return CancelOrderRequest(
33
30
  cancel=cancel, signature=signature, post_sign_timestamp=time.time_ns()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tpluspy
3
- Version: 0.3.0
3
+ Version: 0.3.2
4
4
  Summary: tpluspy: Client utilities for interacting with tplus
5
5
  Home-page: https://github.com/tpluslabs/tpluspy
6
6
  Author: TPlus Labs
@@ -264,7 +264,7 @@ async for trade in client.stream_finalized_trades():
264
264
  # Other available streams:
265
265
  # client.stream_orders() -> OrderEvent
266
266
  # client.stream_all_trades() -> TradeEvent
267
- # client.stream_klines(asset_id) -> KlineUpdate
267
+ # client.stream_klines(asset_id) -> Timebar
268
268
  ```
269
269
 
270
270
  See `examples/websocket_usage.py` for a runnable demonstration using `asyncio.gather` to run multiple streams concurrently.
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes