afp-sdk 0.5.0__py3-none-any.whl → 0.5.2__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.
afp/api/admin.py CHANGED
@@ -1,6 +1,7 @@
1
1
  from .. import validators
2
2
  from ..decorators import refresh_token_on_expiry
3
- from ..schemas import ExchangeProductSubmission
3
+ from ..enums import ListingState
4
+ from ..schemas import ExchangeProductListingSubmission, ExchangeProductUpdateSubmission
4
5
  from .base import ExchangeAPI
5
6
 
6
7
 
@@ -8,8 +9,26 @@ class Admin(ExchangeAPI):
8
9
  """API for AutEx administration, restricted to AutEx admins."""
9
10
 
10
11
  @refresh_token_on_expiry
11
- def approve_product(self, product_id: str) -> None:
12
- """Approves a product for trading on the exchange.
12
+ def list_product(self, product_id: str) -> None:
13
+ """Lists a product on the exchange.
14
+
15
+ The product is in private state after listing until it is revealed to the public.
16
+
17
+ Parameters
18
+ ----------
19
+ product_id : str
20
+
21
+ Raises
22
+ ------
23
+ afp.exceptions.AuthorizationError
24
+ If the configured account is not an exchange administrator.
25
+ """
26
+ product_listing = ExchangeProductListingSubmission(id=product_id)
27
+ self._exchange.list_product(product_listing)
28
+
29
+ @refresh_token_on_expiry
30
+ def reveal_product(self, product_id: str) -> None:
31
+ """Makes a product publicly available for trading on the exchange.
13
32
 
14
33
  Parameters
15
34
  ----------
@@ -20,8 +39,12 @@ class Admin(ExchangeAPI):
20
39
  afp.exceptions.AuthorizationError
21
40
  If the configured account is not an exchange administrator.
22
41
  """
23
- product = ExchangeProductSubmission(id=product_id)
24
- self._exchange.approve_product(product)
42
+ product_update = ExchangeProductUpdateSubmission(
43
+ listing_state=ListingState.PUBLIC
44
+ )
45
+ self._exchange.update_product_listing(
46
+ validators.validate_hexstr32(product_id), product_update
47
+ )
25
48
 
26
49
  @refresh_token_on_expiry
27
50
  def delist_product(self, product_id: str) -> None:
@@ -38,5 +61,9 @@ class Admin(ExchangeAPI):
38
61
  afp.exceptions.AuthorizationError
39
62
  If the configured account is not an exchange administrator.
40
63
  """
41
- value = validators.validate_hexstr32(product_id)
42
- self._exchange.delist_product(value)
64
+ product_update = ExchangeProductUpdateSubmission(
65
+ listing_state=ListingState.DELISTED
66
+ )
67
+ self._exchange.update_product_listing(
68
+ validators.validate_hexstr32(product_id), product_update
69
+ )
afp/api/product.py CHANGED
@@ -1,6 +1,7 @@
1
+ import warnings
1
2
  from datetime import datetime
2
3
  from decimal import Decimal
3
- from typing import cast
4
+ from typing import Any, cast
4
5
 
5
6
  from eth_typing.evm import ChecksumAddress
6
7
  from hexbytes import HexBytes
@@ -29,7 +30,7 @@ class Product(ClearingSystemAPI):
29
30
  ### Factories ###
30
31
 
31
32
  @convert_web3_error()
32
- def create_product(
33
+ def create(
33
34
  self,
34
35
  *,
35
36
  symbol: str,
@@ -117,15 +118,24 @@ class Product(ClearingSystemAPI):
117
118
  extended_metadata=extended_metadata,
118
119
  )
119
120
 
121
+ def create_product(self, **kwargs: Any) -> ProductSpecification:
122
+ """Deprecated alias of `Product.create`."""
123
+ warnings.warn(
124
+ "Product.create_product() is deprecated. Use Product.create() instead.",
125
+ DeprecationWarning,
126
+ stacklevel=2,
127
+ )
128
+ return self.create(**kwargs)
129
+
120
130
  ### Transactions ###
121
131
 
122
132
  @convert_web3_error(PRODUCT_REGISTRY_ABI)
123
- def register_product(self, product: ProductSpecification) -> Transaction:
133
+ def register(self, product_specification: ProductSpecification) -> Transaction:
124
134
  """Submits a product specification to the clearing system.
125
135
 
126
136
  Parameters
127
137
  ----------
128
- product : afp.schemas.ProductSpecification
138
+ product_specification : afp.schemas.ProductSpecification
129
139
 
130
140
  Returns
131
141
  -------
@@ -133,7 +143,7 @@ class Product(ClearingSystemAPI):
133
143
  Transaction parameters.
134
144
  """
135
145
  erc20_contract = ERC20(
136
- self._w3, cast(ChecksumAddress, product.collateral_asset)
146
+ self._w3, cast(ChecksumAddress, product_specification.collateral_asset)
137
147
  )
138
148
  decimals = erc20_contract.decimals()
139
149
 
@@ -142,10 +152,19 @@ class Product(ClearingSystemAPI):
142
152
  )
