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 +34 -7
- afp/api/product.py +35 -7
- afp/api/trading.py +123 -23
- afp/auth.py +1 -1
- afp/constants.py +5 -2
- afp/enums.py +7 -0
- afp/exchange.py +39 -16
- afp/schemas.py +69 -9
- {afp_sdk-0.5.0.dist-info → afp_sdk-0.5.2.dist-info}/METADATA +1 -1
- {afp_sdk-0.5.0.dist-info → afp_sdk-0.5.2.dist-info}/RECORD +12 -12
- {afp_sdk-0.5.0.dist-info → afp_sdk-0.5.2.dist-info}/WHEEL +1 -1
- {afp_sdk-0.5.0.dist-info → afp_sdk-0.5.2.dist-info}/licenses/LICENSE +0 -0
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 ..
|
|
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
|
|
12
|
-
"""
|
|
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
|
-
|
|
24
|
-
|
|
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
|
-
|
|
42
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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,
|
|
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(
|
|
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
|
|
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=
|
|
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(
|
|
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
|
-
|
|
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
|
|
215
|
-
|
|
216
|
-
|
|
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.
|
|
275
|
+
list of afp.schemas.OrderFill
|
|
225
276
|
"""
|
|
226
|
-
|
|
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
|
-
|
|
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
|
|
276
|
-
exchange.
|
|
277
|
-
|
|
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
|
-
|
|
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
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
|
-
|
|
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
|
-
#
|
|
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
afp/exchange.py
CHANGED
|
@@ -16,10 +16,13 @@ from .exceptions import (
|
|
|
16
16
|
from .schemas import (
|
|
17
17
|
ExchangeParameters,
|
|
18
18
|
ExchangeProduct,
|
|
19
|
-
|
|
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(
|
|
54
|
-
|
|
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
|
|
70
|
+
def list_product(
|
|
71
|
+
self, listing_submission: ExchangeProductListingSubmission
|
|
72
|
+
) -> None:
|
|
64
73
|
self._send_request(
|
|
65
|
-
"POST", "/products", data=
|
|
74
|
+
"POST", "/products", data=listing_submission.model_dump_json()
|
|
66
75
|
)
|
|
67
76
|
|
|
68
|
-
#
|
|
69
|
-
def
|
|
70
|
-
self
|
|
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
|
|
95
|
+
def get_orders(self, filter: OrderFilter) -> list[Order]:
|
|
81
96
|
response = self._send_request(
|
|
82
|
-
"GET", "/orders", params=(
|
|
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
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
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
|
-
#
|
|
84
|
+
# Admin API
|
|
57
85
|
|
|
58
86
|
|
|
59
|
-
class
|
|
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(
|
|
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
|
-
|
|
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,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=
|
|
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=
|
|
8
|
-
afp/api/trading.py,sha256=
|
|
9
|
-
afp/auth.py,sha256=
|
|
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=
|
|
25
|
+
afp/constants.py,sha256=EvDhLpKBOsc8OHGm1paiUAdAetPGD4nyi13coB8rd14,1930
|
|
26
26
|
afp/decorators.py,sha256=SEUQtbgPGc4iVPtBQV2eiCejcDAVImmXcI0uPXFhtJA,2774
|
|
27
|
-
afp/enums.py,sha256=
|
|
27
|
+
afp/enums.py,sha256=HQ9EqLEvg9EHBA_UDSyn0Lma72fYoefjHfzOcIVKyh0,616
|
|
28
28
|
afp/exceptions.py,sha256=frdS-EH84K0fOf92RgRgNkTe3VII2m36XNCS8eyXLLM,390
|
|
29
|
-
afp/exchange.py,sha256
|
|
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=
|
|
32
|
+
afp/schemas.py,sha256=PCHoEpaU-O-C50UTrru3eRNalCLccWsTRE1AWITiIP0,6607
|
|
33
33
|
afp/validators.py,sha256=zQvPu3HDu6ADnEE72EJlS5hc1-xfru78Mzd74s1u_EM,1841
|
|
34
|
-
afp_sdk-0.5.
|
|
35
|
-
afp_sdk-0.5.
|
|
36
|
-
afp_sdk-0.5.
|
|
37
|
-
afp_sdk-0.5.
|
|
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,,
|
|
File without changes
|