walrasquant-lib 0.4.20__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.
- walrasquant/__init__.py +7 -0
- walrasquant/aggregation.py +449 -0
- walrasquant/backends/__init__.py +5 -0
- walrasquant/backends/db.py +109 -0
- walrasquant/backends/db_memory.py +61 -0
- walrasquant/backends/db_postgresql.py +321 -0
- walrasquant/backends/db_sqlite.py +310 -0
- walrasquant/base/__init__.py +24 -0
- walrasquant/base/api_client.py +46 -0
- walrasquant/base/connector.py +863 -0
- walrasquant/base/ems.py +794 -0
- walrasquant/base/exchange.py +213 -0
- walrasquant/base/oms.py +428 -0
- walrasquant/base/retry.py +220 -0
- walrasquant/base/sms.py +545 -0
- walrasquant/base/ws_client.py +408 -0
- walrasquant/config.py +284 -0
- walrasquant/constants.py +413 -0
- walrasquant/core/__init__.py +0 -0
- walrasquant/core/cache.py +688 -0
- walrasquant/core/clock.py +59 -0
- walrasquant/core/connection.py +41 -0
- walrasquant/core/entity.py +504 -0
- walrasquant/core/nautilius_core.py +103 -0
- walrasquant/core/registry.py +41 -0
- walrasquant/engine.py +745 -0
- walrasquant/error.py +34 -0
- walrasquant/exchange/__init__.py +13 -0
- walrasquant/exchange/base_factory.py +172 -0
- walrasquant/exchange/binance/__init__.py +30 -0
- walrasquant/exchange/binance/connector.py +1093 -0
- walrasquant/exchange/binance/constants.py +934 -0
- walrasquant/exchange/binance/ems.py +140 -0
- walrasquant/exchange/binance/error.py +48 -0
- walrasquant/exchange/binance/exchange.py +144 -0
- walrasquant/exchange/binance/factory.py +115 -0
- walrasquant/exchange/binance/oms.py +1807 -0
- walrasquant/exchange/binance/rest_api.py +1653 -0
- walrasquant/exchange/binance/schema.py +1063 -0
- walrasquant/exchange/binance/websockets.py +389 -0
- walrasquant/exchange/bitget/__init__.py +28 -0
- walrasquant/exchange/bitget/connector.py +578 -0
- walrasquant/exchange/bitget/constants.py +392 -0
- walrasquant/exchange/bitget/ems.py +202 -0
- walrasquant/exchange/bitget/error.py +36 -0
- walrasquant/exchange/bitget/exchange.py +128 -0
- walrasquant/exchange/bitget/factory.py +135 -0
- walrasquant/exchange/bitget/oms.py +1619 -0
- walrasquant/exchange/bitget/rest_api.py +610 -0
- walrasquant/exchange/bitget/schema.py +885 -0
- walrasquant/exchange/bitget/websockets.py +753 -0
- walrasquant/exchange/bybit/__init__.py +32 -0
- walrasquant/exchange/bybit/connector.py +819 -0
- walrasquant/exchange/bybit/constants.py +479 -0
- walrasquant/exchange/bybit/ems.py +93 -0
- walrasquant/exchange/bybit/error.py +36 -0
- walrasquant/exchange/bybit/exchange.py +108 -0
- walrasquant/exchange/bybit/factory.py +128 -0
- walrasquant/exchange/bybit/oms.py +1195 -0
- walrasquant/exchange/bybit/rest_api.py +570 -0
- walrasquant/exchange/bybit/schema.py +867 -0
- walrasquant/exchange/bybit/websockets.py +307 -0
- walrasquant/exchange/hyperliquid/__init__.py +28 -0
- walrasquant/exchange/hyperliquid/connector.py +370 -0
- walrasquant/exchange/hyperliquid/constants.py +371 -0
- walrasquant/exchange/hyperliquid/ems.py +156 -0
- walrasquant/exchange/hyperliquid/error.py +48 -0
- walrasquant/exchange/hyperliquid/exchange.py +120 -0
- walrasquant/exchange/hyperliquid/factory.py +135 -0
- walrasquant/exchange/hyperliquid/oms.py +1081 -0
- walrasquant/exchange/hyperliquid/rest_api.py +348 -0
- walrasquant/exchange/hyperliquid/schema.py +583 -0
- walrasquant/exchange/hyperliquid/websockets.py +592 -0
- walrasquant/exchange/okx/__init__.py +25 -0
- walrasquant/exchange/okx/connector.py +931 -0
- walrasquant/exchange/okx/constants.py +518 -0
- walrasquant/exchange/okx/ems.py +144 -0
- walrasquant/exchange/okx/error.py +66 -0
- walrasquant/exchange/okx/exchange.py +102 -0
- walrasquant/exchange/okx/factory.py +138 -0
- walrasquant/exchange/okx/oms.py +1199 -0
- walrasquant/exchange/okx/rest_api.py +799 -0
- walrasquant/exchange/okx/schema.py +1449 -0
- walrasquant/exchange/okx/websockets.py +420 -0
- walrasquant/exchange/registry.py +201 -0
- walrasquant/execution/__init__.py +24 -0
- walrasquant/execution/algorithm.py +968 -0
- walrasquant/execution/algorithms/__init__.py +3 -0
- walrasquant/execution/algorithms/twap.py +392 -0
- walrasquant/execution/config.py +34 -0
- walrasquant/execution/constants.py +27 -0
- walrasquant/execution/schema.py +62 -0
- walrasquant/indicator.py +382 -0
- walrasquant/push.py +77 -0
- walrasquant/schema.py +755 -0
- walrasquant/strategy.py +1805 -0
- walrasquant/tools/__init__.py +0 -0
- walrasquant/tools/pm2_wrapper.py +1016 -0
- walrasquant/web/__init__.py +26 -0
- walrasquant/web/app.py +157 -0
- walrasquant/web/server.py +92 -0
- walrasquant_lib-0.4.20.dist-info/METADATA +162 -0
- walrasquant_lib-0.4.20.dist-info/RECORD +105 -0
- walrasquant_lib-0.4.20.dist-info/WHEEL +4 -0
- walrasquant_lib-0.4.20.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
import msgspec
|
|
2
|
+
import eth_account
|
|
3
|
+
from eth_account.signers.local import LocalAccount
|
|
4
|
+
from eth_account.messages import encode_typed_data
|
|
5
|
+
from typing import Dict, Any, List, Literal, cast
|
|
6
|
+
from urllib.parse import urljoin
|
|
7
|
+
import asyncio
|
|
8
|
+
import aiohttp
|
|
9
|
+
from Crypto.Hash import keccak
|
|
10
|
+
|
|
11
|
+
from walrasquant.base.api_client import ApiClient
|
|
12
|
+
from walrasquant.exchange.hyperliquid.constants import (
|
|
13
|
+
HyperLiquidAccountType,
|
|
14
|
+
HyperLiquidRateLimiter,
|
|
15
|
+
HyperLiquidOrderRequest,
|
|
16
|
+
HyperLiquidOrderCancelRequest,
|
|
17
|
+
HyperLiquidCloidCancelRequest,
|
|
18
|
+
)
|
|
19
|
+
from walrasquant.exchange.hyperliquid.schema import (
|
|
20
|
+
HyperLiquidOrderResponse,
|
|
21
|
+
HyperLiquidCancelResponse,
|
|
22
|
+
HyperLiquidUserOrder,
|
|
23
|
+
HyperLiquidUserPerpsSummary,
|
|
24
|
+
HyperLiquidKline,
|
|
25
|
+
HyperLiquidTrade,
|
|
26
|
+
HyperLiquidOrderBook,
|
|
27
|
+
HyperLiquidTicker,
|
|
28
|
+
HyperLiquidUserSpotSummary,
|
|
29
|
+
)
|
|
30
|
+
from walrasquant.exchange.hyperliquid.error import HyperLiquidHttpError
|
|
31
|
+
from walrasquant.core.nautilius_core import LiveClock
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class HyperLiquidApiClient(ApiClient):
|
|
35
|
+
"""REST API client for Hyperliquid exchange"""
|
|
36
|
+
|
|
37
|
+
_limiter: HyperLiquidRateLimiter
|
|
38
|
+
|
|
39
|
+
def __init__(
|
|
40
|
+
self,
|
|
41
|
+
clock: LiveClock,
|
|
42
|
+
api_key: str | None = None,
|
|
43
|
+
secret: str | None = None,
|
|
44
|
+
timeout: int = 10,
|
|
45
|
+
testnet: bool = False,
|
|
46
|
+
enable_rate_limit: bool = True,
|
|
47
|
+
):
|
|
48
|
+
super().__init__(
|
|
49
|
+
clock=clock,
|
|
50
|
+
api_key=api_key,
|
|
51
|
+
secret=secret,
|
|
52
|
+
timeout=timeout,
|
|
53
|
+
rate_limiter=cast(Any, HyperLiquidRateLimiter(enable_rate_limit)),
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
self._account_type = (
|
|
57
|
+
HyperLiquidAccountType.TESTNET
|
|
58
|
+
if testnet
|
|
59
|
+
else HyperLiquidAccountType.MAINNET
|
|
60
|
+
)
|
|
61
|
+
if secret:
|
|
62
|
+
self._eth_account: LocalAccount = eth_account.Account.from_key(secret)
|
|
63
|
+
self._base_url = self._account_type.rest_url
|
|
64
|
+
self._testnet = testnet
|
|
65
|
+
self._headers = {
|
|
66
|
+
"Content-Type": "application/json",
|
|
67
|
+
# "User-Agent": "NexusTrader/1.0",
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
# Decoders for different response types
|
|
71
|
+
self._msg_decoder = msgspec.json.Decoder()
|
|
72
|
+
self._msg_encoder = msgspec.json.Encoder()
|
|
73
|
+
self._order_response_decoder = msgspec.json.Decoder(HyperLiquidOrderResponse)
|
|
74
|
+
self._cancel_response_decoder = msgspec.json.Decoder(HyperLiquidCancelResponse)
|
|
75
|
+
self._user_perps_summary_decoder = msgspec.json.Decoder(
|
|
76
|
+
HyperLiquidUserPerpsSummary
|
|
77
|
+
)
|
|
78
|
+
self._user_spot_summary_decoder = msgspec.json.Decoder(
|
|
79
|
+
HyperLiquidUserSpotSummary
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
self._kline_decoder = msgspec.json.Decoder(list[HyperLiquidKline])
|
|
83
|
+
self._trade_decoder = msgspec.json.Decoder(HyperLiquidTrade)
|
|
84
|
+
self._orderbook_decoder = msgspec.json.Decoder(HyperLiquidOrderBook)
|
|
85
|
+
self._ticker_decoder = msgspec.json.Decoder(HyperLiquidTicker)
|
|
86
|
+
self._user_order_decoder = msgspec.json.Decoder(list[HyperLiquidUserOrder])
|
|
87
|
+
|
|
88
|
+
def _get_rate_limit_cost(self, cost: int = 1, length: int = 0) -> int:
|
|
89
|
+
"""Get rate limit cost for an operation
|
|
90
|
+
|
|
91
|
+
Please refer to https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/rate-limits-and-user-limits
|
|
92
|
+
"""
|
|
93
|
+
return cost + length // 40
|
|
94
|
+
|
|
95
|
+
async def _fetch(
|
|
96
|
+
self,
|
|
97
|
+
method: str,
|
|
98
|
+
base_url: str,
|
|
99
|
+
endpoint: str,
|
|
100
|
+
payload: Dict[str, Any],
|
|
101
|
+
) -> bytes:
|
|
102
|
+
"""Asynchronous HTTP request"""
|
|
103
|
+
await self._init_session()
|
|
104
|
+
session = self._session
|
|
105
|
+
if session is None:
|
|
106
|
+
raise ValueError("HTTP session initialization failed")
|
|
107
|
+
|
|
108
|
+
url = urljoin(base_url, endpoint)
|
|
109
|
+
data = msgspec.json.encode(payload)
|
|
110
|
+
|
|
111
|
+
self._log.debug(f"Request: {method} {url}")
|
|
112
|
+
|
|
113
|
+
try:
|
|
114
|
+
async with session.request(
|
|
115
|
+
method=method,
|
|
116
|
+
url=url,
|
|
117
|
+
headers=self._headers,
|
|
118
|
+
data=data,
|
|
119
|
+
) as response:
|
|
120
|
+
raw = await response.read()
|
|
121
|
+
|
|
122
|
+
if response.status >= 400:
|
|
123
|
+
raise HyperLiquidHttpError(
|
|
124
|
+
status_code=response.status,
|
|
125
|
+
message=raw.decode() if isinstance(raw, bytes) else str(raw),
|
|
126
|
+
headers=dict(response.headers),
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
return raw
|
|
130
|
+
except asyncio.TimeoutError as e:
|
|
131
|
+
self._log.error(f"Timeout {method} {url} {e}")
|
|
132
|
+
raise
|
|
133
|
+
except aiohttp.ClientError as e:
|
|
134
|
+
self._log.error(f"Request Error {method} {url} {e}")
|
|
135
|
+
raise
|
|
136
|
+
except Exception as e:
|
|
137
|
+
self._log.error(f"Error {method} {url} {e}")
|
|
138
|
+
raise
|
|
139
|
+
|
|
140
|
+
# Market Data Endpoints
|
|
141
|
+
async def get_user_perps_summary(self) -> HyperLiquidUserPerpsSummary:
|
|
142
|
+
"""Get user perps summary"""
|
|
143
|
+
endpoint = "/info"
|
|
144
|
+
api_key = self._api_key
|
|
145
|
+
base_url = self._base_url
|
|
146
|
+
if api_key is None or base_url is None:
|
|
147
|
+
raise ValueError("api_key and base_url are required")
|
|
148
|
+
payload = {"type": "clearinghouseState", "user": self._api_key, "dex": ""}
|
|
149
|
+
await self._limiter.info_limit(cost=2)
|
|
150
|
+
raw = await self._fetch("POST", base_url, endpoint, payload)
|
|
151
|
+
return self._user_perps_summary_decoder.decode(raw)
|
|
152
|
+
|
|
153
|
+
async def get_user_spot_summary(self) -> HyperLiquidUserSpotSummary:
|
|
154
|
+
"""Get user spot summary"""
|
|
155
|
+
endpoint = "/info"
|
|
156
|
+
api_key = self._api_key
|
|
157
|
+
base_url = self._base_url
|
|
158
|
+
if api_key is None or base_url is None:
|
|
159
|
+
raise ValueError("api_key and base_url are required")
|
|
160
|
+
payload = {"type": "spotClearinghouseState", "user": api_key}
|
|
161
|
+
await self._limiter.info_limit(cost=2)
|
|
162
|
+
raw = await self._fetch("POST", base_url, endpoint, payload)
|
|
163
|
+
return self._user_spot_summary_decoder.decode(raw)
|
|
164
|
+
|
|
165
|
+
async def get_order_status(self, oid: str | int) -> dict[str, Any]:
|
|
166
|
+
"""
|
|
167
|
+
Query order status by exchange oid or client order id (cloid).
|
|
168
|
+
"""
|
|
169
|
+
endpoint = "/info"
|
|
170
|
+
api_key = self._api_key
|
|
171
|
+
base_url = self._base_url
|
|
172
|
+
if api_key is None or base_url is None:
|
|
173
|
+
raise ValueError("api_key and base_url are required")
|
|
174
|
+
payload = {"type": "orderStatus", "user": api_key, "oid": oid}
|
|
175
|
+
await self._limiter.info_limit(cost=2)
|
|
176
|
+
raw = await self._fetch("POST", base_url, endpoint, payload)
|
|
177
|
+
return self._msg_decoder.decode(raw)
|
|
178
|
+
|
|
179
|
+
async def get_klines(
|
|
180
|
+
self,
|
|
181
|
+
coin: str,
|
|
182
|
+
interval: str,
|
|
183
|
+
startTime: int | None = None,
|
|
184
|
+
endTime: int | None = None,
|
|
185
|
+
) -> List[HyperLiquidKline]:
|
|
186
|
+
"""
|
|
187
|
+
Get kline/candlestick data
|
|
188
|
+
https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint#candle-snapshot
|
|
189
|
+
"""
|
|
190
|
+
endpoint = "/info"
|
|
191
|
+
base_url = self._base_url
|
|
192
|
+
if base_url is None:
|
|
193
|
+
raise ValueError("base_url is required")
|
|
194
|
+
req: dict[str, str | int] = {
|
|
195
|
+
"coin": coin,
|
|
196
|
+
"interval": interval,
|
|
197
|
+
}
|
|
198
|
+
if startTime is not None:
|
|
199
|
+
req["startTime"] = startTime
|
|
200
|
+
if endTime is not None:
|
|
201
|
+
req["endTime"] = endTime
|
|
202
|
+
payload = {"type": "candleSnapshot", "req": req}
|
|
203
|
+
|
|
204
|
+
await self._limiter.info_limit(cost=20)
|
|
205
|
+
raw = await self._fetch("POST", base_url, endpoint, payload)
|
|
206
|
+
return self._kline_decoder.decode(raw)
|
|
207
|
+
|
|
208
|
+
def _construct_phantom_agent(
|
|
209
|
+
self, hash: bytes, is_testnet: bool = False
|
|
210
|
+
) -> Dict[str, Any]:
|
|
211
|
+
return {"source": "b" if is_testnet else "a", "connectionId": hash}
|
|
212
|
+
|
|
213
|
+
def _action_hash(
|
|
214
|
+
self, action: Dict[str, Any], nonce: int, vaultAddress: str | None = None
|
|
215
|
+
) -> bytes:
|
|
216
|
+
data = msgspec.msgpack.encode(action)
|
|
217
|
+
data += nonce.to_bytes(8, "big")
|
|
218
|
+
if vaultAddress is None:
|
|
219
|
+
data += b"\x00"
|
|
220
|
+
else:
|
|
221
|
+
data += b"\x01"
|
|
222
|
+
data += bytes.fromhex(
|
|
223
|
+
vaultAddress[2:] if vaultAddress.startswith("0x") else vaultAddress
|
|
224
|
+
)
|
|
225
|
+
return keccak.new(digest_bits=256, data=data).digest()
|
|
226
|
+
|
|
227
|
+
def _sign_l1_action(
|
|
228
|
+
self, action: Dict[str, Any], nonce: int, vaultAddress: str | None = None
|
|
229
|
+
) -> dict[str, str | int]:
|
|
230
|
+
if not hasattr(self, "_eth_account"):
|
|
231
|
+
raise ValueError("secret is required for signed HyperLiquid actions")
|
|
232
|
+
hash = self._action_hash(action, nonce, vaultAddress)
|
|
233
|
+
phantom_agent = self._construct_phantom_agent(hash, is_testnet=self._testnet)
|
|
234
|
+
encoded_data = encode_typed_data(
|
|
235
|
+
full_message={
|
|
236
|
+
"domain": {
|
|
237
|
+
"chainId": 1337,
|
|
238
|
+
"name": "Exchange",
|
|
239
|
+
"verifyingContract": "0x0000000000000000000000000000000000000000",
|
|
240
|
+
"version": "1",
|
|
241
|
+
},
|
|
242
|
+
"types": {
|
|
243
|
+
"Agent": [
|
|
244
|
+
{"name": "source", "type": "string"},
|
|
245
|
+
{"name": "connectionId", "type": "bytes32"},
|
|
246
|
+
],
|
|
247
|
+
"EIP712Domain": [
|
|
248
|
+
{"name": "name", "type": "string"},
|
|
249
|
+
{"name": "version", "type": "string"},
|
|
250
|
+
{"name": "chainId", "type": "uint256"},
|
|
251
|
+
{"name": "verifyingContract", "type": "address"},
|
|
252
|
+
],
|
|
253
|
+
},
|
|
254
|
+
"primaryType": "Agent",
|
|
255
|
+
"message": phantom_agent,
|
|
256
|
+
}
|
|
257
|
+
)
|
|
258
|
+
signed = self._eth_account.sign_message(encoded_data)
|
|
259
|
+
return {
|
|
260
|
+
"r": f"{signed.r:#x}",
|
|
261
|
+
"s": f"{signed.s:#x}",
|
|
262
|
+
"v": signed.v,
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
# Trading Endpoints
|
|
266
|
+
async def place_orders(
|
|
267
|
+
self,
|
|
268
|
+
orders: List[HyperLiquidOrderRequest],
|
|
269
|
+
grouping: Literal["na", "normalTpsl", "positionTpsl"] = "na",
|
|
270
|
+
) -> HyperLiquidOrderResponse:
|
|
271
|
+
nounce = self._clock.timestamp_ms()
|
|
272
|
+
base_url = self._base_url
|
|
273
|
+
if base_url is None:
|
|
274
|
+
raise ValueError("base_url is required")
|
|
275
|
+
orderAction = {
|
|
276
|
+
"type": "order",
|
|
277
|
+
"orders": orders,
|
|
278
|
+
"grouping": grouping,
|
|
279
|
+
}
|
|
280
|
+
signature = self._sign_l1_action(orderAction, nounce)
|
|
281
|
+
cost = self._get_rate_limit_cost(cost=1, length=len(orders))
|
|
282
|
+
await self._limiter.exchange_limit(cost=cost)
|
|
283
|
+
res = await self._fetch(
|
|
284
|
+
"POST",
|
|
285
|
+
base_url,
|
|
286
|
+
"/exchange",
|
|
287
|
+
{
|
|
288
|
+
"action": orderAction,
|
|
289
|
+
"nonce": nounce,
|
|
290
|
+
"signature": signature,
|
|
291
|
+
},
|
|
292
|
+
)
|
|
293
|
+
order = self._order_response_decoder.decode(res)
|
|
294
|
+
return order
|
|
295
|
+
|
|
296
|
+
async def cancel_orders(
|
|
297
|
+
self,
|
|
298
|
+
cancels: List[HyperLiquidOrderCancelRequest],
|
|
299
|
+
):
|
|
300
|
+
nounce = self._clock.timestamp_ms()
|
|
301
|
+
base_url = self._base_url
|
|
302
|
+
if base_url is None:
|
|
303
|
+
raise ValueError("base_url is required")
|
|
304
|
+
orderAction = {
|
|
305
|
+
"type": "cancel",
|
|
306
|
+
"cancels": cancels,
|
|
307
|
+
}
|
|
308
|
+
signature = self._sign_l1_action(orderAction, nounce)
|
|
309
|
+
# cost = self._get_rate_limit_cost(cost=1, length=len(cancels))
|
|
310
|
+
# await self._limiter.exchange_limit(cost=cost)
|
|
311
|
+
res = await self._fetch(
|
|
312
|
+
"POST",
|
|
313
|
+
base_url,
|
|
314
|
+
"/exchange",
|
|
315
|
+
{
|
|
316
|
+
"action": orderAction,
|
|
317
|
+
"nonce": nounce,
|
|
318
|
+
"signature": signature,
|
|
319
|
+
},
|
|
320
|
+
)
|
|
321
|
+
return self._cancel_response_decoder.decode(res)
|
|
322
|
+
|
|
323
|
+
async def cancel_orders_by_cloid(
|
|
324
|
+
self,
|
|
325
|
+
cancels: List[HyperLiquidCloidCancelRequest],
|
|
326
|
+
):
|
|
327
|
+
nounce = self._clock.timestamp_ms()
|
|
328
|
+
base_url = self._base_url
|
|
329
|
+
if base_url is None:
|
|
330
|
+
raise ValueError("base_url is required")
|
|
331
|
+
orderAction = {
|
|
332
|
+
"type": "cancelByCloid",
|
|
333
|
+
"cancels": cancels,
|
|
334
|
+
}
|
|
335
|
+
signature = self._sign_l1_action(orderAction, nounce)
|
|
336
|
+
# cost = self._get_rate_limit_cost(cost=1, length=len(cancels))
|
|
337
|
+
# await self._limiter.exchange_limit(cost=cost)
|
|
338
|
+
res = await self._fetch(
|
|
339
|
+
"POST",
|
|
340
|
+
base_url,
|
|
341
|
+
"/exchange",
|
|
342
|
+
{
|
|
343
|
+
"action": orderAction,
|
|
344
|
+
"nonce": nounce,
|
|
345
|
+
"signature": signature,
|
|
346
|
+
},
|
|
347
|
+
)
|
|
348
|
+
return self._cancel_response_decoder.decode(res)
|