143
153
  return self._transact(
144
154
  product_registry_contract.register(
145
- self._convert_product_specification(product, decimals)
155
+ self._convert_product_specification(product_specification, decimals)
146
156
  )
147
157
  )
148
158
 
159
+ def register_product(self, product: ProductSpecification) -> Transaction:
160
+ """Deprecated alias of `Product.register`."""
161
+ warnings.warn(
162
+ "Product.register_product() is deprecated. Use Product.register() instead.",
163
+ DeprecationWarning,
164
+ stacklevel=2,
165
+ )
166
+ return self.register(product)
167
+
149
168
  @convert_web3_error(CLEARING_DIAMOND_ABI)
150
169
  def initiate_final_settlement(
151
170
  self, product_id: str, accounts: list[str]
@@ -181,7 +200,7 @@ class Product(ClearingSystemAPI):
181
200
  ### Views ###
182
201
 
183
202
  @convert_web3_error(PRODUCT_REGISTRY_ABI)
184
- def product_state(self, product_id: str) -> str:
203
+ def state(self, product_id: str) -> str:
185
204
  """Returns the current state of a product.
186
205
 
187
206
  Parameters
@@ -200,6 +219,15 @@ class Product(ClearingSystemAPI):
200
219
  state = product_registry_contract.state(HexBytes(product_id))
201
220
  return state.name
202
221
 
222
+ def product_state(self, product_id: str) -> str:
223
+ """Deprecated alias of `Product.state`."""
224
+ warnings.warn(
225
+ "Product.product_state() is deprecated. Use Product.state() instead.",
226
+ DeprecationWarning,
227
+ stacklevel=2,
228
+ )
229
+ return self.state(product_id)
230
+
203
231
  @convert_web3_error(PRODUCT_REGISTRY_ABI)
204
232
  def collateral_asset(self, product_id: str) -> str:
205
233
  """Returns the collateral asset of a product.
afp/api/trading.py CHANGED
@@ -1,25 +1,27 @@
1
1
  import secrets
2
+ import warnings
2
3
  from datetime import datetime
3
4
  from decimal import Decimal
4
- from typing import Generator
5
+ from typing import Generator, Iterable
5
6
 
6
7
  from web3 import Web3
7
8
 
8
9
  from .. import hashing, validators
10
+ from ..constants import DEFAULT_BATCH_SIZE
9
11
  from ..decorators import refresh_token_on_expiry
10
- from ..enums import OrderType
12
+ from ..enums import OrderSide, OrderState, OrderType, TradeState
11
13
  from ..schemas import (
12
14
  ExchangeProduct,
15
+ ExchangeProductFilter,
13
16
  Intent,
14
17
  IntentData,
15
18
  MarketDepthData,
16
19
  Order,
20
+ OrderFilter,
17
21
  OrderCancellationData,
18
22
  OrderFill,
19
23
  OrderFillFilter,
20
- OrderSide,
21
24
  OrderSubmission,
22
- TradeState,
23
25
  )
24
26
  from .base import ExchangeAPI
25
27
 
@@ -61,6 +63,7 @@ class Trading(ExchangeAPI):
61
63
  ----------
62
64
  product : afp.schemas.ExchangeProduct
63
65
  side : str
66
+ One of `BID` and `ASK`.
64
67
  limit_price : decimal.Decimal
65
68
  quantity : decimal.Decimal
66
69
  max_trading_fee_rate : decimal.Decimal
@@ -87,7 +90,7 @@ class Trading(ExchangeAPI):
87
90
  ),
88
91
  quantity=quantity,
89
92
  max_trading_fee_rate=max_trading_fee_rate,
90
- side=getattr(OrderSide, side.upper()),
93
+ side=OrderSide(side.upper()),
91
94
  good_until_time=good_until_time,
92
95
  nonce=self._generate_nonce(),
93
96
  )
