hyperquant 1.47__tar.gz → 1.48__tar.gz

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.
Files changed (46) hide show
  1. {hyperquant-1.47 → hyperquant-1.48}/PKG-INFO +1 -1
  2. {hyperquant-1.47 → hyperquant-1.48}/pyproject.toml +2 -2
  3. {hyperquant-1.47 → hyperquant-1.48}/src/hyperquant/broker/models/polymarket.py +142 -15
  4. {hyperquant-1.47 → hyperquant-1.48}/src/hyperquant/broker/polymarket.py +121 -28
  5. {hyperquant-1.47 → hyperquant-1.48}/uv.lock +2 -593
  6. {hyperquant-1.47 → hyperquant-1.48}/.gitignore +0 -0
  7. {hyperquant-1.47 → hyperquant-1.48}/README.md +0 -0
  8. {hyperquant-1.47 → hyperquant-1.48}/requirements-dev.lock +0 -0
  9. {hyperquant-1.47 → hyperquant-1.48}/requirements.lock +0 -0
  10. {hyperquant-1.47 → hyperquant-1.48}/src/hyperquant/__init__.py +0 -0
  11. {hyperquant-1.47 → hyperquant-1.48}/src/hyperquant/broker/auth.py +0 -0
  12. {hyperquant-1.47 → hyperquant-1.48}/src/hyperquant/broker/bitget.py +0 -0
  13. {hyperquant-1.47 → hyperquant-1.48}/src/hyperquant/broker/bitmart.py +0 -0
  14. {hyperquant-1.47 → hyperquant-1.48}/src/hyperquant/broker/coinw.py +0 -0
  15. {hyperquant-1.47 → hyperquant-1.48}/src/hyperquant/broker/deepcoin.py +0 -0
  16. {hyperquant-1.47 → hyperquant-1.48}/src/hyperquant/broker/edgex.py +0 -0
  17. {hyperquant-1.47 → hyperquant-1.48}/src/hyperquant/broker/hyperliquid.py +0 -0
  18. {hyperquant-1.47 → hyperquant-1.48}/src/hyperquant/broker/lbank.py +0 -0
  19. {hyperquant-1.47 → hyperquant-1.48}/src/hyperquant/broker/lib/edgex_sign.py +0 -0
  20. {hyperquant-1.47 → hyperquant-1.48}/src/hyperquant/broker/lib/hpstore.py +0 -0
  21. {hyperquant-1.47 → hyperquant-1.48}/src/hyperquant/broker/lib/hyper_types.py +0 -0
  22. {hyperquant-1.47 → hyperquant-1.48}/src/hyperquant/broker/lib/polymarket/ctfAbi.py +0 -0
  23. {hyperquant-1.47 → hyperquant-1.48}/src/hyperquant/broker/lib/polymarket/safeAbi.py +0 -0
  24. {hyperquant-1.47 → hyperquant-1.48}/src/hyperquant/broker/lib/util.py +0 -0
  25. {hyperquant-1.47 → hyperquant-1.48}/src/hyperquant/broker/lighter.py +0 -0
  26. {hyperquant-1.47 → hyperquant-1.48}/src/hyperquant/broker/models/apexpro.py +0 -0
  27. {hyperquant-1.47 → hyperquant-1.48}/src/hyperquant/broker/models/bitget.py +0 -0
  28. {hyperquant-1.47 → hyperquant-1.48}/src/hyperquant/broker/models/bitmart.py +0 -0
  29. {hyperquant-1.47 → hyperquant-1.48}/src/hyperquant/broker/models/coinw.py +0 -0
  30. {hyperquant-1.47 → hyperquant-1.48}/src/hyperquant/broker/models/deepcoin.py +0 -0
  31. {hyperquant-1.47 → hyperquant-1.48}/src/hyperquant/broker/models/edgex.py +0 -0
  32. {hyperquant-1.47 → hyperquant-1.48}/src/hyperquant/broker/models/hyperliquid.py +0 -0
  33. {hyperquant-1.47 → hyperquant-1.48}/src/hyperquant/broker/models/lbank.py +0 -0
  34. {hyperquant-1.47 → hyperquant-1.48}/src/hyperquant/broker/models/lighter.py +0 -0
  35. {hyperquant-1.47 → hyperquant-1.48}/src/hyperquant/broker/models/ourbit.py +0 -0
  36. {hyperquant-1.47 → hyperquant-1.48}/src/hyperquant/broker/ourbit.py +0 -0
  37. {hyperquant-1.47 → hyperquant-1.48}/src/hyperquant/broker/ws.py +0 -0
  38. {hyperquant-1.47 → hyperquant-1.48}/src/hyperquant/core.py +0 -0
  39. {hyperquant-1.47 → hyperquant-1.48}/src/hyperquant/datavison/_util.py +0 -0
  40. {hyperquant-1.47 → hyperquant-1.48}/src/hyperquant/datavison/binance.py +0 -0
  41. {hyperquant-1.47 → hyperquant-1.48}/src/hyperquant/datavison/coinglass.py +0 -0
  42. {hyperquant-1.47 → hyperquant-1.48}/src/hyperquant/datavison/okx.py +0 -0
  43. {hyperquant-1.47 → hyperquant-1.48}/src/hyperquant/db.py +0 -0
  44. {hyperquant-1.47 → hyperquant-1.48}/src/hyperquant/draw.py +0 -0
  45. {hyperquant-1.47 → hyperquant-1.48}/src/hyperquant/logkit.py +0 -0
  46. {hyperquant-1.47 → hyperquant-1.48}/src/hyperquant/notikit.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hyperquant
