afp-sdk 0.5.2__tar.gz → 0.5.4__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 (57) hide show
  1. {afp_sdk-0.5.2 → afp_sdk-0.5.4}/CHANGELOG.md +23 -0
  2. {afp_sdk-0.5.2 → afp_sdk-0.5.4}/PKG-INFO +1 -1
  3. {afp_sdk-0.5.2 → afp_sdk-0.5.4}/afp/api/trading.py +100 -7
  4. {afp_sdk-0.5.2 → afp_sdk-0.5.4}/afp/enums.py +1 -0
  5. {afp_sdk-0.5.2 → afp_sdk-0.5.4}/afp/exceptions.py +4 -0
  6. {afp_sdk-0.5.2 → afp_sdk-0.5.4}/afp/exchange.py +28 -0
  7. {afp_sdk-0.5.2 → afp_sdk-0.5.4}/afp/schemas.py +12 -1
  8. {afp_sdk-0.5.2 → afp_sdk-0.5.4}/pyproject.toml +1 -1
  9. {afp_sdk-0.5.2 → afp_sdk-0.5.4}/tests/test_base_api.py +3 -1
  10. {afp_sdk-0.5.2 → afp_sdk-0.5.4}/uv.lock +1 -1
  11. {afp_sdk-0.5.2 → afp_sdk-0.5.4}/.env.template +0 -0
  12. {afp_sdk-0.5.2 → afp_sdk-0.5.4}/.envrc +0 -0
  13. {afp_sdk-0.5.2 → afp_sdk-0.5.4}/LICENSE +0 -0
  14. {afp_sdk-0.5.2 → afp_sdk-0.5.4}/README.md +0 -0
  15. {afp_sdk-0.5.2 → afp_sdk-0.5.4}/afp/__init__.py +0 -0
  16. {afp_sdk-0.5.2 → afp_sdk-0.5.4}/afp/afp.py +0 -0
  17. {afp_sdk-0.5.2 → afp_sdk-0.5.4}/afp/api/__init__.py +0 -0
  18. {afp_sdk-0.5.2 → afp_sdk-0.5.4}/afp/api/admin.py +0 -0
  19. {afp_sdk-0.5.2 → afp_sdk-0.5.4}/afp/api/base.py +0 -0
  20. {afp_sdk-0.5.2 → afp_sdk-0.5.4}/afp/api/margin_account.py +0 -0
  21. {afp_sdk-0.5.2 → afp_sdk-0.5.4}/afp/api/product.py +0 -0
  22. {afp_sdk-0.5.2 → afp_sdk-0.5.4}/afp/auth.py +0 -0
  23. {afp_sdk-0.5.2 → afp_sdk-0.5.4}/afp/bindings/__init__.py +0 -0
  24. {afp_sdk-0.5.2 → afp_sdk-0.5.4}/afp/bindings/auctioneer_facet.py +0 -0
  25. {afp_sdk-0.5.2 → afp_sdk-0.5.4}/afp/bindings/bankruptcy_facet.py +0 -0
  26. {afp_sdk-0.5.2 → afp_sdk-0.5.4}/afp/bindings/clearing_facet.py +0 -0
  27. {afp_sdk-0.5.2 → afp_sdk-0.5.4}/afp/bindings/erc20.py +0 -0
  28. {afp_sdk-0.5.2 → afp_sdk-0.5.4}/afp/bindings/facade.py +0 -0
  29. {afp_sdk-0.5.2 → afp_sdk-0.5.4}/afp/bindings/final_settlement_facet.py +0 -0
  30. {afp_sdk-0.5.2 → afp_sdk-0.5.4}/afp/bindings/margin_account.py +0 -0
  31. {afp_sdk-0.5.2 → afp_sdk-0.5.4}/afp/bindings/margin_account_registry.py +0 -0
  32. {afp_sdk-0.5.2 → afp_sdk-0.5.4}/afp/bindings/mark_price_tracker_facet.py +0 -0
  33. {afp_sdk-0.5.2 → afp_sdk-0.5.4}/afp/bindings/oracle_provider.py +0 -0
  34. {afp_sdk-0.5.2 → afp_sdk-0.5.4}/afp/bindings/product_registry.py +0 -0
  35. {afp_sdk-0.5.2 → afp_sdk-0.5.4}/afp/bindings/system_viewer.py +0 -0
  36. {afp_sdk-0.5.2 → afp_sdk-0.5.4}/afp/bindings/trading_protocol.py +0 -0
  37. {afp_sdk-0.5.2 → afp_sdk-0.5.4}/afp/config.py +0 -0
  38. {afp_sdk-0.5.2 → afp_sdk-0.5.4}/afp/constants.py +0 -0
  39. {afp_sdk-0.5.2 → afp_sdk-0.5.4}/afp/decorators.py +0 -0
  40. {afp_sdk-0.5.2 → afp_sdk-0.5.4}/afp/hashing.py +0 -0
  41. {afp_sdk-0.5.2 → afp_sdk-0.5.4}/afp/py.typed +0 -0
  42. {afp_sdk-0.5.2 → afp_sdk-0.5.4}/afp/validators.py +0 -0
  43. {afp_sdk-0.5.2 → afp_sdk-0.5.4}/devenv.lock +0 -0
  44. {afp_sdk-0.5.2 → afp_sdk-0.5.4}/devenv.nix +0 -0
  45. {afp_sdk-0.5.2 → afp_sdk-0.5.4}/devenv.yaml +0 -0
  46. {afp_sdk-0.5.2 → afp_sdk-0.5.4}/examples/cancel_order.py +0 -0
  47. {afp_sdk-0.5.2 → afp_sdk-0.5.4}/examples/create_product.py +0 -0
  48. {afp_sdk-0.5.2 → afp_sdk-0.5.4}/examples/execute_trade.py +0 -0
  49. {afp_sdk-0.5.2 → afp_sdk-0.5.4}/tests/__init__.py +0 -0
  50. {afp_sdk-0.5.2 → afp_sdk-0.5.4}/tests/assets/test.key +0 -0
  51. {afp_sdk-0.5.2 → afp_sdk-0.5.4}/tests/test_afp.py +0 -0
  52. {afp_sdk-0.5.2 → afp_sdk-0.5.4}/tests/test_decorators.py +0 -0
  53. {afp_sdk-0.5.2 → afp_sdk-0.5.4}/tests/test_exchange_client.py +0 -0
  54. {afp_sdk-0.5.2 → afp_sdk-0.5.4}/tests/test_hashing.py +0 -0
  55. {afp_sdk-0.5.2 → afp_sdk-0.5.4}/tests/test_schemas.py +0 -0
  56. {afp_sdk-0.5.2 → afp_sdk-0.5.4}/tests/test_signing.py +0 -0
  57. {afp_sdk-0.5.2 → afp_sdk-0.5.4}/tests/test_validators.py +0 -0