@@ -161,14 +164,34 @@ class Trading(ExchangeAPI):
161
164
  )
162
165
  return self._exchange.submit_order(submission)
163
166
 
164
- def products(self) -> list[ExchangeProduct]:
167
+ def products(
168
+ self,
169
+ batch: int = 1,
170
+ batch_size: int = DEFAULT_BATCH_SIZE,
171
+ newest_first: bool = True,
172
+ ) -> list[ExchangeProduct]:
165
173
  """Retrieves the products approved for trading on the exchange.
166
174
 
175
+ If there are more than `batch_size` number of products then they can be queried
176
+ in batches.
177
+
178
+ Parameters
179
+ ----------
180
+ batch : int, optional
181
+ 1-based index of the batch of products.
182
+ batch_size : int, optional
183
+ The maximum number of products in one batch.
184
+ newest_first : bool, optional
185
+ Whether to sort products in descending or ascending order by creation time.
186
+
167
187
  Returns
168
188
  -------
169
189
  list of afp.schemas.ExchangeProduct
170
190
  """
171
- return self._exchange.get_approved_products()
191
+ filter = ExchangeProductFilter(
192
+ batch=batch, batch_size=batch_size, newest_first=newest_first
193
+ )
194
+ return self._exchange.get_approved_products(filter)
172
195
 
173
196
  def product(self, product_id: str) -> ExchangeProduct:
174
197
  """Retrieves a product for trading by its ID.
@@ -211,40 +234,106 @@ class Trading(ExchangeAPI):
211
234
  return self._exchange.get_order_by_id(value)
212
235
 
213
236
  @refresh_token_on_expiry
214
- def open_orders(self, product_id: str | None = None) -> list[Order]:
215
- """Retrieves all open and partially filled limit orders that have been submitted
216
- by the authenticated account.
237
+ def orders(
238
+ self,
239
+ *,
240
+ product_id: str | None = None,
241
+ type_: str | None = None,
242
+ states: Iterable[str] = (),
243
+ side: str | None = None,
244
+ start: datetime | None = None,
245
+ end: datetime | None = None,
246
+ batch: int = 1,
247
+ batch_size: int = DEFAULT_BATCH_SIZE,
248
+ newest_first: bool = True,
249
+ ) -> list[Order]:
250
+ """Retrieves the authenticated account's orders that match the given parameters.
251
+
252
+ If there are more than `batch_size` number of orders then they can be queried
253
+ in batches.
217
254
 
218
255
  Parameters
219
256
  ----------
220
257
  product_id : str, optional
258
+ type_ : str, optional
259
+ One of `LIMIT_ORDER` and `CANCEL_ORDER`.
260
+ states : iterable of str
261
+ Any of `RECEIVED`, `PENDING`, `OPEN`, `COMPLETED` and `REJECTED`.
262
+ side : str, optional
263
+ One of `BID` and `ASK`.
264
+ start : datetime.datetime, optional
265
+ end : datetime.datetime, optional
266
+ batch : int, optional
267
+ 1-based index of the batch of orders.
268
+ batch_size : int, optional
269
+ The maximum number of orders in one batch.
270
+ newest_first : bool, optional
271
+ Whether to sort orders in descending or ascending order by creation time.
221
272
 
222
273
  Returns
223
274
  -------
224
- list of afp.schemas.Order
275
+ list of afp.schemas.OrderFill
225
276
  """
226
- return self._exchange.get_open_orders(product_id)
277
+ filter = OrderFilter(
278
+ intent_account_id=self._authenticator.address,
279
+ product_id=product_id,
280
+ type=None if type_ is None else OrderType(type_.upper()),
281
+ states=[OrderState(state.upper()) for state in states],
282
+ side=None if side is None else OrderSide(side.upper()),
283
+ start=start,
284
+ end=end,
285
+ batch=batch,
286
+ batch_size=batch_size,
287
+ newest_first=newest_first,
288
+ )
289
+ return self._exchange.get_orders(filter)
290
+
291
+ @refresh_token_on_expiry
292
+ def open_orders(self, product_id: str | None = None) -> list[Order]:
293
+ """Deprecated alias of Trading.orders(type_="LIMIT_ORDER", states=("OPEN", "PARTIAL"))."""
294
+ warnings.warn(
295
+ "Trading.open_orders() is deprecated. Use "
296
+ 'Trading.orders(type_="LIMIT_ORDER", states=("OPEN", "PARTIAL")) instead.',
297
+ DeprecationWarning,
298
+ stacklevel=2,
299
+ )
300
+ return self.orders(
301
+ type_="limit_order", states=("open", "partial"), product_id=product_id
302
+ )
227
303
 