3
- Version: 1.47
3
+ Version: 1.48
4
4
  Summary: A minimal yet hyper-efficient backtesting framework for quantitative trading
5
5
  Project-URL: Homepage, https://github.com/yourusername/hyperquant
6
6
  Project-URL: Issues, https://github.com/yourusername/hyperquant/issues
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "hyperquant"
3
- version = "1.47"
3
+ version = "1.48"
4
4
  description = "A minimal yet hyper-efficient backtesting framework for quantitative trading"
5
5
  authors = [
6
6
  { name = "MissinA", email = "1421329142@qq.com" }
@@ -17,7 +17,7 @@ dependencies = [
17
17
  "eth-account>=0.10.0",
18
18
  "web3>=7.14.0",
19
19
  "python-dotenv>=1.2.1",
20
- "coincurve>=21.0.0",
20
+ "coincurve>=21.0.0"
21
21
  ]
22
22
  readme = "README.md"
23
23
  requires-python = ">=3.13"
@@ -3,6 +3,7 @@ from __future__ import annotations
3
3
  import json
4
4
  from dataclasses import dataclass, field
5
5
  from heapq import heappop, heappush
6
+ import time
6
7
  from typing import TYPE_CHECKING, Any, Iterable
7
8
 
8
9
  from pybotters.store import DataStore, DataStoreCollection
@@ -15,11 +16,37 @@ if TYPE_CHECKING:
15
16
  class Position(DataStore):
16
17
  """Position DataStore keyed by Polymarket token id."""
17
18
 
18
- _KEYS = ["asset", "outcome"]
19
+ _KEYS = ["asset"]
19
20
 
20
- def _on_response(self, msg: Item) -> None:
21
+ def _init(self) -> None:
22
+ # 缓存LIVE订单已计入的size_matched: {order_id: size_matched}
23
+ self._live_cache: dict[str, float] = {}
24
+
25
+
26
+ def sorted(
27
+ self, query: Item | None = None, limit: int | None = None
28
+ ) -> dict[str, list[Item]]:
29
+ """按ts降序排列,按outcome分组"""
30
+ if query is None:
31
+ query = {}
32
+ result: dict[str, list[Item]] = {}
33
+ for item in self:
34
+ if all(k in item and query[k] == item[k] for k in query):
35
+ outcome = item.get("outcome") or "unknown"
36
+ if outcome not in result:
37
+ result[outcome] = []
38
+ result[outcome].append(item)
39
+ for outcome in result:
40
+ result[outcome].sort(key=lambda x: (x.get("eventSlug") or '0'), reverse=True)
41
+ if limit:
42
+ result[outcome] = result[outcome][:limit]
43
+ return result
44
+
45
+ def _on_response(self, msg: list[Item]) -> None:
21
46
  if msg:
22
47
  self._clear()
48
+ for rec in msg:
49
+ rec["ts"] = 0
23
50
  self._update(msg)
24
51
 
25
52
  def on_trade(self, trade: Item) -> None:
@@ -33,6 +60,7 @@ class Position(DataStore):
33
60
  size_raw = trade.get("size")
34
61
  price_raw = trade.get("price")
35
62
 
63
+
36
64
  if not asset_id or not outcome or side not in {"BUY", "SELL"}:
37
65
  return
38
66
 
@@ -45,6 +73,8 @@ class Position(DataStore):
45
73
  except (TypeError, ValueError):
46
74
  price = None
47
75
 
76
+
77
+
48
78
  key = {"asset": asset_id, "outcome": outcome}
49
79
  existing = self.get(key) or {}
50
80
 
@@ -86,6 +116,85 @@ class Position(DataStore):
86
116
  else:
87
117
  self._insert([rec])
88
118
 
119
+ def _on_order(self, order: dict[str, Any]) -> None:
120
+ """通过order更新持仓,处理LIVE时部分成交的增量统计"""
121
+ # print(order)
122
+ # order写入本地尝试后续分析
123
+ with open("polymarket_orders.log", "a") as f:
124
+ f.write(json.dumps(order) + "\n")
125
+ order_id = order.get("id")
126
+ asset_id = order.get("asset_id")
127
+ outcome = order.get("outcome")
128
+ side = str(order.get("side") or "").upper()
129
+ size_matched = float(order.get("size_matched") or 0)
130
+ price = float(order.get("price") or 0)
131
+ status = str(order.get("status") or "").upper()
132
+
133
+ if not order_id or not asset_id or not outcome or side not in {"BUY", "SELL"}:
134
+ return
135
+
136
+ cached = self._live_cache.get(order_id, 0.0)
137
+
138
+ if status == "LIVE":
139
+ # LIVE时计算增量
140
+ delta = size_matched - cached
141
+ if delta > 0:
142
+ self._live_cache[order_id] = size_matched
143
+ self._apply_trade(asset_id, outcome, side, delta, price)
144
+ elif status in {"CANCELED", "MATCHED"}:
145
+ # 订单完结:计算最终增量 = 最终size_matched - 已计入的cached
146
+ delta = size_matched - cached
147
+ if delta > 0:
148
+ self._apply_trade(asset_id, outcome, side, delta, price)
149
+ # 清理缓存
150
+ self._live_cache.pop(order_id, None)
151
+
152
+ def _apply_trade(self, asset_id: str, outcome: str, side: str, size: float, price: float) -> None:
153
+ """应用成交到持仓"""
154
+ if size <= 0:
155
+ return
156
+
157
+ key = {"asset": asset_id, "outcome": outcome}
158
+ existing = self.get(key) or {}
159
+
160
+ cur_size = float(existing.get("size") or 0.0)
161
+ cur_total_bought = float(existing.get("totalBought") or 0.0)
162
+ cur_avg_price = float(existing.get("avgPrice") or 0.0)
163
+ cur_cost = cur_size * cur_avg_price
164
+
165
+ if side == "BUY":
166
+ new_size = cur_size + size
167
+ total_bought = cur_total_bought + size
168
+ effective_price = price if price else cur_avg_price
169
+ new_cost = cur_cost + size * effective_price
170
+ else: # SELL
171
+ new_size = cur_size - size
172
+ total_bought = cur_total_bought
173
+ new_cost = cur_cost - min(size, cur_size) * cur_avg_price
174
+
175
+ if new_size <= 0:
176
+ new_size = 0.0
177
+ avg_price = 0.0
178
+ new_cost = 0.0
179
+ else:
180
+ avg_price = max(new_cost, 0.0) / new_size
181
+
182
+ rec: dict[str, Any] = {
183
+ "asset": asset_id,
184
+ "outcome": outcome,
185
+ "side": side,
186
+ "size": new_size,
187
+ "totalBought": total_bought,
188
+ "avgPrice": avg_price,
189
+ "ts": int(time.time() * 1000),
190
+ }
191
+
192
+ if existing:
193
+ self._update([rec])
194
+ else:
195
+ self._insert([rec])
196
+
197
+
89
198
 
90
199
  class Fill(DataStore):
91
200
  """Fill records keyed by maker order id."""
@@ -169,6 +278,7 @@ class Order(DataStore):
169
278
  return normalized
170
279
 
171
280
  def _on_response(self, items: list[dict[str, Any]] | dict[str, Any]) -> None:
281
+ """增量同步:insert新增、update变更、delete消失的订单"""
172
282
  rows: list[dict[str, Any]] = []
173
283
  if isinstance(items, dict):
174
284
  items = [items]
@@ -176,20 +286,34 @@ class Order(DataStore):
176
286
  norm = self._normalize(it)
177
287
  if norm:
178
288
  rows.append(norm)
179
- self._clear()
180
- if rows:
181
- self._insert(rows)
289
+
290
+ # 构建新订单id集合
291
+ new_ids = {r["id"] for r in rows}
292
+
293
+ # 删除不再存在的订单(传入完整状态)
294
+ to_delete = [dict(item) for item in self if item["id"] not in new_ids]
295
+ if to_delete:
296
+ self._delete(to_delete)
297
+
298
+ # 插入或更新
299
+ for row in rows:
300
+ existing = self.get({"id": row["id"]})
301
+ if existing:
302
+ # 有变化才update
303
+ if any(existing.get(k) != row.get(k) for k in row):
304
+ self._update([row])
305
+ else:
306
+ self._insert([row])
182
307
 
183
308
  def _on_message(self, msg: dict[str, Any]) -> None:
184
- norm = self._normalize(msg)
185
- if not norm:
186
- return
187
- key = {"id": norm["id"]}
188
- if self.get(key):
189
- self._update([norm])
190
- else:
191
- self._insert([norm])
309
+ status = str(msg.get("status") or "").upper()
310
+ # CANCELED MATCHED 删除
311
+ order = self.get({"id": msg.get("id")})
312
+ if not order:
313
+ self._insert([msg])
192
314
 
315
+ if status in {"CANCELED", "MATCHED"}:
316
+ self._delete([msg])
193
317
 
194
318
  class MyTrade(DataStore):
195
319
  """User trades keyed by trade id."""
@@ -795,6 +919,7 @@ class PolymarketDataStore(DataStoreCollection):
795
919
  "order_type": "GTC",
796
920
  "created_at": 1762912331
797
921
  }
