nado-protocol 0.1.4__py3-none-any.whl → 0.1.5__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.
@@ -1,3 +1,4 @@
1
+ from typing import Optional, List
1
2
  from nado_protocol.engine_client.types.execute import (
2
3
  BurnNlpParams,
3
4
  CancelAndPlaceParams,
@@ -16,6 +17,7 @@ from nado_protocol.trigger_client.types.execute import (
16
17
  )
17
18
  from nado_protocol.utils.exceptions import MissingTriggerClient
18
19
  from nado_protocol.utils.subaccount import Subaccount
20
+ from nado_protocol.utils.expiration import OrderType
19
21
 
20
22
 
21
23
  class MarketExecuteAPI(NadoBaseAPI):
@@ -174,3 +176,147 @@ class MarketExecuteAPI(NadoBaseAPI):
174
176
  if self.context.trigger_client is None:
175
177
  raise MissingTriggerClient()
176
178
  return self.context.trigger_client.cancel_product_trigger_orders(params)
179
+
180
+ def place_twap_order(
181
+ self,
182
+ product_id: int,
183
+ price_x18: str,
184
+ total_amount_x18: str,
185
+ times: int,
186
+ slippage_frac: float,
187
+ interval_seconds: int,
188
+ sender: Optional[str] = None,
189
+ subaccount_owner: Optional[str] = None,
190
+ subaccount_name: str = "default",
191
+ expiration: Optional[int] = None,
192
+ nonce: Optional[int] = None,
193
+ custom_amounts_x18: Optional[List[str]] = None,
194
+ reduce_only: bool = False,
195
+ spot_leverage: Optional[bool] = None,
196
+ id: Optional[int] = None,
197
+ ) -> ExecuteResponse:
198
+ """
199
+ Place a TWAP (Time-Weighted Average Price) order.
200
+
201
+ This is a convenience method that creates a TWAP trigger order with the specified parameters.
202
+
203
+ Args:
204
+ product_id (int): The product ID for the order.
205
+ price_x18 (str): The limit price multiplied by 1e18.
206
+ total_amount_x18 (str): The total amount to trade multiplied by 1e18 (signed, negative for sell).
207
+ times (int): Number of TWAP executions (1-500).
208
+ slippage_frac (float): Slippage tolerance as a fraction (e.g., 0.01 for 1%).
209
+ interval_seconds (int): Time interval between executions in seconds.
210
+ sender (Optional[str]): The sender address (32 bytes hex or SubaccountParams). If provided, takes precedence over subaccount_owner/subaccount_name.
211
+ subaccount_owner (Optional[str]): The subaccount owner address. If not provided, uses client's signer address. Ignored if sender is provided.
212
+ subaccount_name (str): The subaccount name. Defaults to "default". Ignored if sender is provided.
213
+ expiration (Optional[int]): Order expiration timestamp. If not provided, calculated as min(((times - 1) * interval_seconds) + 1 hour, 25 hours) from now.
214
+ nonce (Optional[int]): Order nonce. If not provided, will be auto-generated.
215
+ custom_amounts_x18 (Optional[List[str]]): Custom amounts for each execution multiplied by 1e18.
216
+ reduce_only (bool): Whether this is a reduce-only order. Defaults to False.
217
+ spot_leverage (Optional[bool]): Whether to use spot leverage.
218
+ id (Optional[int]): Optional order ID.
219
+
220
+ Returns:
221
+ ExecuteResponse: The response from placing the TWAP order.
222
+
223
+ Raises:
224
+ MissingTriggerClient: If trigger client is not configured.
225
+ """
226
+ if self.context.trigger_client is None:
227
+ raise MissingTriggerClient()
228
+ return self.context.trigger_client.place_twap_order(
229
+ product_id=product_id,
230
+ price_x18=price_x18,
231
+ total_amount_x18=total_amount_x18,
232
+ times=times,
233
+ slippage_frac=slippage_frac,
234
+ interval_seconds=interval_seconds,
235
+ sender=sender,
236
+ subaccount_owner=subaccount_owner,
237
+ subaccount_name=subaccount_name,
238
+ expiration=expiration,
239
+ nonce=nonce,
240
+ custom_amounts_x18=custom_amounts_x18,
241
+ reduce_only=reduce_only,
242
+ spot_leverage=spot_leverage,
243
+ id=id,
244
+ )
245
+
246
+ def place_price_trigger_order(
247
+ self,
248
+ product_id: int,
249
+ price_x18: str,
250
+ amount_x18: str,
251
+ trigger_price_x18: str,
252
+ trigger_type: str,
253
+ sender: Optional[str] = None,
254
+ subaccount_owner: Optional[str] = None,
255
+ subaccount_name: str = "default",
256
+ expiration: Optional[int] = None,
257
+ nonce: Optional[int] = None,
258
+ reduce_only: bool = False,
259
+ order_type: OrderType = OrderType.DEFAULT,
260
+ spot_leverage: Optional[bool] = None,
261
+ id: Optional[int] = None,
262
+ dependency: Optional[dict] = None,
263
+ ) -> ExecuteResponse:
264
+ """
265
+ Place a price trigger order.
266
+
267
+ This is a convenience method that creates a price trigger order with the specified parameters.
268
+
269
+ Args:
270
+ product_id (int): The product ID for the order.
271
+ price_x18 (str): The limit price multiplied by 1e18.
272
+ amount_x18 (str): The amount to trade multiplied by 1e18 (signed, negative for sell).
273
+ trigger_price_x18 (str): The trigger price multiplied by 1e18.
274
+ trigger_type (str): Type of price trigger - one of:
275
+ "last_price_above", "last_price_below",
276
+ "oracle_price_above", "oracle_price_below",
277
+ "mid_price_above", "mid_price_below".
278
+ sender (Optional[str]): The sender address (32 bytes hex or SubaccountParams). If provided, takes precedence over subaccount_owner/subaccount_name.
279
+ subaccount_owner (Optional[str]): The subaccount owner address. If not provided, uses client's signer address. Ignored if sender is provided.
280
+ subaccount_name (str): The subaccount name. Defaults to "default". Ignored if sender is provided.
281
+ expiration (Optional[int]): Order expiration timestamp. If not provided, defaults to 7 days from now.
282
+ nonce (Optional[int]): Order nonce. If not provided, will be auto-generated.
283
+ reduce_only (bool): Whether this is a reduce-only order. Defaults to False.
284
+ order_type (OrderType): Order execution type (DEFAULT, IOC, FOK, POST_ONLY). Defaults to DEFAULT.
285
+ spot_leverage (Optional[bool]): Whether to use spot leverage.
286
+ id (Optional[int]): Optional order ID.
287
+ dependency (Optional[dict]): Optional dependency trigger dict with 'digest' and 'on_partial_fill' keys.
288
+
289
+ Returns:
290
+ ExecuteResponse: The response from placing the price trigger order.
291
+
292
+ Raises:
293
+ MissingTriggerClient: If trigger client is not configured.
294
+ ValueError: If trigger_type is not supported.
295
+ """
296
+ if self.context.trigger_client is None:
297
+ raise MissingTriggerClient()
298
+
299
+ # Convert dict to Dependency if provided
300
+ dependency_obj = None
301
+ if dependency is not None:
302
+ from nado_protocol.trigger_client.types.models import Dependency
303
+
304
+ dependency_obj = Dependency.parse_obj(dependency)
305
+
306
+ return self.context.trigger_client.place_price_trigger_order(
307
+ product_id=product_id,
308
+ price_x18=price_x18,
309
+ amount_x18=amount_x18,
310
+ trigger_price_x18=trigger_price_x18,
311
+ trigger_type=trigger_type,
312
+ sender=sender,
313
+ subaccount_owner=subaccount_owner,
314
+ subaccount_name=subaccount_name,
315
+ expiration=expiration,
316
+ nonce=nonce,
317
+ reduce_only=reduce_only,
318
+ order_type=order_type,
319
+ spot_leverage=spot_leverage,
320
+ id=id,
321
+ dependency=dependency_obj,
322
+ )
@@ -1,6 +1,6 @@
1
1
  import requests
2
2
  from functools import singledispatchmethod
3
- from typing import Union
3
+ from typing import Union, Optional, List, cast
4
4
  from nado_protocol.contracts.types import NadoExecuteType
5
5
  from nado_protocol.trigger_client.types.execute import (
6
6
  TriggerExecuteParams,
@@ -16,8 +16,26 @@ from nado_protocol.utils.exceptions import (
16
16
  BadStatusCodeException,
17
17
  ExecuteFailedException,
18
18
  )
19
- from nado_protocol.utils.execute import NadoBaseExecute
19
+ from nado_protocol.utils.execute import NadoBaseExecute, OrderParams
20
20
  from nado_protocol.utils.model import NadoBaseModel, is_instance_of_union
21
+ from nado_protocol.utils.twap import create_twap_order
22
+ from nado_protocol.utils.order import build_appendix, OrderAppendixTriggerType
23
+ from nado_protocol.utils.expiration import OrderType, get_expiration_timestamp
24
+ from nado_protocol.utils.nonce import gen_order_nonce
25
+ from nado_protocol.utils.subaccount import SubaccountParams
26
+ from nado_protocol.utils.bytes32 import subaccount_to_hex
27
+ from nado_protocol.trigger_client.types.models import (
28
+ PriceTrigger,
29
+ PriceTriggerData,
30
+ LastPriceAbove,
31
+ LastPriceBelow,
32
+ OraclePriceAbove,
33
+ OraclePriceBelow,
34
+ MidPriceAbove,
35
+ MidPriceBelow,
36
+ PriceRequirement,
37
+ Dependency,
38
+ )
21
39
 
22
40
 
23
41
  class TriggerExecuteClient(NadoBaseExecute):
@@ -95,6 +113,213 @@ class TriggerExecuteClient(NadoBaseExecute):
95
113
  )
96
114
  return self.execute(params)
97
115
 
116
+ def place_twap_order(
117
+ self,
118
+ product_id: int,
119
+ price_x18: str,
120
+ total_amount_x18: str,
121
+ times: int,
122
+ slippage_frac: float,
123
+ interval_seconds: int,
124
+ sender: Optional[Union[str, SubaccountParams]] = None,
125
+ subaccount_owner: Optional[str] = None,
126
+ subaccount_name: str = "default",
127
+ expiration: Optional[int] = None,
128
+ nonce: Optional[int] = None,
129
+ custom_amounts_x18: Optional[List[str]] = None,
130
+ reduce_only: bool = False,
131
+ spot_leverage: Optional[bool] = None,
132
+ id: Optional[int] = None,
133
+ ) -> ExecuteResponse:
134
+ """
135
+ Place a TWAP (Time-Weighted Average Price) order.
136
+
137
+ This is a convenience method that creates a TWAP trigger order with the specified parameters.
138
+
139
+ Args:
140
+ product_id (int): The product ID for the order.
141
+ price_x18 (str): The limit price multiplied by 1e18.
142
+ total_amount_x18 (str): The total amount to trade multiplied by 1e18 (signed, negative for sell).
143
+ times (int): Number of TWAP executions (1-500).
144
+ slippage_frac (float): Slippage tolerance as a fraction (e.g., 0.01 for 1%).
145
+ interval_seconds (int): Time interval between executions in seconds.
146
+ sender (Optional[str]): The sender address (32 bytes hex or SubaccountParams). If provided, takes precedence over subaccount_owner/subaccount_name.
147
+ subaccount_owner (Optional[str]): The subaccount owner address. If not provided, uses client's signer address. Ignored if sender is provided.
148
+ subaccount_name (str): The subaccount name. Defaults to "default". Ignored if sender is provided.
149
+ expiration (Optional[int]): Order expiration timestamp. If not provided, calculated as min(((times - 1) * interval_seconds) + 1 hour, 25 hours) from now.
150
+ nonce (Optional[int]): Order nonce. If not provided, will be auto-generated.
151
+ custom_amounts_x18 (Optional[List[str]]): Custom amounts for each execution multiplied by 1e18.
152
+ reduce_only (bool): Whether this is a reduce-only order. Defaults to False.
153
+ spot_leverage (Optional[bool]): Whether to use spot leverage.
154
+ id (Optional[int]): Optional order ID.
155
+
156
+ Returns:
157
+ ExecuteResponse: The response from placing the TWAP order.
158
+ """
159
+ # Calculate default expiration if not provided
160
+ # Backend requires: min_expiration <= expiration <= timestamp + 25 hours
161
+ # Where min_expiration = ((times - 1) * interval) + now
162
+ if expiration is None:
163
+ min_duration = (times - 1) * interval_seconds
164
+ max_duration = 60 * 60 * 25 # 25 hours in seconds
165
+ # Set expiration to minimum duration + 1 hour buffer, capped at 25 hours
166
+ buffer_duration = min(min_duration + 60 * 60, max_duration)
167
+ expiration = get_expiration_timestamp(buffer_duration)
168
+
169
+ # Build sender from subaccount parameters if not directly provided
170
+ sender_value: Union[str, SubaccountParams]
171
+ if sender is None:
172
+ sender_value = SubaccountParams(
173
+ subaccount_owner=subaccount_owner or self.signer.address,
174
+ subaccount_name=subaccount_name,
175
+ )
176
+ else:
177
+ sender_value = sender
178
+
179
+ # Convert sender to hex string if it's SubaccountParams
180
+ sender_hex: str
181
+ if isinstance(sender_value, SubaccountParams):
182
+ sender_hex = subaccount_to_hex(sender_value)
183
+ else:
184
+ sender_hex = sender_value
185
+
186
+ params = create_twap_order(
187
+ product_id=product_id,
188
+ sender=sender_hex,
189
+ price_x18=price_x18,
190
+ total_amount_x18=total_amount_x18,
191
+ expiration=expiration,
192
+ nonce=nonce if nonce is not None else gen_order_nonce(),
193
+ times=times,
194
+ slippage_frac=slippage_frac,
195
+ interval_seconds=interval_seconds,
196
+ custom_amounts_x18=custom_amounts_x18,
197
+ reduce_only=reduce_only,
198
+ spot_leverage=spot_leverage,
199
+ id=id,
200
+ )
201
+ return self.place_trigger_order(params)
202
+
203
+ def place_price_trigger_order(
204
+ self,
205
+ product_id: int,
206
+ price_x18: str,
207
+ amount_x18: str,
208
+ trigger_price_x18: str,
209
+ trigger_type: str,
210
+ sender: Optional[Union[str, SubaccountParams]] = None,
211
+ subaccount_owner: Optional[str] = None,
212
+ subaccount_name: str = "default",
213
+ expiration: Optional[int] = None,
214
+ nonce: Optional[int] = None,
215
+ reduce_only: bool = False,
216
+ order_type: OrderType = OrderType.DEFAULT,
217
+ spot_leverage: Optional[bool] = None,
218
+ id: Optional[int] = None,
219
+ dependency: Optional[Dependency] = None,
220
+ ) -> ExecuteResponse:
221
+ """
222
+ Place a price trigger order.
223
+
224
+ This is a convenience method that creates a price trigger order with the specified parameters.
225
+
226
+ Args:
227
+ product_id (int): The product ID for the order.
228
+ price_x18 (str): The limit price multiplied by 1e18.
229
+ amount_x18 (str): The amount to trade multiplied by 1e18 (signed, negative for sell).
230
+ trigger_price_x18 (str): The trigger price multiplied by 1e18.
231
+ trigger_type (str): Type of price trigger - one of:
232
+ "last_price_above", "last_price_below",
233
+ "oracle_price_above", "oracle_price_below",
234
+ "mid_price_above", "mid_price_below".
235
+ sender (Optional[str]): The sender address (32 bytes hex or SubaccountParams). If provided, takes precedence over subaccount_owner/subaccount_name.
236
+ subaccount_owner (Optional[str]): The subaccount owner address. If not provided, uses client's signer address. Ignored if sender is provided.
237
+ subaccount_name (str): The subaccount name. Defaults to "default". Ignored if sender is provided.
238
+ expiration (Optional[int]): Order expiration timestamp. If not provided, defaults to 7 days from now.
239
+ nonce (Optional[int]): Order nonce. If not provided, will be auto-generated.
240
+ reduce_only (bool): Whether this is a reduce-only order. Defaults to False.
241
+ order_type (OrderType): Order execution type (DEFAULT, IOC, FOK, POST_ONLY). Defaults to DEFAULT.
242
+ spot_leverage (Optional[bool]): Whether to use spot leverage.
243
+ id (Optional[int]): Optional order ID.
244
+ dependency (Optional[Dependency]): Optional dependency trigger. If provided, this order will trigger
245
+ when the specified order (by digest) is filled. The dependency includes:
246
+ - digest (str): The digest of the order to depend on
247
+ - on_partial_fill (bool): Whether to trigger on partial fill (True) or only on full fill (False)
248
+
249
+ Returns:
250
+ ExecuteResponse: The response from placing the price trigger order.
251
+
252
+ Raises:
253
+ ValueError: If trigger_type is not supported.
254
+ """
255
+ # Create the appropriate price requirement based on trigger type
256
+ price_requirement: PriceRequirement
257
+ if trigger_type == "last_price_above":
258
+ price_requirement = LastPriceAbove(last_price_above=trigger_price_x18)
259
+ elif trigger_type == "last_price_below":
260
+ price_requirement = LastPriceBelow(last_price_below=trigger_price_x18)
261
+ elif trigger_type == "oracle_price_above":
262
+ price_requirement = OraclePriceAbove(oracle_price_above=trigger_price_x18)
263
+ elif trigger_type == "oracle_price_below":
264
+ price_requirement = OraclePriceBelow(oracle_price_below=trigger_price_x18)
265
+ elif trigger_type == "mid_price_above":
266
+ price_requirement = MidPriceAbove(mid_price_above=trigger_price_x18)
267
+ elif trigger_type == "mid_price_below":
268
+ price_requirement = MidPriceBelow(mid_price_below=trigger_price_x18)
269
+ else:
270
+ raise ValueError(
271
+ f"Unsupported trigger_type: {trigger_type}. "
272
+ f"Supported types: ['last_price_above', 'last_price_below', 'oracle_price_above', 'oracle_price_below', 'mid_price_above', 'mid_price_below']"
273
+ )
274
+
275
+ trigger = PriceTrigger(
276
+ price_trigger=PriceTriggerData(
277
+ price_requirement=price_requirement, dependency=dependency
278
+ )
279
+ )
280
+
281
+ # Build appendix with PRICE trigger type
282
+ appendix = build_appendix(
283
+ order_type=order_type,
284
+ reduce_only=reduce_only,
285
+ trigger_type=OrderAppendixTriggerType.PRICE,
286
+ )
287
+
288
+ # Default expiration to 7 days if not provided
289
+ if expiration is None:
290
+ expiration = get_expiration_timestamp(60 * 60 * 24 * 7)
291
+
292
+ # Build sender from subaccount parameters if not directly provided
293
+ sender_value: Union[str, SubaccountParams]
294
+ if sender is None:
295
+ sender_value = SubaccountParams(
296
+ subaccount_owner=subaccount_owner or self.signer.address,
297
+ subaccount_name=subaccount_name,
298
+ )
299
+ else:
300
+ sender_value = sender
301
+
302
+ order_params = OrderParams(
303
+ sender=sender_value,
304
+ priceX18=int(price_x18),
305
+ amount=int(amount_x18),
306
+ expiration=expiration,
307
+ nonce=nonce if nonce is not None else gen_order_nonce(),
308
+ appendix=appendix,
309
+ )
310
+
311
+ params = PlaceTriggerOrderParams(
312
+ product_id=product_id,
313
+ order=order_params,
314
+ trigger=trigger,
315
+ signature=None,
316
+ digest=None,
317
+ spot_leverage=spot_leverage,
318
+ id=id,
319
+ )
320
+
321
+ return self.place_trigger_order(params)
322
+
98
323
  def cancel_trigger_orders(
99
324
  self, params: CancelTriggerOrdersParams
100
325
  ) -> ExecuteResponse:
@@ -4,7 +4,11 @@ from nado_protocol.trigger_client.types import TriggerClientOpts
4
4
  from nado_protocol.trigger_client.types.query import (
5
5
  ListTriggerOrdersParams,
6
6
  ListTriggerOrdersRequest,
7
+ ListTwapExecutionsParams,
8
+ ListTwapExecutionsRequest,
7
9
  TriggerQueryResponse,
10
+ TriggerQueryParams,
11
+ TriggerQueryRequest,
8
12
  )
9
13
  from nado_protocol.utils.exceptions import (
10
14
  BadStatusCodeException,
@@ -59,3 +63,18 @@ class TriggerQueryClient(NadoBaseExecute):
59
63
  NadoTxType.LIST_TRIGGER_ORDERS, params.tx.dict()
60
64
  )
61
65
  return self.query(ListTriggerOrdersRequest.parse_obj(params).dict())
66
+
67
+ def list_twap_executions(
68
+ self, params: ListTwapExecutionsParams
69
+ ) -> TriggerQueryResponse:
70
+ """
71
+ List TWAP executions for a specific order digest.
72
+
73
+ Args:
74
+ params (ListTwapExecutionsParams): Parameters containing the order digest.
75
+
76
+ Returns:
77
+ TriggerQueryResponse: Response containing TWAP execution details.
78
+ """
79
+ params = ListTwapExecutionsParams.parse_obj(params)
80
+ return self.query(ListTwapExecutionsRequest.parse_obj(params).dict())
@@ -1,28 +1,69 @@
1
- from typing import Optional, Union
1
+ from typing import Optional, Union, List
2
2
  from nado_protocol.utils.model import NadoBaseModel
3
3
 
4
4
 
5
- class PriceAboveTrigger(NadoBaseModel):
6
- price_above: str
5
+ class OraclePriceAbove(NadoBaseModel):
6
+ oracle_price_above: str
7
7
 
8
8
 
9
- class PriceBelowTrigger(NadoBaseModel):
10
- price_below: str
9
+ class OraclePriceBelow(NadoBaseModel):
10
+ oracle_price_below: str
11
11
 
12
12
 
13
- class LastPriceAboveTrigger(NadoBaseModel):
13
+ class LastPriceAbove(NadoBaseModel):
14
14
  last_price_above: str
15
15
 
16
16
 
17
- class LastPriceBelowTrigger(NadoBaseModel):
17
+ class LastPriceBelow(NadoBaseModel):
18
18
  last_price_below: str
19
19
 
20
20
 
21
- TriggerCriteria = Union[
22
- PriceAboveTrigger, PriceBelowTrigger, LastPriceAboveTrigger, LastPriceBelowTrigger
21
+ class MidPriceAbove(NadoBaseModel):
22
+ mid_price_above: str
23
+
24
+
25
+ class MidPriceBelow(NadoBaseModel):
26
+ mid_price_below: str
27
+
28
+
29
+ PriceRequirement = Union[
30
+ OraclePriceAbove,
31
+ OraclePriceBelow,
32
+ LastPriceAbove,
33
+ LastPriceBelow,
34
+ MidPriceAbove,
35
+ MidPriceBelow,
23
36
  ]
24
37
 
25
38
 
39
+ class Dependency(NadoBaseModel):
40
+ digest: str
41
+ on_partial_fill: bool
42
+
43
+
44
+ class PriceTriggerData(NadoBaseModel):
45
+ price_requirement: PriceRequirement
46
+ dependency: Optional[Dependency] = None
47
+
48
+
49
+ class TimeTriggerData(NadoBaseModel):
50
+ """Time-based trigger for TWAP orders."""
51
+
52
+ interval: int # interval in seconds between executions
53
+ amounts: Optional[List[str]] = None # optional custom amounts per execution
54
+
55
+
56
+ class PriceTrigger(NadoBaseModel):
57
+ price_trigger: PriceTriggerData
58
+
59
+
60
+ class TimeTrigger(NadoBaseModel):
61
+ time_trigger: TimeTriggerData
62
+
63
+
64
+ TriggerCriteria = Union[PriceTrigger, TimeTrigger]
65
+
66
+
26
67
  class OrderData(NadoBaseModel):
27
68
  sender: str
28
69
  priceX18: str