228
304
  @refresh_token_on_expiry
229
305
  def order_fills(
230
306
  self,
231
307
  *,
232
308
  product_id: str | None = None,
233
- margin_account_id: str | None = None,
234
309
  intent_hash: str | None = None,
235
310
  start: datetime | None = None,
236
311
  end: datetime | None = None,
312
+ trade_states: Iterable[str] = (),
313
+ batch: int = 1,
314
+ batch_size: int = DEFAULT_BATCH_SIZE,
315
+ newest_first: bool = True,
237
316
  ) -> list[OrderFill]:
238
317
  """Retrieves the authenticated account's order fills that match the given
239
318
  parameters.
240
319
 
320
+ If there are more than `batch_size` number of order fills then they can be
321
+ queried in batches.
322
+
241
323
  Parameters
242
324
  ----------
243
325
  product_id : str, optional
244
- margin_account_id : str, optional
245
326
  intent_hash : str, optional
246
327
  start : datetime.datetime, optional
247
328
  end : datetime.datetime, optional
329
+ trade_states : iterable of str
330
+ Any of `PENDING`, `CLEARED` and `REJECTED`.
331
+ batch : int, optional
332
+ 1-based index of the batch of order fills.
333
+ batch_size : int, optional
334
+ The maximum number of order fills in one batch.
335
+ newest_first : bool, optional
336
+ Whether to sort order fills in descending or ascending order by creation time.
248
337
 
249
338
  Returns
250
339
  -------
@@ -253,11 +342,13 @@ class Trading(ExchangeAPI):
253
342
  filter = OrderFillFilter(
254
343
  intent_account_id=self._authenticator.address,
255
344
  product_id=product_id,
256
- margin_account_id=margin_account_id,
257
345
  intent_hash=intent_hash,
258
346
  start=start,
259
347
  end=end,
260
- trade_state=None,
348
+ trade_states=[TradeState(state.upper()) for state in trade_states],
349
+ batch=batch,
350
+ batch_size=batch_size,
351
+ newest_first=newest_first,
261
352
  )
262
353
  return self._exchange.get_order_fills(filter)
263
354
 
@@ -266,21 +357,28 @@ class Trading(ExchangeAPI):
266
357
  self,
267
358
  *,
268
359
  product_id: str | None = None,
269
- margin_account_id: str | None = None,
270
360
  intent_hash: str | None = None,
361
+ trade_states: Iterable[str] = ("PENDING",),
271
362
  ) -> Generator[OrderFill, None, None]:
272
363
  """Subscribes to the authenticated account's new order fills that match the
273
364
  given parameters.
274
365
 
275
- Returns a generator that yields new order fills as they are published by the
276
- exchange. A new order fill gets publised as soon as there is a match in the
277
- order book, before the trade is submitted to clearing.
366
+ Returns a generator that yields order fills as they are published by the
367
+ exchange.
368
+
369
+ If `trade_states` includes `PENDING` (the default value) then a new order fill
370
+ is yielded as soon as there is a match in the order book, before the trade is
371
+ submitted to clearing.
372
+
373
+ If `trade_states` is empty or more than one trade state is specified then
374
+ updates to order fills are yielded at every state transition.
278
375
 
279
376
  Parameters
280
377
  ----------
281
378
  product_id : str, optional
282
- margin_account_id : str, optional
283
379
  intent_hash : str, optional
380
+ trade_states: iterable of str
381
+ Any of `PENDING`, `CLEARED` and `REJECTED`.
284
382
 
285
383
  Yields
286
384
  -------
@@ -289,11 +387,13 @@ class Trading(ExchangeAPI):
289
387
  filter = OrderFillFilter(
290
388
  intent_account_id=self._authenticator.address,
291
389
  product_id=product_id,
292
- margin_account_id=margin_account_id,
293
390
  intent_hash=intent_hash,
294
391
  start=None,
295
392
  end=None,
296
- trade_state=TradeState.PENDING,
393
+ trade_states=[TradeState(state.upper()) for state in trade_states],
394
+ batch=None,
395
+ batch_size=None,
396
+ newest_first=None,
297
397
  )
298
398
  yield from self._exchange.iter_order_fills(filter)
299
399
 
afp/auth.py CHANGED
@@ -52,7 +52,7 @@ class KeyfileAuthenticator(PrivateKeyAuthenticator):
52
52
 