922
+
798
923
  """
799
924
 
800
925
  return self._get("order")
@@ -908,10 +1033,12 @@ class PolymarketDataStore(DataStoreCollection):
908
1033
  self.book._on_message(m)
909
1034
  elif msg_type == "order":
910
1035
  self.orders._on_message(m)
1036
+ self.position._on_order(m)
1037
+
911
1038
  elif msg_type == "trade":
912
1039
  self.mytrade._on_message(m)
913
- self.fill._on_trade(m)
914
- self.position.on_trade(m)
1040
+ # self.fill._on_trade(m)
1041
+ # self.position.on_trade(m)
915
1042
  elif msg_type == 'orders_matched':
916
1043
  payload = m.get("payload") or {}
917
1044
  if not payload:
@@ -1,10 +1,12 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import asyncio
4
+ import logging
4
5
  from contextlib import suppress
5
6
  from datetime import UTC, datetime, timedelta
6
7
  from functools import lru_cache
7
8
  import os
9
+ import time
8
10
  from typing import Any, Iterable, Iterator, Literal, Mapping, Sequence
9
11
 
10
12
  import json
@@ -227,6 +229,16 @@ class Polymarket:
227
229
  signature_type: int | None = None,
228
230
  funder: str | None = None
229
231
  ) -> None:
232
+ # Logger (per-class, safe default)
233
+ self.logger = logging.getLogger(f"{API_NAME}.{self.__class__.__name__}")
234
+ if not self.logger.handlers:
235
+ handler = logging.StreamHandler()
236
+ formatter = logging.Formatter(
237
+ "[%(asctime)s][%(levelname)s][%(name)s] %(message)s"
238
+ )
239
+ handler.setFormatter(formatter)
240
+ self.logger.addHandler(handler)
241
+ self.logger.setLevel(logging.INFO)
230
242
  self.client = client
231
243
  self.rest_api = (rest_api or DEFAULT_REST_ENDPOINT).rstrip("/")
232
244
  self.ws_public = ws_public or DEFAULT_WS_ENDPOINT
@@ -271,22 +283,29 @@ class Polymarket:
271
283
  "book",
272
284
  "books",
273
285
  "position",
274
- "history_position",
275
286
  "orders",
276
- ] = "all",
287
+ ] | Sequence[str] = "all",
277
288
  *,
278
289
  token_ids: Sequence[str] | str | None = None,
279
290
  limit: int | None = None,
280
- funder: str | None = None,
281
- event_id: str | None = None
282
291
  ) -> None:
283
- """Refresh cached data using Polymarket REST endpoints."""
292
+ """Refresh cached data using Polymarket REST endpoints.
293
+
294
+ update_type 可以是单个字符串或列表,例如:
295
+ update_type='position'
296
+ update_type=['position', 'orders']
297
+ """
298
+ # 统一转为 set
299
+ if isinstance(update_type, str):
300
+ types = {update_type}
301
+ else:
302
+ types = set(update_type)
284
303
 
285
- include_detail = update_type in {"all", "detail", "markets"}
286
- include_books = update_type in {"all", "book", "books"}
287
- include_position = update_type in {"all", "position"}
288
- include_history_position = update_type in {"all", "history_position"}
289
- include_orders = update_type in {"all", "orders"}
304
+ include_detail = "all" in types or "detail" in types or "markets" in types
305
+ include_books = "all" in types or "book" in types or "books" in types
306
+ include_position = "all" in types or "position" in types
307
+ include_history_position = "history_position" in types
308
+ include_orders = "all" in types or "orders" in types
290
309
 
291
310
  if include_books and token_ids is None:
292
311
  raise ValueError("token_ids are required when updating books")
@@ -315,21 +334,11 @@ class Polymarket:
315
334
  )
316
335
 
317
336
  if include_position or include_history_position:
318
- funder = funder or self.funder
319
- path = '/positions' if include_position else '/closed-positions'
320
- params = {"user": funder, 'sizeThreshold': 0.1}
321
- if event_id:
322
- params.update({'eventId': event_id})
323
337
  tasks.append(
324
338
  (
325
339
  "position",
326
340
  asyncio.create_task(
327
- self._rest(
328
- "GET",
329
- path,
330
- params=params,
331
- host=DEFAULT_DATA_ENDPOINT
332
- )
341
+ self.get_mergeable_positions()
333
342
  ),
334
343
  )
335
344
  )
@@ -342,8 +351,25 @@ class Polymarket:
342
351
  raise ValueError(f"Unsupported update_type={update_type}")
343
352
 
344
353
  results: dict[str, Any] = {}
345
- for key, fut in tasks:
346
- results[key] = await fut
354
+
355
+ keys = [k for k, _ in tasks]
356
+ futs = [f for _, f in tasks]
357
+
358
+ done = await asyncio.gather(*futs, return_exceptions=True)
359
+
360
+ for key, res in zip(keys, done):
361
+ if isinstance(res, Exception):
362
+ # REST 更新为 best-effort:记录错误但不中断整体流程
363
+ try:
364
+ logger = getattr(self, "logger", None)
365
+ if logger:
366
+ logger.warning(f"[update] {key} failed: {res}", exc_info=True)
367
+ else:
368
+ print(f"[update] {key} failed: {res}")
369
+ except Exception:
370
+ pass
371
+ continue
372
+ results[key] = res
347
373
 
348
374
 
349
375
  if "books" in results:
@@ -441,12 +467,12 @@ class Polymarket:
441
467
 
442
468
  wsapp = self.client.ws_connect(
443
469
  RTS_DATA_ENDPOINT,
470
+ send_json=payload,
444
471
  hdlr_str=callback,
445
472
  heartbeat=5,
446
473
  )
447
474
 
448
475
  await wsapp._event.wait()
449
- await wsapp.current_ws.send_json(payload)
450
476
  return wsapp
451
477
 
452
478
 
@@ -480,6 +506,9 @@ class Polymarket:
480
506
  self,
481
507
  callback: Any = None,
482
508
  markets: Sequence[str] | None = None,
509
+ rest_sync: bool = True,
510
+ rest_order_sync_interval: int = 5,
511
+ rest_position_sync_interval: int = 8,
483
512
  ) -> pybotters.ws.WebSocketApp:
484
513
  """Subscribe to personal updates (requires authentication)."""
485
514
 
@@ -487,8 +516,15 @@ class Polymarket:
487
516
  if not creds:
488
517
  raise RuntimeError("Polymarket API credentials are required for personal subscriptions")
489
518
 
519
+ # 记录 position store 最后更新时间
520
+ last_position_update = time.time()
521
+
490
522
  def _handler(message, ws=None):
523
+ nonlocal last_position_update
491
524
  self.store.onmessage(message, ws)
525
+ # 检测是否是 position 相关消息
526
+ if isinstance(message, dict) and message.get('event_type') in ('order', 'trade'):
527
+ last_position_update = time.time()
492
528
  if callback:
493
529
  callback(message, ws)
494
530
 
@@ -503,14 +539,45 @@ class Polymarket:
503
539
  auth = {"apiKey": api_key, "secret": api_secret, "passphrase": api_passphrase}
504
540
  payload = {"markets": list(markets or []), "type": "user", "auth": auth}
505
541
 
542
+ # 在开始前用rest_api同步持仓
543
+ await self.update('position')
544
+
545
+ # 后台任务:3秒无更新则同步持仓
546
+ async def _rest_sync_watchdog():
547
+ nonlocal last_position_update
548
+ last_orders_update = time.time()
549
+ while True:
550
+ await asyncio.sleep(1)
551
+ now = time.time()
552
+ # position: 6秒无更新则同步
553
+ if now - last_position_update > rest_position_sync_interval:
554
+ try:
555
+ await self.update('position')
556
+ last_position_update = now
557
+ except Exception:
558
+ pass
559
+ # orders: 每3秒同步一次
560
+ if now - last_orders_update > rest_order_sync_interval:
561
+ try:
562
+ await self.update('orders')
563
+ last_orders_update = now
564
+ except Exception:
565
+ pass
566
+
567
+ if rest_sync:
568
+ asyncio.create_task(_rest_sync_watchdog())
569
+
570
+ # 使用 send_json 参数,这样重连后会自动重新订阅
506
571
  self._ws_personal = self.client.ws_connect(
507
572
  "wss://ws-subscriptions-clob.polymarket.com/ws/user",
573
+ send_json=payload,
508
574
  hdlr_json=effective_cb,
509
575
  heartbeat=30,
510
576
  auth=None,
511
577
  )
512
578
  await self._ws_personal._event.wait()
513
- await self._ws_personal.current_ws.send_json(payload)
579
+
580
+
514
581
  return self._ws_personal
515
582
 
516
583
  async def sub_trades(self, slug: str):
@@ -522,7 +589,6 @@ class Polymarket:
522
589
  "topic": "activity",
523
590
  "type": "orders_matched",
524
591
  "filters": json.dumps({"event_slug": slug}, separators=(',', ':'))
525
- # "filters": "{\"event_slug\":\"btc-updown-15m-1762951500\"}"
526
592
  }
527
593
  ]
528
594
  }
@@ -537,14 +603,14 @@ class Polymarket:
537
603
 
538
604
  self.store.onmessage(data, ws)
539
605
 
540
-
606
+ # 使用 send_json 参数,重连后自动重新订阅
541
607
  wsapp = self.client.ws_connect(
542
608
  RTS_DATA_ENDPOINT,
609
+ send_json=payload,
543
610
  hdlr_str=callback,
544
611
  heartbeat=5
545
612
  )
546
613
  await wsapp._event.wait()
547
- await wsapp.current_ws.send_json(payload)
548
614
  return wsapp
549
615
 
550
616
 
@@ -1433,6 +1499,33 @@ class Polymarket:
1433
1499
  position = position / 1e6
1434
1500
  return position
1435
1501
 
1502
+ async def get_mergeable_positions(
1503
+ self,
1504
+ *,
1505
+ size_threshold: float = 0.1,
1506
+ limit: int = 100,
1507
+ sort_by: str = "TOKENS",
1508
+ sort_direction: str = "DESC",
1509
+ user: str | None = None,
1510
+ neg_risk: bool = False,
1511
+ mergeable: bool = True,
1512
+ ) -> Any:
1513
+ params = {
1514
+ "sizeThreshold": str(size_threshold),
1515
+ "limit": str(limit),
1516
+ "sortBy": sort_by,
1517
+ "sortDirection": sort_direction,
1518
+ "mergeable": str(mergeable).lower(),
1519
+ }
1520
+ if user is not None:
1521
+ params["user"] = user
1522
+ else:
1523
+ params['user'] = self.funder
1524
+
1525
+ if neg_risk:
1526
+ params["negRisk"] = "true"
1527
+ return await self._rest("GET", "/positions", params=params, host=DEFAULT_DATA_ENDPOINT)
1528
+
1436
1529
 
1437
1530
  async def merge_tokens_strict(
1438
1531
  self,