@@ -1,3 +1,24 @@
1
+ ## [v0.5.4] - 2025-11-05
2
+
3
+ ### Added
4
+
5
+ - Add `afp.exceptions.RateLimitExceeded` exception type ([#37](https://github.com/autonity/afp-sdk/pull/37))
6
+ - Add trading fee rate to `afp.schemas.OrderFill` structure ([#39](https://github.com/autonity/afp-sdk/pull/39))
7
+
8
+ ### Changed
9
+
10
+ - Update for interface changes in the AutEx exchange ([#36](https://github.com/autonity/afp-sdk/pull/36))
11
+
12
+ ## [v0.5.3] - 2025-10-29
13
+
14
+ ### Added
15
+
16
+ - Add `Trading.ohlcv()` and `Trading.iter_ohlcv()` methods for querying OHLCV time series data ([#34](https://github.com/autonity/afp-sdk/pull/34))
17
+
18
+ ### Changed
19
+
20
+ - Change `Trading.orders()`, `Trading.order_fills()` and `Trading.iter_order_fills()` to allow filtering items by intent account ID ([#33](https://github.com/autonity/afp-sdk/pull/33))
21
+
1
22
  ## [v0.5.2] - 2025-10-04
2
23
 
3
24
  ### Added
@@ -57,6 +78,8 @@
57
78
 
58
79
  _First public release for Forecastathon._
59
80
 
81
+ [v0.5.4]: https://github.com/autonity/afp-sdk/releases/tag/v0.5.4
82
+ [v0.5.3]: https://github.com/autonity/afp-sdk/releases/tag/v0.5.3
60
83
  [v0.5.2]: https://github.com/autonity/afp-sdk/releases/tag/v0.5.2
61
84
  [v0.5.1]: https://github.com/autonity/afp-sdk/releases/tag/v0.5.1
62
85
  [v0.5.0]: https://github.com/autonity/afp-sdk/releases/tag/v0.5.0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: afp-sdk
3
- Version: 0.5.2
3
+ Version: 0.5.4
4
4
  Summary: Autonomous Futures Protocol Python SDK
5
5
  Keywords: autonity,web3,trading,crypto,prediction,forecast,markets
6
6
  License-Expression: MIT
@@ -1,6 +1,6 @@
1
1
  import secrets
2
2
  import warnings
3
- from datetime import datetime
3
+ from datetime import datetime, timedelta
4
4
  from decimal import Decimal
5
5
  from typing import Generator, Iterable
6
6
 
@@ -16,6 +16,7 @@ from ..schemas import (
16
16
  Intent,
17
17
  IntentData,
18
18
  MarketDepthData,
19
+ OHLCVItem,
19
20
  Order,
20
21
  OrderFilter,
21
22
  OrderCancellationData,
@@ -123,8 +124,9 @@ class Trading(ExchangeAPI):
123
124
 
124
125
  Raises
125
126
  ------
126
- afp.exceptions.ValidationError
127
- If the exchange rejects the intent.
127
+ afp.exceptions.RateLimitExceeded
128
+ If the exchange rejects the order because of too many requests from the
129
+ authenticated account.
128
130
  """
129
131
  submission = OrderSubmission(
130
132
  type=OrderType.LIMIT_ORDER,
@@ -146,8 +148,13 @@ class Trading(ExchangeAPI):
146
148
 
147
149
  Raises
148
150
  ------
151
+ afp.exceptions.NotFoundError
152
+ If the exchange rejects the cancellation because the intent does not exist.
153
+ afp.exceptions.RateLimitExceeded
154
+ If the exchange rejects the cancellation because of too many requests from
155
+ the authenticated account.
149
156
  afp.exceptions.ValidationError
150
- If the exchange rejects the cancellation.
157
+ If the exchange rejects the cancellation because it is invalid.
151
158
  """
152
159
  nonce = self._generate_nonce()
153
160
  cancellation_hash = hashing.generate_order_cancellation_hash(nonce, intent_hash)
@@ -238,6 +245,7 @@ class Trading(ExchangeAPI):
238
245
  self,
239
246
  *,
240
247
  product_id: str | None = None,
248
+ intent_account_id: str | None = None,
241
249
  type_: str | None = None,
242
250
  states: Iterable[str] = (),
243
251
  side: str | None = None,
@@ -255,6 +263,8 @@ class Trading(ExchangeAPI):
255
263
  Parameters
256
264
  ----------
257
265
  product_id : str, optional
266
+ intent_account_id : str, optional
267
+ Defaults to the address of the authenticated account.
258
268
  type_ : str, optional
259
269
  One of `LIMIT_ORDER` and `CANCEL_ORDER`.
260
270
  states : iterable of str
@@ -274,8 +284,11 @@ class Trading(ExchangeAPI):
274
284
  -------
275
285
  list of afp.schemas.OrderFill
276
286
  """
287
+ if intent_account_id is None:
288
+ intent_account_id = self._authenticator.address
289
+
277
290
  filter = OrderFilter(
278
- intent_account_id=self._authenticator.address,
291
+ intent_account_id=intent_account_id,
279
292
  product_id=product_id,
280
293
  type=None if type_ is None else OrderType(type_.upper()),
281
294
  states=[OrderState(state.upper()) for state in states],
@@ -306,6 +319,7 @@ class Trading(ExchangeAPI):
306
319
  self,
307
320
  *,
308
321
  product_id: str | None = None,
322
+ intent_account_id: str | None = None,
309
323
  intent_hash: str | None = None,
310
324
  start: datetime | None = None,
311
325
  end: datetime | None = None,
@@ -323,6 +337,8 @@ class Trading(ExchangeAPI):
323
337
  Parameters
324
338
  ----------
325
339
  product_id : str, optional
340
+ intent_account_id : str, optional
341
+ Defaults to the address of the authenticated account.
326
342
  intent_hash : str, optional
327
343
  start : datetime.datetime, optional
328
344
  end : datetime.datetime, optional
@@ -339,8 +355,11 @@ class Trading(ExchangeAPI):
339
355
  -------
340
356
  list of afp.schemas.OrderFill
341
357
  """
358
+ if intent_account_id is None:
359
+ intent_account_id = self._authenticator.address
360
+
342
361
  filter = OrderFillFilter(
343
- intent_account_id=self._authenticator.address,
362
+ intent_account_id=intent_account_id,
344
363
  product_id=product_id,
345
364
  intent_hash=intent_hash,
346
365
  start=start,
@@ -357,6 +376,7 @@ class Trading(ExchangeAPI):
357
376
  self,
358
377
  *,
359
378
  product_id: str | None = None,
379
+ intent_account_id: str | None = None,
360
380
  intent_hash: str | None = None,
361
381
  trade_states: Iterable[str] = ("PENDING",),
362
382
  ) -> Generator[OrderFill, None, None]:
@@ -376,6 +396,8 @@ class Trading(ExchangeAPI):
376
396
  Parameters
377
397
  ----------
378
398
  product_id : str, optional
399
+ intent_account_id : str, optional
400
+ Defaults to the address of the authenticated account.
379
401
  intent_hash : str, optional
380
402
  trade_states: iterable of str
381
403
  Any of `PENDING`, `CLEARED` and `REJECTED`.
@@ -384,8 +406,11 @@ class Trading(ExchangeAPI):
384
406
  -------
385
407
  afp.schemas.OrderFill
386
408
  """
409
+ if intent_account_id is None:
410
+ intent_account_id = self._authenticator.address
411
+
387
412
  filter = OrderFillFilter(
388
- intent_account_id=self._authenticator.address,
413
+ intent_account_id=intent_account_id,
389
414
  product_id=product_id,
390
415
  intent_hash=intent_hash,
391
416
  start=None,
@@ -439,3 +464,71 @@ class Trading(ExchangeAPI):
439
464
  """
440
465
  value = validators.validate_hexstr32(product_id)
441
466
  yield from self._exchange.iter_market_depth_data(value)
467
+
468
+ def ohlcv(
469
+ self,
470
+ product_id: str,
471
+ start: datetime | None = None,
472
+ interval: timedelta = timedelta(minutes=5),
473
+ ) -> list[OHLCVItem]:
474
+ """Retrieves Open-High-Low-Close-Volume time series data for the given product.
475
+
476
+ Parameters
477
+ ----------
478
+ product_id : str
479
+ start : datetime
480
+ Defaults to 1 day ago.
481
+ interval : timedelta
482
+ The distance between 2 data points. Gets rounded to a multiple of 5 seconds.
483
+ Defaults to 5 minutes.
484
+
485
+ Returns
486
+ -------
487
+ list of afp.schemas.OHLCVItem
488
+
489
+ Raises
490
+ ------
491
+ afp.exceptions.NotFoundError
492
+ If no such product exists.
493
+ """
494
+ if start is None:
495
+ start = datetime.now() - timedelta(days=1)
496
+
497
+ product_id = validators.validate_hexstr32(product_id)
498
+ start_timestamp = int(start.timestamp())
499
+ interval_secs = int(validators.validate_timedelta(interval).total_seconds())
500
+ return self._exchange.get_time_series_data(
501
+ product_id, start_timestamp, interval_secs
502
+ )
503
+
504
+ def iter_ohlcv(
505
+ self, product_id: str, interval: timedelta = timedelta(seconds=5)
506
+ ) -> Generator[OHLCVItem, None, None]:
507
+ """Subscribes to Open-High-Low-Close-Volume time series data updates for the
508
+ given product.
509
+
510
+ Returns a generator that yields OHLCV data points as they are published
511
+ by the exhange.
512
+
513
+ Parameters
514
+ ----------
515
+ product_id : str
516
+ interval : timedelta
517
+ The distance between 2 data points. Gets rounded to a multiple of 5 seconds.
518
+ Defaults to 5 seconds.
519
+
520
+ Yields
521
+ -------
522
+ afp.schemas.OHLCVItem
523
+
524
+ Raises
525
+ ------
526
+ afp.exceptions.NotFoundError
527
+ If no such product exists.
528
+ """
529
+ product_id = validators.validate_hexstr32(product_id)
530
+ start_timestamp = int(datetime.now().timestamp())
531
+ interval_secs = int(validators.validate_timedelta(interval).total_seconds())
532
+ yield from self._exchange.iter_time_series_data(
533
+ product_id, start_timestamp, interval_secs
534
+ )
@@ -11,6 +11,7 @@ class ListingState(StrEnum):
11
11
  class OrderType(StrEnum):
12
12
  LIMIT_ORDER = "LIMIT_ORDER"
13
13
  CANCEL_ORDER = "CANCEL_ORDER"
14
+ EXCHANGE_INITIATED_CANCELLATION = "EXCHANGE_INITIATED_CANCELLATION"
14
15
 
15
16
 
16
17
  class OrderState(StrEnum):
@@ -26,5 +26,9 @@ class NotFoundError(ExchangeError):
26
26
  pass
27
27
 
28
28
 
29
+ class RateLimitExceeded(ExchangeError):
30
+ pass
31
+
32
+
29
33
  class ValidationError(ExchangeError):
30
34
  pass
@@ -11,6 +11,7 @@ from .exceptions import (
11
11
  AuthorizationError,
12
12
  ExchangeError,
13
13
  NotFoundError,
14
+ RateLimitExceeded,
14
15
  ValidationError,
15
16
  )
16
17
  from .schemas import (
@@ -21,6 +22,7 @@ from .schemas import (
21
22
  ExchangeProductUpdateSubmission,
22
23
  LoginSubmission,
23
24
  MarketDepthData,
25
+ OHLCVItem,
24
26
  Order,
25
27
  OrderFilter,
26
28
  OrderFill,
@@ -138,6 +140,30 @@ class ExchangeClient:
138
140
  for line in response.iter_lines():
139
141
  yield MarketDepthData.model_validate_json(line)
140
142
 
143
+ # GET /time-series/{product_id}
144
+ def get_time_series_data(
145
+ self, product_id: str, start: int, interval: int
146
+ ) -> list[OHLCVItem]:
147
+ response = self._send_request(
148
+ "GET",
149
+ f"/time-series/{product_id}",
150
+ params=dict(start=start, interval=interval),
151
+ )
152
+ return [OHLCVItem(**item) for item in response.json()["data"]]
153
+
154
+ # GET /stream/time-series/{product_id}
155
+ def iter_time_series_data(
156
+ self, product_id: str, start: int, interval: int
157
+ ) -> Generator[OHLCVItem, None, None]:
158
+ response = self._send_request(
159
+ "GET",
160
+ f"/stream/time-series/{product_id}",
161
+ params=dict(start=start, interval=interval),
162
+ stream=True,
163
+ )
164
+ for line in response.iter_lines():
165
+ yield OHLCVItem.model_validate_json(line)
166
+
141
167
  def _send_request(
142
168
  self,
143
169
  method: str,
@@ -173,6 +199,8 @@ class ExchangeClient:
173
199
  raise AuthorizationError(http_error) from http_error
174
200
  if http_error.response.status_code == requests.codes.NOT_FOUND:
175
201
  raise NotFoundError(http_error) from http_error
202
+ if http_error.response.status_code == requests.codes.TOO_MANY_REQUESTS:
203
+ raise RateLimitExceeded(http_error) from http_error
176
204
  if http_error.response.status_code == requests.codes.BAD_REQUEST:
177
205
  try:
178
206
  reason = response.json()["detail"]
@@ -78,7 +78,8 @@ class LoginSubmission(Model):
78
78
 
79
79
  class ExchangeParameters(Model):
80
80
  trading_protocol_id: str
81
- trading_fee_rate: Decimal
81
+ maker_trading_fee_rate: Decimal
82
+ taker_trading_fee_rate: Decimal
82
83
 
83
84
 
84
85
  # Admin API
@@ -182,6 +183,7 @@ class OrderFill(Model):
182
183
  trade: Trade
183
184
  quantity: int
184
185
  price: Decimal
186
+ trading_fee_rate: Decimal
185
187
 
186
188
 
187
189
  class OrderFillFilter(PaginationFilter):
@@ -209,6 +211,15 @@ class MarketDepthData(Model):
209
211
  asks: list[MarketDepthItem]
210
212
 
211
213
 
214
+ class OHLCVItem(Model):
215
+ timestamp: Timestamp
216
+ open: Decimal
217
+ high: Decimal
218
+ low: Decimal
219
+ close: Decimal
220
+ volume: int
221
+
222
+
212
223
  # Clearing API
213
224
 
214
225
 
@@ -4,7 +4,7 @@ build-backend = "uv_build"
4
4
 
5
5
  [project]
6
6
  name = "afp-sdk"
7
- version = "0.5.2"
7
+ version = "0.5.4"
8
8
  description = "Autonomous Futures Protocol Python SDK"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -81,7 +81,9 @@ def test_ClearingSystemAPI__applies_transaction_parameters(monkeypatch):
81
81
 
82
82
  def test_ExchangeAPI__applies_exchange_parameters(monkeypatch):
83
83
  fake_exchange_params = ExchangeParameters(
84
- trading_protocol_id="baz", trading_fee_rate=Decimal("0")
84
+ trading_protocol_id="baz",
85
+ maker_trading_fee_rate=Decimal("0"),
86
+ taker_trading_fee_rate=Decimal("0"),
85
87
  )
86
88
  mock_login = Mock(return_value=fake_exchange_params)
87
89
  mock_generate_login_nonce = Mock(return_value="12345678")
@@ -13,7 +13,7 @@ wheels = [
13
13
 
14
14
  [[package]]
15
15
  name = "afp-sdk"
16
- version = "0.5.2"
16
+ version = "0.5.4"
17
17
  source = { editable = "." }
18
18
  dependencies = [
19
19
  { name = "decorator" },
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