53
53
  Parameters
54
54
  ----------
55
- keyfile : str
55
+ key_file : str
56
56
  The path to the keyfile.
57
57
  password : str
58
58
  The password for decrypting the keyfile.
afp/constants.py CHANGED
@@ -1,4 +1,5 @@
1
1
  import os
2
+ from importlib import metadata
2
3
  from types import SimpleNamespace
3
4
 
4
5
 
@@ -6,10 +7,12 @@ def _int_or_none(value: str | None) -> int | None:
6
7
  return int(value) if value is not None else None
7
8
 
8
9
 
9
- USER_AGENT = "afp-sdk"
10
+ # Venue API constants
11
+ USER_AGENT = "afp-sdk/{}".format(metadata.version("afp-sdk"))
12
+ DEFAULT_BATCH_SIZE = 50
10
13
  DEFAULT_EXCHANGE_API_VERSION = 1
11
14
 
12
- # Constants from clearing/contracts/lib/constants.sol
15
+ # Clearing System constants
13
16
  RATE_MULTIPLIER = 10**4
14
17
  FEE_RATE_MULTIPLIER = 10**6
15
18
  FULL_PRECISION_MULTIPLIER = 10**18
afp/enums.py CHANGED
@@ -1,6 +1,13 @@
1
1
  from enum import StrEnum
2
2
 
3
3
 
4
+ class ListingState(StrEnum):
5
+ PRIVATE = "PRIVATE"
6
+ PUBLIC = "PUBLIC"
7
+ READ_ONLY = "READ_ONLY"
8
+ DELISTED = "DELISTED"
9
+
10
+
4
11
  class OrderType(StrEnum):
5
12
  LIMIT_ORDER = "LIMIT_ORDER"
6
13
  CANCEL_ORDER = "CANCEL_ORDER"
