nado-protocol 0.1.4__py3-none-any.whl → 0.1.6__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.
- nado_protocol/client/apis/market/execute.py +146 -0
- nado_protocol/engine_client/types/query.py +10 -0
- nado_protocol/indexer_client/types/models.py +1 -0
- nado_protocol/indexer_client/types/query.py +3 -1
- nado_protocol/trigger_client/execute.py +227 -2
- nado_protocol/trigger_client/query.py +19 -0
- nado_protocol/trigger_client/types/models.py +50 -9
- nado_protocol/trigger_client/types/query.py +137 -15
- nado_protocol/utils/twap.py +189 -0
- nado_protocol-0.1.6.dist-info/METADATA +309 -0
- {nado_protocol-0.1.4.dist-info → nado_protocol-0.1.6.dist-info}/RECORD +13 -12
- {nado_protocol-0.1.4.dist-info → nado_protocol-0.1.6.dist-info}/WHEEL +1 -1
- nado_protocol-0.1.4.dist-info/METADATA +0 -159
- {nado_protocol-0.1.4.dist-info → nado_protocol-0.1.6.dist-info}/entry_points.txt +0 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
from typing import Optional
|
|
1
|
+
from typing import Optional, List, Union
|
|
2
|
+
from enum import Enum
|
|
2
3
|
|
|
3
4
|
from pydantic import validator
|
|
4
5
|
|
|
@@ -9,6 +10,22 @@ from nado_protocol.utils.execute import BaseParams, SignatureParams
|
|
|
9
10
|
from nado_protocol.utils.model import NadoBaseModel
|
|
10
11
|
|
|
11
12
|
|
|
13
|
+
class TriggerType(str, Enum):
|
|
14
|
+
PRICE_TRIGGER = "price_trigger"
|
|
15
|
+
TIME_TRIGGER = "time_trigger"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class TriggerOrderStatusType(str, Enum):
|
|
19
|
+
CANCELLED = "cancelled"
|
|
20
|
+
TRIGGERED = "triggered"
|
|
21
|
+
INTERNAL_ERROR = "internal_error"
|
|
22
|
+
TRIGGERING = "triggering"
|
|
23
|
+
WAITING_PRICE = "waiting_price"
|
|
24
|
+
WAITING_DEPENDENCY = "waiting_dependency"
|
|
25
|
+
TWAP_EXECUTING = "twap_executing"
|
|
26
|
+
TWAP_COMPLETED = "twap_completed"
|
|
27
|
+
|
|
28
|
+
|
|
12
29
|
class ListTriggerOrdersTx(BaseParams):
|
|
13
30
|
recvTime: int
|
|
14
31
|
|
|
@@ -20,18 +37,114 @@ class ListTriggerOrdersParams(NadoBaseModel):
|
|
|
20
37
|
|
|
21
38
|
type = "list_trigger_orders"
|
|
22
39
|
tx: ListTriggerOrdersTx
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
40
|
+
product_ids: Optional[List[int]] = None
|
|
41
|
+
trigger_types: Optional[List[TriggerType]] = None
|
|
42
|
+
status_types: Optional[List[TriggerOrderStatusType]] = None
|
|
43
|
+
max_update_time: Optional[int] = None
|
|
44
|
+
max_digest: Optional[str] = None
|
|
45
|
+
digests: Optional[List[str]] = None
|
|
46
|
+
reduce_only: Optional[bool] = None
|
|
47
|
+
limit: Optional[int] = None
|
|
48
|
+
signature: Optional[str] = None
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class ListTwapExecutionsParams(NadoBaseModel):
|
|
52
|
+
"""
|
|
53
|
+
Parameters for listing TWAP executions for a specific order
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
type = "list_twap_executions"
|
|
57
|
+
digest: str
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class ExecutedStatusData(NadoBaseModel):
|
|
61
|
+
"""Data for executed TWAP execution"""
|
|
62
|
+
|
|
63
|
+
executed_time: int
|
|
64
|
+
execute_response: dict # ExecuteResponse from engine
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class ExecutedStatus(NadoBaseModel):
|
|
68
|
+
"""Status when TWAP execution has been executed"""
|
|
69
|
+
|
|
70
|
+
executed: ExecutedStatusData
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class FailedStatus(NadoBaseModel):
|
|
74
|
+
"""Status when TWAP execution failed"""
|
|
75
|
+
|
|
76
|
+
failed: str
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class CancelledStatus(NadoBaseModel):
|
|
80
|
+
"""Status when TWAP execution was cancelled"""
|
|
81
|
+
|
|
82
|
+
cancelled: str
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class TwapExecutionDetail(NadoBaseModel):
|
|
86
|
+
"""Detail of a single TWAP execution"""
|
|
87
|
+
|
|
88
|
+
execution_id: int
|
|
89
|
+
scheduled_time: int
|
|
90
|
+
status: Union[
|
|
91
|
+
ExecutedStatus, FailedStatus, CancelledStatus, str
|
|
92
|
+
] # str for "pending"
|
|
93
|
+
updated_at: int
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class TwapExecutionsData(NadoBaseModel):
|
|
97
|
+
"""Data model for TWAP executions"""
|
|
98
|
+
|
|
99
|
+
executions: List[TwapExecutionDetail]
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class TriggeredStatus(NadoBaseModel):
|
|
103
|
+
"""Status when order has been triggered"""
|
|
104
|
+
|
|
105
|
+
triggered: dict # Contains trigger execution details
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class TriggerCancelledStatus(NadoBaseModel):
|
|
109
|
+
"""Status when order has been cancelled"""
|
|
110
|
+
|
|
111
|
+
cancelled: str # Cancellation reason (e.g., "user_requested")
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class TriggerInternalErrorStatus(NadoBaseModel):
|
|
115
|
+
"""Status when there was an internal error"""
|
|
116
|
+
|
|
117
|
+
internal_error: str # Error description
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
class TwapExecutingStatusObject(NadoBaseModel):
|
|
121
|
+
"""Status when TWAP order is executing"""
|
|
122
|
+
|
|
123
|
+
twap_executing: dict # Contains execution details
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class TwapCompletedStatusObject(NadoBaseModel):
|
|
127
|
+
"""Status when TWAP order is completed"""
|
|
128
|
+
|
|
129
|
+
twap_completed: dict # Contains completion details
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
# Union type for trigger order status
|
|
133
|
+
# Order matters: more specific types (with required fields) should come first
|
|
134
|
+
TriggerOrderStatus = Union[
|
|
135
|
+
TriggeredStatus,
|
|
136
|
+
TriggerCancelledStatus,
|
|
137
|
+
TriggerInternalErrorStatus,
|
|
138
|
+
TwapExecutingStatusObject,
|
|
139
|
+
TwapCompletedStatusObject,
|
|
140
|
+
str, # For simple status strings like "waiting_price", "waiting_dependency", etc.
|
|
141
|
+
]
|
|
30
142
|
|
|
31
143
|
|
|
32
144
|
class TriggerOrder(NadoBaseModel):
|
|
33
145
|
order: TriggerOrderData
|
|
34
|
-
status:
|
|
146
|
+
status: TriggerOrderStatus
|
|
147
|
+
placed_at: int
|
|
35
148
|
updated_at: int
|
|
36
149
|
|
|
37
150
|
|
|
@@ -40,7 +153,7 @@ class TriggerOrdersData(NadoBaseModel):
|
|
|
40
153
|
Data model for trigger orders
|
|
41
154
|
"""
|
|
42
155
|
|
|
43
|
-
orders:
|
|
156
|
+
orders: List[TriggerOrder]
|
|
44
157
|
|
|
45
158
|
|
|
46
159
|
class ListTriggerOrdersRequest(ListTriggerOrdersParams):
|
|
@@ -54,6 +167,15 @@ class ListTriggerOrdersRequest(ListTriggerOrdersParams):
|
|
|
54
167
|
return v
|
|
55
168
|
|
|
56
169
|
|
|
170
|
+
class ListTwapExecutionsRequest(ListTwapExecutionsParams):
|
|
171
|
+
pass
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
TriggerQueryParams = Union[ListTriggerOrdersParams, ListTwapExecutionsParams]
|
|
175
|
+
TriggerQueryRequest = Union[ListTriggerOrdersRequest, ListTwapExecutionsRequest]
|
|
176
|
+
TriggerQueryData = Union[TriggerOrdersData, TwapExecutionsData]
|
|
177
|
+
|
|
178
|
+
|
|
57
179
|
class TriggerQueryResponse(NadoBaseModel):
|
|
58
180
|
"""
|
|
59
181
|
Represents a response to a query request.
|
|
@@ -61,7 +183,7 @@ class TriggerQueryResponse(NadoBaseModel):
|
|
|
61
183
|
Attributes:
|
|
62
184
|
status (ResponseStatus): The status of the query response.
|
|
63
185
|
|
|
64
|
-
data (Optional[
|
|
186
|
+
data (Optional[TriggerQueryData]): The data returned from the query, or an error message if the query failed.
|
|
65
187
|
|
|
66
188
|
error (Optional[str]): The error message, if any error occurred during the query.
|
|
67
189
|
|
|
@@ -71,7 +193,7 @@ class TriggerQueryResponse(NadoBaseModel):
|
|
|
71
193
|
"""
|
|
72
194
|
|
|
73
195
|
status: ResponseStatus
|
|
74
|
-
data: Optional[
|
|
75
|
-
error: Optional[str]
|
|
76
|
-
error_code: Optional[int]
|
|
77
|
-
request_type: Optional[str]
|
|
196
|
+
data: Optional[TriggerQueryData] = None
|
|
197
|
+
error: Optional[str] = None
|
|
198
|
+
error_code: Optional[int] = None
|
|
199
|
+
request_type: Optional[str] = None
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
from typing import List, Optional
|
|
2
|
+
from nado_protocol.utils.order import (
|
|
3
|
+
build_appendix,
|
|
4
|
+
OrderAppendixTriggerType,
|
|
5
|
+
)
|
|
6
|
+
from nado_protocol.utils.expiration import OrderType
|
|
7
|
+
from nado_protocol.utils.execute import OrderParams
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def create_twap_order(
|
|
11
|
+
product_id: int,
|
|
12
|
+
sender: str,
|
|
13
|
+
price_x18: str,
|
|
14
|
+
total_amount_x18: str,
|
|
15
|
+
expiration: int,
|
|
16
|
+
nonce: int,
|
|
17
|
+
times: int,
|
|
18
|
+
slippage_frac: float,
|
|
19
|
+
interval_seconds: int,
|
|
20
|
+
custom_amounts_x18: Optional[List[str]] = None,
|
|
21
|
+
reduce_only: bool = False,
|
|
22
|
+
spot_leverage: Optional[bool] = None,
|
|
23
|
+
id: Optional[int] = None,
|
|
24
|
+
):
|
|
25
|
+
"""
|
|
26
|
+
Create a TWAP (Time-Weighted Average Price) order.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
product_id (int): The product ID for the order.
|
|
30
|
+
sender (str): The sender address (32 bytes hex).
|
|
31
|
+
price_x18 (str): The limit price multiplied by 1e18.
|
|
32
|
+
total_amount_x18 (str): The total amount to trade multiplied by 1e18 (signed, negative for sell).
|
|
33
|
+
expiration (int): Order expiration timestamp.
|
|
34
|
+
nonce (int): Order nonce.
|
|
35
|
+
times (int): Number of TWAP executions (1-500).
|
|
36
|
+
slippage_frac (float): Slippage tolerance as a fraction (e.g., 0.01 for 1%).
|
|
37
|
+
interval_seconds (int): Time interval between executions in seconds.
|
|
38
|
+
custom_amounts_x18 (Optional[List[str]]): Custom amounts for each execution multiplied by 1e18.
|
|
39
|
+
If provided, uses TWAP_CUSTOM_AMOUNTS trigger type.
|
|
40
|
+
reduce_only (bool): Whether this is a reduce-only order.
|
|
41
|
+
spot_leverage (Optional[bool]): Whether to use spot leverage.
|
|
42
|
+
id (Optional[int]): Optional order ID.
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
PlaceTriggerOrderParams: Parameters for placing the TWAP order.
|
|
46
|
+
|
|
47
|
+
Raises:
|
|
48
|
+
ValueError: If parameters are invalid.
|
|
49
|
+
"""
|
|
50
|
+
# Import here to avoid circular imports
|
|
51
|
+
from nado_protocol.trigger_client.types.models import TimeTrigger
|
|
52
|
+
from nado_protocol.trigger_client.types.execute import PlaceTriggerOrderParams
|
|
53
|
+
|
|
54
|
+
if times < 1 or times > 500:
|
|
55
|
+
raise ValueError(f"TWAP times must be between 1 and 500, got {times}")
|
|
56
|
+
|
|
57
|
+
if slippage_frac < 0 or slippage_frac > 1:
|
|
58
|
+
raise ValueError(
|
|
59
|
+
f"Slippage fraction must be between 0 and 1, got {slippage_frac}"
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
if interval_seconds <= 0:
|
|
63
|
+
raise ValueError(f"Interval must be positive, got {interval_seconds}")
|
|
64
|
+
|
|
65
|
+
# Determine trigger type
|
|
66
|
+
trigger_type = (
|
|
67
|
+
OrderAppendixTriggerType.TWAP_CUSTOM_AMOUNTS
|
|
68
|
+
if custom_amounts_x18 is not None
|
|
69
|
+
else OrderAppendixTriggerType.TWAP
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
# Build appendix - TWAP orders must use IOC execution type
|
|
73
|
+
appendix = build_appendix(
|
|
74
|
+
order_type=OrderType.IOC,
|
|
75
|
+
reduce_only=reduce_only,
|
|
76
|
+
trigger_type=trigger_type,
|
|
77
|
+
twap_times=times,
|
|
78
|
+
twap_slippage_frac=slippage_frac,
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
# Create the base order
|
|
82
|
+
order_params = OrderParams(
|
|
83
|
+
sender=sender,
|
|
84
|
+
priceX18=int(price_x18),
|
|
85
|
+
amount=int(total_amount_x18),
|
|
86
|
+
expiration=expiration,
|
|
87
|
+
nonce=nonce,
|
|
88
|
+
appendix=appendix,
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
# Create trigger criteria
|
|
92
|
+
from nado_protocol.trigger_client.types.models import TimeTriggerData
|
|
93
|
+
|
|
94
|
+
trigger = TimeTrigger(
|
|
95
|
+
time_trigger=TimeTriggerData(
|
|
96
|
+
interval=interval_seconds,
|
|
97
|
+
amounts=custom_amounts_x18,
|
|
98
|
+
)
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
return PlaceTriggerOrderParams(
|
|
102
|
+
product_id=product_id,
|
|
103
|
+
order=order_params,
|
|
104
|
+
trigger=trigger,
|
|
105
|
+
signature=None, # Will be filled by client
|
|
106
|
+
digest=None, # Will be filled by client
|
|
107
|
+
spot_leverage=spot_leverage,
|
|
108
|
+
id=id,
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def validate_twap_order(
|
|
113
|
+
total_amount_x18: str,
|
|
114
|
+
times: int,
|
|
115
|
+
custom_amounts_x18: Optional[List[str]] = None,
|
|
116
|
+
) -> None:
|
|
117
|
+
"""
|
|
118
|
+
Validate TWAP order parameters.
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
total_amount_x18 (str): The total amount to trade multiplied by 1e18.
|
|
122
|
+
times (int): Number of TWAP executions.
|
|
123
|
+
custom_amounts_x18 (Optional[List[str]]): Custom amounts for each execution multiplied by 1e18.
|
|
124
|
+
|
|
125
|
+
Raises:
|
|
126
|
+
ValueError: If validation fails.
|
|
127
|
+
"""
|
|
128
|
+
total_amount_int = int(total_amount_x18)
|
|
129
|
+
|
|
130
|
+
if custom_amounts_x18 is None:
|
|
131
|
+
# For equal distribution, total amount must be divisible by times
|
|
132
|
+
if total_amount_int % times != 0:
|
|
133
|
+
raise ValueError(
|
|
134
|
+
f"Total amount {total_amount_x18} must be divisible by times {times} "
|
|
135
|
+
f"for equal distribution TWAP orders"
|
|
136
|
+
)
|
|
137
|
+
else:
|
|
138
|
+
# For custom amounts, verify the list length and sum
|
|
139
|
+
if len(custom_amounts_x18) != times:
|
|
140
|
+
raise ValueError(
|
|
141
|
+
f"Custom amounts list length ({len(custom_amounts_x18)}) must equal "
|
|
142
|
+
f"times ({times})"
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
custom_sum = sum(int(amount) for amount in custom_amounts_x18)
|
|
146
|
+
if custom_sum != total_amount_int:
|
|
147
|
+
raise ValueError(
|
|
148
|
+
f"Sum of custom amounts ({custom_sum}) must equal "
|
|
149
|
+
f"total amount ({total_amount_int})"
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def estimate_twap_completion_time(times: int, interval_seconds: int) -> int:
|
|
154
|
+
"""
|
|
155
|
+
Estimate the total time for TWAP order completion.
|
|
156
|
+
|
|
157
|
+
Args:
|
|
158
|
+
times (int): Number of TWAP executions.
|
|
159
|
+
interval_seconds (int): Time interval between executions.
|
|
160
|
+
|
|
161
|
+
Returns:
|
|
162
|
+
int: Estimated completion time in seconds.
|
|
163
|
+
"""
|
|
164
|
+
return (times - 1) * interval_seconds
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def calculate_equal_amounts(total_amount_x18: str, times: int) -> List[str]:
|
|
168
|
+
"""
|
|
169
|
+
Calculate equal amounts for TWAP executions.
|
|
170
|
+
|
|
171
|
+
Args:
|
|
172
|
+
total_amount_x18 (str): The total amount to distribute multiplied by 1e18.
|
|
173
|
+
times (int): Number of executions.
|
|
174
|
+
|
|
175
|
+
Returns:
|
|
176
|
+
List[str]: List of equal amounts for each execution multiplied by 1e18.
|
|
177
|
+
|
|
178
|
+
Raises:
|
|
179
|
+
ValueError: If total amount is not divisible by times.
|
|
180
|
+
"""
|
|
181
|
+
total_amount_int = int(total_amount_x18)
|
|
182
|
+
|
|
183
|
+
if total_amount_int % times != 0:
|
|
184
|
+
raise ValueError(
|
|
185
|
+
f"Total amount {total_amount_x18} is not divisible by times {times}"
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
amount_per_execution = total_amount_int // times
|
|
189
|
+
return [str(amount_per_execution)] * times
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: nado-protocol
|
|
3
|
+
Version: 0.1.6
|
|
4
|
+
Summary: Nado Protocol SDK
|
|
5
|
+
Keywords: nado protocol,nado sdk,nado protocol api
|
|
6
|
+
Author: Jeury Mejia
|
|
7
|
+
Author-email: jeury@inkfnd.com
|
|
8
|
+
Maintainer: Frank Jia
|
|
9
|
+
Maintainer-email: frank@inkfnd.com
|
|
10
|
+
Requires-Python: >=3.9,<4.0
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
18
|
+
Requires-Dist: eth-account (>=0.8.0,<0.9.0)
|
|
19
|
+
Requires-Dist: pydantic (>=1.10.7,<2.0.0)
|
|
20
|
+
Requires-Dist: web3 (>=6.4.0,<7.0.0)
|
|
21
|
+
Project-URL: Documentation, https://nadohq.github.io/nado-python-sdk/
|
|
22
|
+
Project-URL: Homepage, https://nado.xyz
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
|
|
25
|
+
# Nado Protocol Python SDK
|
|
26
|
+
|
|
27
|
+
This is the Python SDK for the [Nado Protocol API](TODO).
|
|
28
|
+
|
|
29
|
+
See [SDK docs](https://nadohq.github.io/nado-python-sdk/index.html) to get started.
|
|
30
|
+
|
|
31
|
+
## Requirements
|
|
32
|
+
|
|
33
|
+
- Python 3.9 or above
|
|
34
|
+
|
|
35
|
+
## Installation
|
|
36
|
+
|
|
37
|
+
You can install the SDK via pip:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
pip install nado-protocol
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Basic usage
|
|
44
|
+
|
|
45
|
+
### Import the necessary utilities:
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
from nado_protocol.client import create_nado_client, NadoClientMode
|
|
49
|
+
from nado_protocol.contracts.types import DepositCollateralParams
|
|
50
|
+
from nado_protocol.engine_client.types.execute import (
|
|
51
|
+
OrderParams,
|
|
52
|
+
PlaceOrderParams,
|
|
53
|
+
SubaccountParams
|
|
54
|
+
)
|
|
55
|
+
from nado_protocol.utils.expiration import OrderType, get_expiration_timestamp
|
|
56
|
+
from nado_protocol.utils.math import to_pow_10, to_x18
|
|
57
|
+
from nado_protocol.utils.nonce import gen_order_nonce
|
|
58
|
+
from nado_protocol.utils.order import build_appendix
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Create the NadoClient providing your private key:
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
print("setting up nado client...")
|
|
65
|
+
private_key = "xxx"
|
|
66
|
+
client = create_nado_client(NadoClientMode.DEVNET, private_key)
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Perform basic operations:
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
# Depositing collaterals
|
|
73
|
+
print("approving allowance...")
|
|
74
|
+
approve_allowance_tx_hash = client.spot.approve_allowance(0, to_pow_10(100000, 6))
|
|
75
|
+
print("approve allowance tx hash:", approve_allowance_tx_hash)
|
|
76
|
+
|
|
77
|
+
print("querying my allowance...")
|
|
78
|
+
token_allowance = client.spot.get_token_allowance(0, client.context.signer.address)
|
|
79
|
+
print("token allowance:", token_allowance)
|
|
80
|
+
|
|
81
|
+
print("depositing collateral...")
|
|
82
|
+
deposit_tx_hash = client.spot.deposit(
|
|
83
|
+
DepositCollateralParams(
|
|
84
|
+
subaccount_name="default", product_id=0, amount=to_pow_10(100000, 6)
|
|
85
|
+
)
|
|
86
|
+
)
|
|
87
|
+
print("deposit collateral tx hash:", deposit_tx_hash)
|
|
88
|
+
|
|
89
|
+
# Placing orders
|
|
90
|
+
print("placing order...")
|
|
91
|
+
owner = client.context.engine_client.signer.address
|
|
92
|
+
product_id = 1
|
|
93
|
+
order = OrderParams(
|
|
94
|
+
sender=SubaccountParams(
|
|
95
|
+
subaccount_owner=owner,
|
|
96
|
+
subaccount_name="default",
|
|
97
|
+
),
|
|
98
|
+
priceX18=to_x18(20000),
|
|
99
|
+
amount=to_pow_10(1, 17),
|
|
100
|
+
expiration=get_expiration_timestamp(40),
|
|
101
|
+
nonce=gen_order_nonce(),
|
|
102
|
+
appendix=build_appendix(order_type=OrderType.POST_ONLY)
|
|
103
|
+
)
|
|
104
|
+
res = client.market.place_order({"product_id": product_id, "order": order})
|
|
105
|
+
print("order result:", res.json(indent=2))
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## TWAP and Trigger Orders
|
|
109
|
+
|
|
110
|
+
The SDK provides comprehensive support for Time-Weighted Average Price (TWAP) orders and price trigger orders through the Trigger Client.
|
|
111
|
+
|
|
112
|
+
### TWAP Orders
|
|
113
|
+
|
|
114
|
+
TWAP orders allow you to execute large trades over time with controlled slippage:
|
|
115
|
+
|
|
116
|
+
```python
|
|
117
|
+
from nado_protocol.trigger_client import TriggerClient
|
|
118
|
+
from nado_protocol.trigger_client.types import TriggerClientOpts
|
|
119
|
+
from nado_protocol.utils.math import to_x18
|
|
120
|
+
from nado_protocol.utils.expiration import get_expiration_timestamp
|
|
121
|
+
|
|
122
|
+
# Create trigger client
|
|
123
|
+
trigger_client = TriggerClient(
|
|
124
|
+
opts=TriggerClientOpts(url=TRIGGER_BACKEND_URL, signer=private_key)
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
# Place a TWAP order to buy 5 BTC over 2 hours
|
|
128
|
+
twap_result = trigger_client.place_twap_order(
|
|
129
|
+
product_id=1,
|
|
130
|
+
sender=client.signer.address,
|
|
131
|
+
price_x18=str(to_x18(50_000)), # Max $50k per execution
|
|
132
|
+
total_amount_x18=str(to_x18(5)), # Buy 5 BTC total
|
|
133
|
+
expiration=get_expiration_timestamp(60 * 24), # 24 hours
|
|
134
|
+
nonce=client.order_nonce(),
|
|
135
|
+
times=10, # Split into 10 executions
|
|
136
|
+
slippage_frac=0.005, # 0.5% slippage tolerance
|
|
137
|
+
interval_seconds=720, # 12 minutes between executions
|
|
138
|
+
)
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### TWAP with Custom Amounts
|
|
142
|
+
|
|
143
|
+
For more sophisticated strategies, you can specify custom amounts for each execution:
|
|
144
|
+
|
|
145
|
+
```python
|
|
146
|
+
# Decreasing size strategy: 2 BTC, 1.5 BTC, 1 BTC, 0.5 BTC
|
|
147
|
+
custom_amounts = [
|
|
148
|
+
str(to_x18(2)), # 2 BTC
|
|
149
|
+
str(to_x18(1.5)), # 1.5 BTC
|
|
150
|
+
str(to_x18(1)), # 1 BTC
|
|
151
|
+
str(to_x18(0.5)), # 0.5 BTC
|
|
152
|
+
]
|
|
153
|
+
|
|
154
|
+
custom_twap_result = trigger_client.place_twap_order(
|
|
155
|
+
product_id=1,
|
|
156
|
+
sender=client.signer.address,
|
|
157
|
+
price_x18=str(to_x18(51_000)),
|
|
158
|
+
total_amount_x18=str(to_x18(5)), # 5 BTC total
|
|
159
|
+
expiration=get_expiration_timestamp(60 * 12),
|
|
160
|
+
nonce=client.order_nonce(),
|
|
161
|
+
times=4, # 4 executions
|
|
162
|
+
slippage_frac=0.01, # 1% slippage
|
|
163
|
+
interval_seconds=1800, # 30 minutes
|
|
164
|
+
custom_amounts_x18=custom_amounts,
|
|
165
|
+
)
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### Price Trigger Orders
|
|
169
|
+
|
|
170
|
+
Create conditional orders that execute when price conditions are met:
|
|
171
|
+
|
|
172
|
+
```python
|
|
173
|
+
# Stop-loss order (sell when price drops below $45k)
|
|
174
|
+
stop_loss = trigger_client.place_price_trigger_order(
|
|
175
|
+
product_id=1,
|
|
176
|
+
sender=client.signer.address,
|
|
177
|
+
price_x18=str(to_x18(44_000)), # Sell at $44k
|
|
178
|
+
amount_x18=str(-to_x18(1)), # Sell 1 BTC (negative for sell)
|
|
179
|
+
expiration=get_expiration_timestamp(60 * 24 * 7), # 1 week
|
|
180
|
+
nonce=client.order_nonce(),
|
|
181
|
+
trigger_price_x18=str(to_x18(45_000)), # Trigger below $45k
|
|
182
|
+
trigger_type="last_price_below",
|
|
183
|
+
reduce_only=True, # Only reduce position
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
# Take-profit order (sell when price rises above $55k)
|
|
187
|
+
take_profit = trigger_client.place_price_trigger_order(
|
|
188
|
+
product_id=1,
|
|
189
|
+
sender=client.signer.address,
|
|
190
|
+
price_x18=str(to_x18(56_000)), # Sell at $56k
|
|
191
|
+
amount_x18=str(-to_x18(1)), # Sell 1 BTC
|
|
192
|
+
expiration=get_expiration_timestamp(60 * 24 * 7),
|
|
193
|
+
nonce=client.order_nonce(),
|
|
194
|
+
trigger_price_x18=str(to_x18(55_000)), # Trigger above $55k
|
|
195
|
+
trigger_type="last_price_above",
|
|
196
|
+
reduce_only=True,
|
|
197
|
+
)
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### Supported Trigger Types
|
|
201
|
+
|
|
202
|
+
The SDK supports six types of price triggers:
|
|
203
|
+
|
|
204
|
+
- `"last_price_above"`: Trigger when last traded price goes above threshold
|
|
205
|
+
- `"last_price_below"`: Trigger when last traded price goes below threshold
|
|
206
|
+
- `"oracle_price_above"`: Trigger when oracle price goes above threshold
|
|
207
|
+
- `"oracle_price_below"`: Trigger when oracle price goes below threshold
|
|
208
|
+
- `"mid_price_above"`: Trigger when mid price goes above threshold
|
|
209
|
+
- `"mid_price_below"`: Trigger when mid price goes below threshold
|
|
210
|
+
|
|
211
|
+
### Complete Trading Strategy Example
|
|
212
|
+
|
|
213
|
+
Here's how to set up a complete trading strategy with stop-loss, take-profit, and DCA:
|
|
214
|
+
|
|
215
|
+
```python
|
|
216
|
+
# 1. Stop-loss protection
|
|
217
|
+
stop_loss = trigger_client.place_price_trigger_order(
|
|
218
|
+
product_id=1,
|
|
219
|
+
sender=client.signer.address,
|
|
220
|
+
price_x18=str(to_x18(44_000)),
|
|
221
|
+
amount_x18=str(-to_x18(2)), # Close 2 BTC position
|
|
222
|
+
expiration=get_expiration_timestamp(60 * 24 * 30),
|
|
223
|
+
nonce=client.order_nonce(),
|
|
224
|
+
trigger_price_x18=str(to_x18(45_000)),
|
|
225
|
+
trigger_type="last_price_below",
|
|
226
|
+
reduce_only=True,
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
# 2. Take-profit target
|
|
230
|
+
take_profit = trigger_client.place_price_trigger_order(
|
|
231
|
+
product_id=1,
|
|
232
|
+
sender=client.signer.address,
|
|
233
|
+
price_x18=str(to_x18(58_000)),
|
|
234
|
+
amount_x18=str(-to_x18(2)), # Close 2 BTC position
|
|
235
|
+
expiration=get_expiration_timestamp(60 * 24 * 30),
|
|
236
|
+
nonce=client.order_nonce(),
|
|
237
|
+
trigger_price_x18=str(to_x18(57_000)),
|
|
238
|
+
trigger_type="last_price_above",
|
|
239
|
+
reduce_only=True,
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
# 3. DCA accumulation strategy
|
|
243
|
+
dca_strategy = trigger_client.place_twap_order(
|
|
244
|
+
product_id=1,
|
|
245
|
+
sender=client.signer.address,
|
|
246
|
+
price_x18=str(to_x18(52_000)), # Max $52k per buy
|
|
247
|
+
total_amount_x18=str(to_x18(10)), # Buy 10 BTC over time
|
|
248
|
+
expiration=get_expiration_timestamp(60 * 24 * 7),
|
|
249
|
+
nonce=client.order_nonce(),
|
|
250
|
+
times=20, # 20 executions
|
|
251
|
+
slippage_frac=0.005, # 0.5% slippage
|
|
252
|
+
interval_seconds=1800, # 30 minutes
|
|
253
|
+
)
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
See [Getting Started](https://nadohq.github.io/nado-python-sdk/getting-started.html) for more.
|
|
257
|
+
|
|
258
|
+
## Running locally
|
|
259
|
+
|
|
260
|
+
1. Clone [github repo](https://github.com/nadohq/nado-python-sdk)
|
|
261
|
+
|
|
262
|
+
2. Install poetry
|
|
263
|
+
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
$ curl -sSL https://install.python-poetry.org | python3 -
|
|
267
|
+
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
3. Setup a virtual environment and activate it
|
|
271
|
+
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
$ python3 -m venv venv
|
|
275
|
+
$ source ./venv/bin/activate
|
|
276
|
+
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
4. Install dependencies via `poetry install`
|
|
280
|
+
5. Setup an `.env` file and set the following envvars
|
|
281
|
+
|
|
282
|
+
```shell
|
|
283
|
+
CLIENT_MODE='devnet'
|
|
284
|
+
SIGNER_PRIVATE_KEY="0x..."
|
|
285
|
+
LINKED_SIGNER_PRIVATE_KEY="0x..." # not required
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
### Run tests
|
|
289
|
+
|
|
290
|
+
```
|
|
291
|
+
$ poetry run test
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
### Run sanity checks
|
|
295
|
+
|
|
296
|
+
- `poetry run client-sanity`: runs sanity checks for the top-level client.
|
|
297
|
+
- `poetry run engine-sanity`: runs sanity checks for the `engine-client`.
|
|
298
|
+
- `poetry run indexer-sanity`: runs sanity checks for the `indexer-client`.
|
|
299
|
+
- `poetry run trigger-sanity`: runs sanity checks for the `trigger-client` including TWAP and price trigger examples.
|
|
300
|
+
- `poetry run contracts-sanity`: runs sanity checks for the contracts module.
|
|
301
|
+
|
|
302
|
+
### Build Docs
|
|
303
|
+
|
|
304
|
+
To build the docs locally run:
|
|
305
|
+
|
|
306
|
+
```
|
|
307
|
+
$ poetry run sphinx-build docs/source docs/build
|
|
308
|
+
```
|
|
309
|
+
|