afp-sdk 0.1.0__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/__init__.py +10 -0
- afp/api/__init__.py +0 -0
- afp/api/admin.py +55 -0
- afp/api/base.py +76 -0
- afp/api/builder.py +201 -0
- afp/api/clearing.py +377 -0
- afp/api/liquidation.py +167 -0
- afp/api/trading.py +342 -0
- afp/bindings/__init__.py +48 -0
- afp/bindings/auctioneer_facet.py +758 -0
- afp/bindings/bankruptcy_facet.py +355 -0
- afp/bindings/clearing_facet.py +1019 -0
- afp/bindings/erc20.py +379 -0
- afp/bindings/facade.py +87 -0
- afp/bindings/final_settlement_facet.py +268 -0
- afp/bindings/margin_account.py +1355 -0
- afp/bindings/margin_account_registry.py +617 -0
- afp/bindings/mark_price_tracker_facet.py +111 -0
- afp/bindings/oracle_provider.py +539 -0
- afp/bindings/product_registry.py +1302 -0
- afp/bindings/trading_protocol.py +1181 -0
- afp/config.py +38 -0
- afp/decorators.py +74 -0
- afp/enums.py +27 -0
- afp/exceptions.py +26 -0
- afp/exchange.py +151 -0
- afp/py.typed +0 -0
- afp/schemas.py +207 -0
- afp/signing.py +69 -0
- afp/validators.py +43 -0
- afp_sdk-0.1.0.dist-info/METADATA +180 -0
- afp_sdk-0.1.0.dist-info/RECORD +34 -0
- afp_sdk-0.1.0.dist-info/WHEEL +4 -0
- afp_sdk-0.1.0.dist-info/licenses/LICENSE +21 -0
afp/api/trading.py
ADDED
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
import secrets
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
from decimal import Decimal
|
|
4
|
+
from typing import Generator
|
|
5
|
+
|
|
6
|
+
from web3 import Web3
|
|
7
|
+
|
|
8
|
+
from .. import signing, validators
|
|
9
|
+
from ..decorators import refresh_token_on_expiry
|
|
10
|
+
from ..enums import OrderType
|
|
11
|
+
from ..schemas import (
|
|
12
|
+
ExchangeProduct,
|
|
13
|
+
Intent,
|
|
14
|
+
IntentData,
|
|
15
|
+
MarketDepthData,
|
|
16
|
+
Order,
|
|
17
|
+
OrderCancellationData,
|
|
18
|
+
OrderFill,
|
|
19
|
+
OrderFillFilter,
|
|
20
|
+
OrderSide,
|
|
21
|
+
OrderSubmission,
|
|
22
|
+
TradeState,
|
|
23
|
+
)
|
|
24
|
+
from .base import ExchangeAPI
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class Trading(ExchangeAPI):
|
|
28
|
+
"""API for trading in the AutEx exchange.
|
|
29
|
+
|
|
30
|
+
Authenticates with the exchange on creation.
|
|
31
|
+
|
|
32
|
+
Parameters
|
|
33
|
+
----------
|
|
34
|
+
private_key : str
|
|
35
|
+
The private key of the account that submits intents to the exchange.
|
|
36
|
+
|
|
37
|
+
Raises
|
|
38
|
+
------
|
|
39
|
+
afp.exceptions.AuthenticationError
|
|
40
|
+
If the exchange rejects the login attempt.
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
@staticmethod
|
|
44
|
+
def _generate_nonce() -> int:
|
|
45
|
+
return secrets.randbelow(2**31) # Postgres integer range
|
|
46
|
+
|
|
47
|
+
def create_intent(
|
|
48
|
+
self,
|
|
49
|
+
*,
|
|
50
|
+
product: ExchangeProduct,
|
|
51
|
+
side: str,
|
|
52
|
+
limit_price: Decimal,
|
|
53
|
+
quantity: int,
|
|
54
|
+
max_trading_fee_rate: Decimal,
|
|
55
|
+
good_until_time: datetime,
|
|
56
|
+
margin_account_id: str | None = None,
|
|
57
|
+
) -> Intent:
|
|
58
|
+
"""Creates an intent with the given intent data, generates its hash and signs it
|
|
59
|
+
with the configured account's private key.
|
|
60
|
+
|
|
61
|
+
The intent account's address is derived from the private key. The intent account
|
|
62
|
+
is assumed to be the same as the margin account if the margin account ID is not
|
|
63
|
+
specified.
|
|
64
|
+
|
|
65
|
+
Parameters
|
|
66
|
+
----------
|
|
67
|
+
product : afp.schemas.ExchangeProduct
|
|
68
|
+
side : str
|
|
69
|
+
limit_price : decimal.Decimal
|
|
70
|
+
quantity : decimal.Decimal
|
|
71
|
+
max_trading_fee_rate : decimal.Decimal
|
|
72
|
+
good_until_time : datetime.datetime
|
|
73
|
+
margin_account_id : str, optional
|
|
74
|
+
|
|
75
|
+
Returns
|
|
76
|
+
-------
|
|
77
|
+
afp.schemas.Intent
|
|
78
|
+
"""
|
|
79
|
+
margin_account_id = (
|
|
80
|
+
validators.validate_address(margin_account_id)
|
|
81
|
+
if margin_account_id is not None
|
|
82
|
+
else self._account.address
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
intent_data = IntentData(
|
|
86
|
+
trading_protocol_id=self._trading_protocol_id,
|
|
87
|
+
product_id=product.id,
|
|
88
|
+
limit_price=limit_price,
|
|
89
|
+
quantity=quantity,
|
|
90
|
+
max_trading_fee_rate=max_trading_fee_rate,
|
|
91
|
+
side=getattr(OrderSide, side.upper()),
|
|
92
|
+
good_until_time=good_until_time,
|
|
93
|
+
nonce=self._generate_nonce(),
|
|
94
|
+
)
|
|
95
|
+
intent_hash = signing.generate_intent_hash(
|
|
96
|
+
intent_data=intent_data,
|
|
97
|
+
margin_account_id=margin_account_id,
|
|
98
|
+
intent_account_id=self._account.address,
|
|
99
|
+
tick_size=product.tick_size,
|
|
100
|
+
)
|
|
101
|
+
signature = signing.sign_message(self._account, intent_hash)
|
|
102
|
+
return Intent(
|
|
103
|
+
hash=Web3.to_hex(intent_hash),
|
|
104
|
+
margin_account_id=margin_account_id,
|
|
105
|
+
intent_account_id=self._account.address,
|
|
106
|
+
signature=Web3.to_hex(signature),
|
|
107
|
+
data=intent_data,
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
@refresh_token_on_expiry
|
|
111
|
+
def submit_limit_order(self, intent: Intent) -> Order:
|
|
112
|
+
"""Sends an intent expressing a limit order to the exchange.
|
|
113
|
+
|
|
114
|
+
Parameters
|
|
115
|
+
----------
|
|
116
|
+
intent : afp.schemas.Intent
|
|
117
|
+
|
|
118
|
+
Returns
|
|
119
|
+
-------
|
|
120
|
+
afp.schemas.Order
|
|
121
|
+
|
|
122
|
+
Raises
|
|
123
|
+
------
|
|
124
|
+
afp.exceptions.ValidationError
|
|
125
|
+
If the exchange rejects the intent.
|
|
126
|
+
"""
|
|
127
|
+
submission = OrderSubmission(
|
|
128
|
+
type=OrderType.LIMIT_ORDER,
|
|
129
|
+
intent=intent,
|
|
130
|
+
)
|
|
131
|
+
return self._exchange.submit_order(submission)
|
|
132
|
+
|
|
133
|
+
@refresh_token_on_expiry
|
|
134
|
+
def submit_cancel_order(self, intent_hash: str) -> Order:
|
|
135
|
+
"""Sends a cancellation order to the exchange.
|
|
136
|
+
|
|
137
|
+
Parameters
|
|
138
|
+
----------
|
|
139
|
+
intent_hash : str
|
|
140
|
+
|
|
141
|
+
Returns
|
|
142
|
+
-------
|
|
143
|
+
afp.schemas.Order
|
|
144
|
+
|
|
145
|
+
Raises
|
|
146
|
+
------
|
|
147
|
+
afp.exceptions.ValidationError
|
|
148
|
+
If the exchange rejects the cancellation.
|
|
149
|
+
"""
|
|
150
|
+
nonce = self._generate_nonce()
|
|
151
|
+
cancellation_hash = signing.generate_order_cancellation_hash(nonce, intent_hash)
|
|
152
|
+
signature = signing.sign_message(self._account, cancellation_hash)
|
|
153
|
+
cancellation_data = OrderCancellationData(
|
|
154
|
+
intent_hash=intent_hash,
|
|
155
|
+
nonce=nonce,
|
|
156
|
+
intent_account_id=self._account.address,
|
|
157
|
+
signature=Web3.to_hex(signature),
|
|
158
|
+
)
|
|
159
|
+
submission = OrderSubmission(
|
|
160
|
+
type=OrderType.CANCEL_ORDER,
|
|
161
|
+
cancellation_data=cancellation_data,
|
|
162
|
+
)
|
|
163
|
+
return self._exchange.submit_order(submission)
|
|
164
|
+
|
|
165
|
+
@refresh_token_on_expiry
|
|
166
|
+
def products(self) -> list[ExchangeProduct]:
|
|
167
|
+
"""Retrieves the products approved for trading on the exchange.
|
|
168
|
+
|
|
169
|
+
Returns
|
|
170
|
+
-------
|
|
171
|
+
list of afp.schemas.ExchangeProduct
|
|
172
|
+
"""
|
|
173
|
+
return self._exchange.get_approved_products()
|
|
174
|
+
|
|
175
|
+
@refresh_token_on_expiry
|
|
176
|
+
def product(self, product_id: str) -> ExchangeProduct:
|
|
177
|
+
"""Retrieves a product for trading by its ID.
|
|
178
|
+
|
|
179
|
+
Parameters
|
|
180
|
+
----------
|
|
181
|
+
product_id : str
|
|
182
|
+
|
|
183
|
+
Returns
|
|
184
|
+
-------
|
|
185
|
+
afp.schemas.ExchangeProduct
|
|
186
|
+
|
|
187
|
+
Raises
|
|
188
|
+
------
|
|
189
|
+
afp.exceptions.NotFoundError
|
|
190
|
+
If no such product exists.
|
|
191
|
+
"""
|
|
192
|
+
value = validators.validate_hexstr32(product_id)
|
|
193
|
+
return self._exchange.get_product_by_id(value)
|
|
194
|
+
|
|
195
|
+
@refresh_token_on_expiry
|
|
196
|
+
def order(self, order_id: str) -> Order:
|
|
197
|
+
"""Retrieves an order by its ID from the orders that have been submitted by the
|
|
198
|
+
authenticated account.
|
|
199
|
+
|
|
200
|
+
Parameters
|
|
201
|
+
----------
|
|
202
|
+
order_id : str
|
|
203
|
+
|
|
204
|
+
Returns
|
|
205
|
+
-------
|
|
206
|
+
afp.schemas.Order
|
|
207
|
+
|
|
208
|
+
Raises
|
|
209
|
+
------
|
|
210
|
+
afp.exceptions.NotFoundError
|
|
211
|
+
If no such order exists.
|
|
212
|
+
"""
|
|
213
|
+
value = validators.validate_hexstr32(order_id)
|
|
214
|
+
return self._exchange.get_order_by_id(value)
|
|
215
|
+
|
|
216
|
+
@refresh_token_on_expiry
|
|
217
|
+
def open_orders(self) -> list[Order]:
|
|
218
|
+
"""Retrieves all open and partially filled limit orders that have been submitted
|
|
219
|
+
by the authenticated account.
|
|
220
|
+
|
|
221
|
+
Returns
|
|
222
|
+
-------
|
|
223
|
+
list of afp.schemas.Order
|
|
224
|
+
"""
|
|
225
|
+
return self._exchange.get_open_orders()
|
|
226
|
+
|
|
227
|
+
@refresh_token_on_expiry
|
|
228
|
+
def order_fills(
|
|
229
|
+
self,
|
|
230
|
+
*,
|
|
231
|
+
product_id: str | None = None,
|
|
232
|
+
margin_account_id: str | None = None,
|
|
233
|
+
intent_hash: str | None = None,
|
|
234
|
+
start: datetime | None = None,
|
|
235
|
+
end: datetime | None = None,
|
|
236
|
+
) -> list[OrderFill]:
|
|
237
|
+
"""Retrieves the authenticated account's order fills that match the given
|
|
238
|
+
parameters.
|
|
239
|
+
|
|
240
|
+
Parameters
|
|
241
|
+
----------
|
|
242
|
+
product_id : str, optional
|
|
243
|
+
margin_account_id : str, optional
|
|
244
|
+
intent_hash : str, optional
|
|
245
|
+
start : datetime.datetime, optional
|
|
246
|
+
end : datetime.datetime, optional
|
|
247
|
+
|
|
248
|
+
Returns
|
|
249
|
+
-------
|
|
250
|
+
list of afp.schemas.OrderFill
|
|
251
|
+
"""
|
|
252
|
+
filter = OrderFillFilter(
|
|
253
|
+
intent_account_id=self._account.address,
|
|
254
|
+
product_id=product_id,
|
|
255
|
+
margin_account_id=margin_account_id,
|
|
256
|
+
intent_hash=intent_hash,
|
|
257
|
+
start=start,
|
|
258
|
+
end=end,
|
|
259
|
+
trade_state=None,
|
|
260
|
+
)
|
|
261
|
+
return self._exchange.get_order_fills(filter)
|
|
262
|
+
|
|
263
|
+
@refresh_token_on_expiry
|
|
264
|
+
def iter_order_fills(
|
|
265
|
+
self,
|
|
266
|
+
*,
|
|
267
|
+
product_id: str | None = None,
|
|
268
|
+
margin_account_id: str | None = None,
|
|
269
|
+
intent_hash: str | None = None,
|
|
270
|
+
) -> Generator[OrderFill, None, None]:
|
|
271
|
+
"""Subscribes to the authenticated account's new order fills that match the
|
|
272
|
+
given parameters.
|
|
273
|
+
|
|
274
|
+
Returns a generator that yields new order fills as they are published by the
|
|
275
|
+
exchange. A new order fill gets publised as soon as there is a match in the
|
|
276
|
+
order book, before the trade is submitted to clearing.
|
|
277
|
+
|
|
278
|
+
Parameters
|
|
279
|
+
----------
|
|
280
|
+
product_id : str, optional
|
|
281
|
+
margin_account_id : str, optional
|
|
282
|
+
intent_hash : str, optional
|
|
283
|
+
|
|
284
|
+
Yields
|
|
285
|
+
-------
|
|
286
|
+
afp.schemas.OrderFill
|
|
287
|
+
"""
|
|
288
|
+
filter = OrderFillFilter(
|
|
289
|
+
intent_account_id=self._account.address,
|
|
290
|
+
product_id=product_id,
|
|
291
|
+
margin_account_id=margin_account_id,
|
|
292
|
+
intent_hash=intent_hash,
|
|
293
|
+
start=None,
|
|
294
|
+
end=None,
|
|
295
|
+
trade_state=TradeState.PENDING,
|
|
296
|
+
)
|
|
297
|
+
yield from self._exchange.iter_order_fills(filter)
|
|
298
|
+
|
|
299
|
+
@refresh_token_on_expiry
|
|
300
|
+
def market_depth(self, product_id: str) -> MarketDepthData:
|
|
301
|
+
"""Retrieves the depth of market for the given product.
|
|
302
|
+
|
|
303
|
+
Parameters
|
|
304
|
+
----------
|
|
305
|
+
product_id : str
|
|
306
|
+
|
|
307
|
+
Returns
|
|
308
|
+
-------
|
|
309
|
+
afp.schemas.MarketDepthData
|
|
310
|
+
|
|
311
|
+
Raises
|
|
312
|
+
------
|
|
313
|
+
afp.exceptions.NotFoundError
|
|
314
|
+
If no such product exists.
|
|
315
|
+
"""
|
|
316
|
+
value = validators.validate_hexstr32(product_id)
|
|
317
|
+
return self._exchange.get_market_depth_data(value)
|
|
318
|
+
|
|
319
|
+
@refresh_token_on_expiry
|
|
320
|
+
def iter_market_depth(
|
|
321
|
+
self, product_id: str
|
|
322
|
+
) -> Generator[MarketDepthData, None, None]:
|
|
323
|
+
"""Subscribes to updates of the depth of market for the given product.
|
|
324
|
+
|
|
325
|
+
Returns a generator that yields the updated market depth data as it is published
|
|
326
|
+
by the exhange.
|
|
327
|
+
|
|
328
|
+
Parameters
|
|
329
|
+
----------
|
|
330
|
+
product_id : str
|
|
331
|
+
|
|
332
|
+
Yields
|
|
333
|
+
-------
|
|
334
|
+
afp.schemas.MarketDepthData
|
|
335
|
+
|
|
336
|
+
Raises
|
|
337
|
+
------
|
|
338
|
+
afp.exceptions.NotFoundError
|
|
339
|
+
If no such product exists.
|
|
340
|
+
"""
|
|
341
|
+
value = validators.validate_hexstr32(product_id)
|
|
342
|
+
yield from self._exchange.iter_market_depth_data(value)
|
afp/bindings/__init__.py
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""Typed bindings around the smart contracts of the Autonity Futures Protocol."""
|
|
2
|
+
|
|
3
|
+
from .auctioneer_facet import AuctionConfig, AuctionData, BidData
|
|
4
|
+
from .bankruptcy_facet import LAAData, PositionLossData
|
|
5
|
+
from .clearing_facet import ClearingConfig, Config, Intent, IntentData, Side, Trade
|
|
6
|
+
from .facade import (
|
|
7
|
+
ClearingDiamond,
|
|
8
|
+
MarginAccountRegistry,
|
|
9
|
+
OracleProvider,
|
|
10
|
+
ProductRegistry,
|
|
11
|
+
)
|
|
12
|
+
from .margin_account import MarginAccount, PositionData, Settlement
|
|
13
|
+
from .product_registry import (
|
|
14
|
+
OracleSpecification,
|
|
15
|
+
Product,
|
|
16
|
+
ProductMetadata,
|
|
17
|
+
ProductState,
|
|
18
|
+
)
|
|
19
|
+
from .trading_protocol import TradingProtocol
|
|
20
|
+
|
|
21
|
+
__all__ = (
|
|
22
|
+
# Contract bindings
|
|
23
|
+
"ClearingDiamond",
|
|
24
|
+
"MarginAccount",
|
|
25
|
+
"MarginAccountRegistry",
|
|
26
|
+
"OracleProvider",
|
|
27
|
+
"ProductRegistry",
|
|
28
|
+
"TradingProtocol",
|
|
29
|
+
# Structures
|
|
30
|
+
"AuctionConfig",
|
|
31
|
+
"AuctionData",
|
|
32
|
+
"BidData",
|
|
33
|
+
"ClearingConfig",
|
|
34
|
+
"Config",
|
|
35
|
+
"Intent",
|
|
36
|
+
"IntentData",
|
|
37
|
+
"LAAData",
|
|
38
|
+
"OracleSpecification",
|
|
39
|
+
"PositionData",
|
|
40
|
+
"PositionLossData",
|
|
41
|
+
"Product",
|
|
42
|
+
"ProductMetadata",
|
|
43
|
+
"Settlement",
|
|
44
|
+
"Trade",
|
|
45
|
+
# Enumerations
|
|
46
|
+
"ProductState",
|
|
47
|
+
"Side",
|
|
48
|
+
)
|