afp/exchange.py CHANGED
@@ -16,10 +16,13 @@ from .exceptions import (
16
16
  from .schemas import (
17
17
  ExchangeParameters,
18
18
  ExchangeProduct,
19
- ExchangeProductSubmission,
19
+ ExchangeProductFilter,
20
+ ExchangeProductListingSubmission,
21
+ ExchangeProductUpdateSubmission,
20
22
  LoginSubmission,
21
23
  MarketDepthData,
22
24
  Order,
25
+ OrderFilter,
23
26
  OrderFill,
24
27
  OrderFillFilter,
25
28
  OrderSubmission,
@@ -50,8 +53,12 @@ class ExchangeClient:
50
53
  return ExchangeParameters(**response.json())
51
54
 
52
55
  # GET /products
53
- def get_approved_products(self) -> list[ExchangeProduct]:
54
- response = self._send_request("GET", "/products")
56
+ def get_approved_products(
57
+ self, filter: ExchangeProductFilter
58
+ ) -> list[ExchangeProduct]:
59
+ response = self._send_request(
60
+ "GET", "/products", params=filter.model_dump(exclude_none=True)
61
+ )
55
62
  return [ExchangeProduct(**item) for item in response.json()["products"]]
56
63
 
57
64
  # GET /products/{product_id}
@@ -60,14 +67,22 @@ class ExchangeClient:
60
67
  return ExchangeProduct(**response.json())
61
68
 
62
69
  # POST /products
63
- def approve_product(self, product_submission: ExchangeProductSubmission) -> None:
70
+ def list_product(
71
+ self, listing_submission: ExchangeProductListingSubmission
72
+ ) -> None:
64
73
  self._send_request(
65
- "POST", "/products", data=product_submission.model_dump_json()
74
+ "POST", "/products", data=listing_submission.model_dump_json()
66
75
  )
67
76
 
68
- # DELETE /products
69
- def delist_product(self, product_id: str) -> None:
70
- self._send_request("DELETE", f"/products/{product_id}")
77
+ # PATCH /products
78
+ def update_product_listing(
79
+ self, product_id: str, update_submission: ExchangeProductUpdateSubmission
80
+ ) -> None:
81
+ self._send_request(
82
+ "PATCH",
83
+ f"/products/{product_id}",
84
+ data=update_submission.model_dump_json(),
85
+ )
71
86
 
72
87
  # POST /orders
73
88
  def submit_order(self, order_submission: OrderSubmission) -> Order:
@@ -77,9 +92,9 @@ class ExchangeClient:
77
92
  return Order(**response.json())
78
93
 
79
94
  # GET /orders
80
- def get_open_orders(self, product_id: str | None = None) -> list[Order]:
95
+ def get_orders(self, filter: OrderFilter) -> list[Order]:
81
96
  response = self._send_request(
82
- "GET", "/orders", params=({"product_id": product_id} if product_id else {})
97
+ "GET", "/orders", params=filter.model_dump(exclude_none=True)
83
98
  )
84
99
  return [Order(**item) for item in response.json()["orders"]]
85
100
 
@@ -158,11 +173,19 @@ class ExchangeClient:
158
173
  raise AuthorizationError(http_error) from http_error
159
174
  if http_error.response.status_code == requests.codes.NOT_FOUND:
160
175
  raise NotFoundError(http_error) from http_error
161
-
162
- try:
163
- reason = response.json()["detail"]
164
- except (json.JSONDecodeError, KeyError):
165
- reason = response.text
166
- raise ValidationError(reason) from http_error
176
+ if http_error.response.status_code == requests.codes.BAD_REQUEST:
177
+ try:
178
+ reason = response.json()["detail"]
179
+ except (json.JSONDecodeError, KeyError):
180
+ reason = http_error
181
+ raise ValidationError(reason) from http_error
182
+ if http_error.response.status_code == requests.codes.UNPROCESSABLE:
183
+ try:
184
+ reason = ", ".join(err["msg"] for err in response.json()["detail"])
185
+ except (json.JSONDecodeError, KeyError, TypeError):
186
+ reason = http_error
187
+ raise ValidationError(reason) from http_error
188
+
189
+ raise ExchangeError(http_error) from http_error
167
190
 
168
191
  return response
afp/schemas.py CHANGED
@@ -1,7 +1,7 @@
1
1
  from datetime import datetime
2
2
  from decimal import Decimal
3
3
  from functools import partial
4
- from typing import Annotated, Any
4
+ from typing import Annotated, Any, Literal
5
5
 
6
6
  import inflection
7
7
  from pydantic import (
@@ -12,10 +12,11 @@ from pydantic import (
12
12
  ConfigDict,
13
13
  Field,
14
14
  PlainSerializer,
15
+ computed_field,
15
16
  )
16
17
 
17
18
  from . import validators
18
- from .enums import OrderSide, OrderState, OrderType, TradeState
19
+ from .enums import ListingState, OrderSide, OrderState, OrderType, TradeState
19
20
 
20
21
 
21
22
  # Use datetime internally but UNIX timestamp in client-server communication
@@ -40,6 +41,33 @@ class Model(BaseModel):
40
41
  return super().model_dump_json(by_alias=by_alias, **kwargs)
41
42
 
42
43
 
44
+ class PaginationFilter(Model):
45
+ batch: Annotated[None | int, Field(gt=0, exclude=True)]
46
+ batch_size: Annotated[None | int, Field(gt=0, exclude=True)]
47
+ newest_first: Annotated[None | bool, Field(exclude=True)]
48
+
49
+ @computed_field
50
+ @property
51
+ def page(self) -> None | int:
52
+ return self.batch
53
+
54
+ @computed_field
55
+ @property
56
+ def page_size(self) -> None | int:
57
+ return self.batch_size
58
+
59
+ @computed_field
60
+ @property
61
+ def sort(self) -> None | Literal["ASC", "DESC"]:
62
+ match self.newest_first:
63
+ case None:
64
+ return None
65
+ case True:
66
+ return "DESC"
67
+ case False:
68
+ return "ASC"
69
+
70
+
43
71
  # Authentication
44
72
 
45
73
 
@@ -53,23 +81,35 @@ class ExchangeParameters(Model):
53
81
  trading_fee_rate: Decimal
54
82
 
55
83
 
56
- # Trading API
84
+ # Admin API
57
85
 
58
86
 
59
- class ExchangeProductSubmission(Model):
87
+ class ExchangeProductListingSubmission(Model):
60
88
  id: Annotated[str, AfterValidator(validators.validate_hexstr32)]
61
89
 
62
90
 
91
+ class ExchangeProductUpdateSubmission(Model):
92
+ listing_state: ListingState
93
+
94
+
95
+ # Trading API
96
+
97
+
63
98
  class ExchangeProduct(Model):
64
99
  id: str
65
100
  symbol: str
66
101
  tick_size: int
67
102
  collateral_asset: str
103
+ listing_state: ListingState
68
104
 
69
105
  def __str__(self) -> str:
70
106
  return self.id
71
107
 
72
108
 
109
+ class ExchangeProductFilter(PaginationFilter):
110
+ pass
111
+
112
+
73
113
  class IntentData(Model):
74
114
  trading_protocol_id: str
75
115
  product_id: str
@@ -98,6 +138,21 @@ class Order(Model):
98
138
  intent: Intent
99
139
 
100
140
 
141
+ class OrderFilter(PaginationFilter):
142
+ intent_account_id: str
143
+ product_id: None | Annotated[str, AfterValidator(validators.validate_hexstr32)]
144
+ type: None | OrderType
145
+ states: Annotated[list[OrderState], Field(exclude=True)]
146
+ side: None | OrderSide
147
+ start: None | Timestamp
148
+ end: None | Timestamp
149
+
150
+ @computed_field
151
+ @property
152
+ def state(self) -> str | None:
153
+ return ",".join(self.states) if self.states else None
154
+
155
+
101
156
  class OrderCancellationData(Model):
102
157
  intent_hash: Annotated[str, AfterValidator(validators.validate_hexstr32)]
103
158
  nonce: int
@@ -129,16 +184,18 @@ class OrderFill(Model):
129
184
  price: Decimal
130
185
 
131
186
 
132
- class OrderFillFilter(Model):
187
+ class OrderFillFilter(PaginationFilter):
133
188
  intent_account_id: str
134
189
  product_id: None | Annotated[str, AfterValidator(validators.validate_hexstr32)]
135
- margin_account_id: (
136
- None | Annotated[str, AfterValidator(validators.validate_address)]
137
- )
138
190
  intent_hash: None | Annotated[str, AfterValidator(validators.validate_hexstr32)]
139
191
  start: None | Timestamp
140
192
  end: None | Timestamp
141
- trade_state: None | TradeState
193
+ trade_states: Annotated[list[TradeState], Field(exclude=True)]
194
+
195
+ @computed_field
196
+ @property
197
+ def trade_state(self) -> str | None:
198
+ return ",".join(self.trade_states) if self.trade_states else None
142
199
 
143
200
 
144
201
  class MarketDepthItem(Model):
@@ -197,6 +254,9 @@ class ProductSpecification(Model):
197
254
  tradeout_interval: Annotated[int, Field(ge=0)]
198
255
  extended_metadata: str
199
256
 
257
+ def __str__(self) -> str:
258
+ return self.id
259
+
200
260
 
201
261
  # Liquidation API
202
262
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: afp-sdk
3
- Version: 0.5.0
3
+ Version: 0.5.2
4
4
  Summary: Autonomous Futures Protocol Python SDK
5
5
  Keywords: autonity,web3,trading,crypto,prediction,forecast,markets
6
6
  License-Expression: MIT
@@ -1,12 +1,12 @@
1
1
  afp/__init__.py,sha256=tvvXz4miC3QI8wy4vjAAf_LtL-19eEgBRA6dnrh3LA8,349
2
2
  afp/afp.py,sha256=1xpocnWjALP6QlA-Hn7G2znttJ-ikcb2WA9sLueY3zc,9107
3
3
  afp/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
- afp/api/admin.py,sha256=5Q2V6Lde8ERwa0OvNhgez50WW6RcQH0eBA6aHPGwVtY,1243
4
+ afp/api/admin.py,sha256=6TiWCo0SB41CtXYTkTYYGQY7MrdB6OMTyU3-0J0V_7o,2170
5
5
  afp/api/base.py,sha256=5X1joEwFX4gxYUDCSTvp_hSFCfLZCktnW-UtZFxDquk,4471
6
6
  afp/api/margin_account.py,sha256=sC1DF7J3QTHR7cXcRjTG33oZswVy6qr9xnevQbrQ-ew,15256
7
- afp/api/product.py,sha256=9EGQ5_UZ0m_uIkBIukp99fYVtTIQrqupXxQQ1ATev_E,9100
8
- afp/api/trading.py,sha256=exQQ3LJA6XCbRAUBuYVjSJ5PHFi4COyqDORxC37LLfQ,10379
9
- afp/auth.py,sha256=791uNaXrmcRuLbzSzgBsro1k4EOP9N1Ez3VCChX-giw,2054
7
+ afp/api/product.py,sha256=2N4bPBahuw19GcBbBL20oQ5RFRuUxa5LOZzmN7hs39U,10156
8
+ afp/api/trading.py,sha256=RdBFcY4U6A1T24vZUqR1J8MwdhaouMYZfFqBrdbMcA0,14309
9
+ afp/auth.py,sha256=sV_9E6CgRWV1xYoppc4IdrnqNo5ZNDBIp6QF3fQbMWE,2055
10
10
  afp/bindings/__init__.py,sha256=_n9xoogYi8AAlSx_PE-wjnwP1ujVyDUwoRM0BSm243U,1271
11
11
  afp/bindings/auctioneer_facet.py,sha256=4p906zdU2lUsqpWlsiLE3dlxTPrlNpqk8DtjiQUWJ8M,23919
12
12
  afp/bindings/bankruptcy_facet.py,sha256=-Kp4Ap5PLAmo_tFrp-1s_yAh0mtHS20z3BC5YIrVjrU,12548
@@ -22,16 +22,16 @@ afp/bindings/product_registry.py,sha256=-_h786jzMCsaTqqnoxpmVgBkGf45eCUMthp_Pkqr
22
22
  afp/bindings/system_viewer.py,sha256=0FivdhpfXMrBesXcHkfO9uELyr7GiRmGe36xS5sURGE,41094
23
23
  afp/bindings/trading_protocol.py,sha256=ZloF3REbjFq9v0UGVsM0_Lk0EhfWJKdeJ0PzVEnyZo0,39573
24
24
  afp/config.py,sha256=_WKywiuty8poE1A0v46uBe1JGpfCzRlxCPamKennfpE,699
25
- afp/constants.py,sha256=XIph4R0Tx-BPw_NZJgWUtb7NdS9sYzEciFRSStm3VMs,1840
25
+ afp/constants.py,sha256=EvDhLpKBOsc8OHGm1paiUAdAetPGD4nyi13coB8rd14,1930
26
26
  afp/decorators.py,sha256=SEUQtbgPGc4iVPtBQV2eiCejcDAVImmXcI0uPXFhtJA,2774
27
- afp/enums.py,sha256=4dglBx4Amcu0GrlJn0cIj-rp9uDHZGfDEP_vMAO02xo,485
27
+ afp/enums.py,sha256=HQ9EqLEvg9EHBA_UDSyn0Lma72fYoefjHfzOcIVKyh0,616
28
28
  afp/exceptions.py,sha256=frdS-EH84K0fOf92RgRgNkTe3VII2m36XNCS8eyXLLM,390
29
- afp/exchange.py,sha256=ZhpZzq33jBGAderS9Pl_WZjnYLlECG7nyjYdX24KnRo,5760
29
+ afp/exchange.py,sha256=-QE44UZ-3Q0gsyWip4o19V4BYy283JpaKuQh7_-uS5g,6624
30
30
  afp/hashing.py,sha256=gBCWN93-ydRPlgnnorSvDQlylcnglrAypRDb-1K-20I,1949
31
31
  afp/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
32
- afp/schemas.py,sha256=bby8MKc6ALv-v8UaUrhiaKwpkXuQexA6CD8hw6urLUM,5083
32
+ afp/schemas.py,sha256=PCHoEpaU-O-C50UTrru3eRNalCLccWsTRE1AWITiIP0,6607
33
33
  afp/validators.py,sha256=zQvPu3HDu6ADnEE72EJlS5hc1-xfru78Mzd74s1u_EM,1841
34
- afp_sdk-0.5.0.dist-info/licenses/LICENSE,sha256=ZdaKItgc2ppfqta2OJV0oHpSJiK87PUxmUkUo-_0SB8,1065
35
- afp_sdk-0.5.0.dist-info/WHEEL,sha256=5h_Q-_6zWQhhADpsAD_Xpw7gFbCRK5WjOOEq0nB806Q,79
36
- afp_sdk-0.5.0.dist-info/METADATA,sha256=zO6jbifQf9v8abl1bgpTNyuWLxpn1Oz-ch6PH0yms_0,6354
37
- afp_sdk-0.5.0.dist-info/RECORD,,
34
+ afp_sdk-0.5.2.dist-info/licenses/LICENSE,sha256=ZdaKItgc2ppfqta2OJV0oHpSJiK87PUxmUkUo-_0SB8,1065
35
+ afp_sdk-0.5.2.dist-info/WHEEL,sha256=n2u5OFBbdZvCiUKAmfnY1Po2j3FB_NWfuUlt5WiAjrk,79
36
+ afp_sdk-0.5.2.dist-info/METADATA,sha256=SBHaznfY-kCneVxC6gFXrqL0YE28Wxk1LlxWsQ1-hrg,6354
37
+ afp_sdk-0.5.2.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: uv 0.8.18
2
+ Generator: uv 0.8.23
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any