hyperquant 0.26__tar.gz → 0.31__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 (30) hide show
  1. {hyperquant-0.26 → hyperquant-0.31}/PKG-INFO +1 -1
  2. hyperquant-0.31/data/logs/notikit.log +0 -0
  3. {hyperquant-0.26 → hyperquant-0.31}/pyproject.toml +1 -1
  4. {hyperquant-0.26 → hyperquant-0.31}/src/hyperquant/__init__.py +3 -1
  5. hyperquant-0.31/src/hyperquant/broker/auth.py +51 -0
  6. {hyperquant-0.26 → hyperquant-0.31}/src/hyperquant/broker/hyperliquid.py +67 -35
  7. hyperquant-0.31/src/hyperquant/broker/models/hyperliquid.py +284 -0
  8. hyperquant-0.31/src/hyperquant/broker/models/ourbit.py +502 -0
  9. hyperquant-0.31/src/hyperquant/broker/ourbit.py +247 -0
  10. hyperquant-0.31/src/hyperquant/broker/ws.py +12 -0
  11. hyperquant-0.31/test.py +106 -0
  12. {hyperquant-0.26 → hyperquant-0.31}/uv.lock +1 -1
  13. hyperquant-0.26/test.py +0 -52
  14. {hyperquant-0.26 → hyperquant-0.31}/.gitignore +0 -0
  15. {hyperquant-0.26 → hyperquant-0.31}/.python-version +0 -0
  16. {hyperquant-0.26 → hyperquant-0.31}/README.md +0 -0
  17. {hyperquant-0.26 → hyperquant-0.31}/pub.sh +0 -0
  18. {hyperquant-0.26 → hyperquant-0.31}/requirements-dev.lock +0 -0
  19. {hyperquant-0.26 → hyperquant-0.31}/requirements.lock +0 -0
  20. {hyperquant-0.26 → hyperquant-0.31}/src/hyperquant/broker/lib/hpstore.py +0 -0
  21. {hyperquant-0.26 → hyperquant-0.31}/src/hyperquant/broker/lib/hyper_types.py +0 -0
  22. {hyperquant-0.26 → hyperquant-0.31}/src/hyperquant/core.py +0 -0
  23. {hyperquant-0.26 → hyperquant-0.31}/src/hyperquant/datavison/_util.py +0 -0
  24. {hyperquant-0.26 → hyperquant-0.31}/src/hyperquant/datavison/binance.py +0 -0
  25. {hyperquant-0.26 → hyperquant-0.31}/src/hyperquant/datavison/coinglass.py +0 -0
  26. {hyperquant-0.26 → hyperquant-0.31}/src/hyperquant/datavison/okx.py +0 -0
  27. {hyperquant-0.26 → hyperquant-0.31}/src/hyperquant/db.py +0 -0
  28. {hyperquant-0.26 → hyperquant-0.31}/src/hyperquant/draw.py +0 -0
  29. {hyperquant-0.26 → hyperquant-0.31}/src/hyperquant/logkit.py +0 -0
  30. {hyperquant-0.26 → hyperquant-0.31}/src/hyperquant/notikit.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hyperquant
3
- Version: 0.26
3
+ Version: 0.31
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
File without changes
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "hyperquant"
3
- version = "0.26"
3
+ version = "0.31"
4
4
  description = "A minimal yet hyper-efficient backtesting framework for quantitative trading"
5
5
  authors = [
6
6
  { name = "MissinA", email = "1421329142@qq.com" }
@@ -3,4 +3,6 @@ from .draw import *
3
3
  from .logkit import *
4
4
  from .datavison import *
5
5
  from .notikit import *
6
- __version__ = "0.1.0"
6
+ __version__ = "0.1.0"
7
+
8
+ from .broker import auth, ws
@@ -0,0 +1,51 @@
1
+ import time
2
+ import hashlib
3
+ from typing import Any
4
+ from multidict import CIMultiDict
5
+ from yarl import URL
6
+ import pybotters
7
+ import json as pyjson
8
+ from urllib.parse import urlencode
9
+
10
+
11
+ def md5_hex(s: str) -> str:
12
+ return hashlib.md5(s.encode("utf-8")).hexdigest()
13
+
14
+
15
+ # 🔑 Ourbit 的鉴权函数
16
+ class Auth:
17
+ @staticmethod
18
+ def ourbit(args: tuple[str, URL], kwargs: dict[str, Any]) -> tuple[str, URL]:
19
+ method: str = args[0]
20
+ url: URL = args[1]
21
+ data = kwargs.get("data") or {}
22
+ headers: CIMultiDict = kwargs["headers"]
23
+
24
+ # 从 session 里取 token
25
+ session = kwargs["session"]
26
+ token = session.__dict__["_apis"][pybotters.auth.Hosts.items[url.host].name][0]
27
+
28
+ # 时间戳 & body
29
+ now_ms = int(time.time() * 1000)
30
+ raw_body_for_sign = data if isinstance(data, str) else pyjson.dumps(data, separators=(",", ":"), ensure_ascii=False)
31
+
32
+ # 签名
33
+ mid_hash = md5_hex(f"{token}{now_ms}")[7:]
34
+ final_hash = md5_hex(f"{now_ms}{raw_body_for_sign}{mid_hash}")
35
+
36
+ # 设置 headers
37
+ headers.update({
38
+ "Authorization": token,
39
+ "Language": "Chinese",
40
+ "language": "Chinese",
41
+ "Content-Type": "application/json",
42
+ "x-ourbit-sign": final_hash,
43
+ "x-ourbit-nonce": str(now_ms),
44
+ })
45
+
46
+ # 更新 kwargs.body,保证发出去的与签名一致
47
+ kwargs.update({"data": raw_body_for_sign})
48
+
49
+ return args
50
+
51
+ pybotters.auth.Hosts.items['futures.ourbit.com'] = pybotters.auth.Item("ourbit", Auth.ourbit)
@@ -26,10 +26,34 @@ from typing import Any, Dict, Optional
26
26
  import pybotters
27
27
  from yarl import URL
28
28
 
29
- from .lib.hpstore import MyHyperStore
30
- from .lib.hyper_types import AccountBalance
29
+ from .models.hyperliquid import MyHyperStore
31
30
 
32
- import uuid
31
+ def to_cloid(s: str) -> str:
32
+ """
33
+ 可逆地将字符串转为 Hyperliquid cloid 格式(要求字符串最多16字节,超出报错)。
34
+ :param s: 原始字符串
35
+ :return: 形如0x...的cloid字符串
36
+ """
37
+ b = s.encode('utf-8')
38
+ if len(b) > 16:
39
+ raise ValueError("String too long for reversible cloid (max 16 bytes)")
40
+ # 补齐到16字节
41
+ b = b.ljust(16, b'\0')
42
+ return "0x" + b.hex()
43
+
44
+ def cloid_to_str(cloid: str) -> str:
45
+ """
46
+ 从cloid还原回原始字符串
47
+ :param cloid: 形如0x...的cloid字符串
48
+ :return: 原始字符串
49
+ """
50
+ try:
51
+ if not (cloid.startswith("0x") and len(cloid) == 34):
52
+ raise ValueError("Invalid cloid format for reversal")
53
+ b = bytes.fromhex(cloid[2:])
54
+ return b.rstrip(b'\0').decode('utf-8')
55
+ except Exception as e:
56
+ return ''
33
57
 
34
58
 
35
59
  __all__ = [
@@ -56,6 +80,7 @@ class AssetMeta:
56
80
  asset_id: int
57
81
  name: str
58
82
  sz_decimals: int
83
+ tick_size: float = None
59
84
 
60
85
  @dataclass(frozen=True, slots=True)
61
86
  class SpotAssetMeta:
@@ -80,31 +105,27 @@ class OrderData():
80
105
 
81
106
  _DECIMAL_CTX_5 = decimal.Context(prec=5)
82
107
 
108
+ def normalize_number(n):
109
+ # 能去掉小数点后多余的零
110
+ return format(decimal.Decimal(str(n)).normalize(), "f")
83
111
 
84
- def _normalize_number(number: str) -> str:
85
- """Normalize a number.
86
-
87
- e.g. "3300.0" -> "3300"
88
-
89
- Hyperliquid API expects normalized numbers. Otherwise, `L1 error` will occur.
112
+ def _fmt_price(price: float, sz_decimals: int, *, max_decimals: int = 6) -> float:
90
113
  """
91
- return format(decimal.Decimal(number).normalize(), "f")
92
-
93
- def _fmt_price(price: float, sz_decimals: int, *, max_decimals: int = 6) -> str:
94
- """Format *price* according to Hyperliquid rules.
95
-
96
- * For `price >= 1` keep five significant digits.
97
- * For small prices keep *max_decimals - sz_decimals* after the dot.
114
+ 格式化价格:
115
+ - 大于100000直接取整数
116
+ - 其它情况保留5个有效数字,然后再按max_decimals-sz_decimals截断小数位
98
117
  """
99
- if price >= 1:
100
- return format(_DECIMAL_CTX_5.create_decimal(price).normalize(), "f")
118
+ if price > 100_000:
119
+ return str(round(price))
120
+ # 先保留5个有效数字,再截断小数位
121
+ price_5sf = float(f"{price:.5g}")
122
+ return normalize_number(round(price_5sf, max_decimals - sz_decimals))
101
123
 
102
- decimal_places = max_decimals - sz_decimals
103
- return _normalize_number(format(decimal.Decimal(price).quantize(decimal.Decimal(f"1e-{decimal_places}")), "f"))
104
-
105
-
106
- def _fmt_size(size: float, sz_decimals: int) -> str:
107
- return _normalize_number(format(decimal.Decimal(size).quantize(decimal.Decimal(f"1e-{sz_decimals}")), "f"))
124
+ def _fmt_size(sz: float, sz_decimals: int) -> float:
125
+ """
126
+ 格式化数量:直接按 sz_decimals 小数位截断
127
+ """
128
+ return normalize_number(round(sz, sz_decimals))
108
129
 
109
130
 
110
131
  # ╭─────────────────────────────────────────────────────────────────────────╮
@@ -144,6 +165,7 @@ class HyperliquidTrader:
144
165
 
145
166
  self._ws_app: Optional[pybotters.ws.WebSocketConnection] = None
146
167
  self.store: MyHyperStore = MyHyperStore()
168
+
147
169
 
148
170
 
149
171
  # ──────────────────────────────────────────────────────────────────────
@@ -165,6 +187,7 @@ class HyperliquidTrader:
165
187
  if self._user:
166
188
  await self.store.initialize(
167
189
  ("orders", self._client.post(_INFO, data={"type": "openOrders", "user": self._user})),
190
+ ("positions", self._client.post(_INFO, data={"type": "clearinghouseState", "user": self._user})),
168
191
  )
169
192
  self._client.ws_connect(
170
193
  self._wss_url,
@@ -198,6 +221,12 @@ class HyperliquidTrader:
198
221
  async def __aexit__(self, exc_type, exc, tb): # noqa: D401
199
222
  if not self._external_client and self._client is not None:
200
223
  await self._client.__aexit__(exc_type, exc, tb)
224
+
225
+ async def sync_orders(self):
226
+ await self.store.orders._clear()
227
+ await self.store.initialize(
228
+ ("orders", self._client.post(_INFO, data={"type": "openOrders", "user": self._user})),
229
+ )
201
230
 
202
231
  # ──────────────────────────────────────────────────────────────────────
203
232
  # Internal – metadata & formatting helpers
@@ -209,7 +238,7 @@ class HyperliquidTrader:
209
238
  raise RuntimeError(f"Failed to fetch meta: {resp.error}")
210
239
 
211
240
  self._assets = {
212
- d["name"]: AssetMeta(asset_id=i, name=d["name"], sz_decimals=d["szDecimals"])
241
+ d["name"]: AssetMeta(asset_id=i, name=d["name"], sz_decimals=d["szDecimals"], tick_size=10**(d["szDecimals"] - 6))
213
242
  for i, d in enumerate(resp.data["universe"])
214
243
  }
215
244
 
@@ -313,7 +342,7 @@ class HyperliquidTrader:
313
342
  # ──────────────────────────────────────────────────────────────────────
314
343
  # Public API – account
315
344
  # ──────────────────────────────────────────────────────────────────────
316
- async def balances(self, user: Optional[str] = None) -> AccountBalance | None: # todo support spot
345
+ async def balances(self, user: Optional[str] = None): # todo support spot
317
346
  try:
318
347
  user = user or self._user
319
348
  if user is None:
@@ -437,6 +466,8 @@ class HyperliquidTrader:
437
466
  price: Optional[float] = None, # required for limit orders
438
467
  use_ws: bool = False,
439
468
  is_spot: bool = False,
469
+ reduce_only: bool = False, # whether to place a reduce‑only order
470
+ cloid: Optional[str] = None,
440
471
  ) -> OrderData:
441
472
  """`place_order` 下订单
442
473
 
@@ -462,6 +493,7 @@ class HyperliquidTrader:
462
493
  tif = "Ioc"
463
494
 
464
495
  size_str = _fmt_size(size, meta.sz_decimals)
496
+ # print(f'下单 @ size_str: {size_str}, price_str: {price_str}, asset: {asset}, is_spot: {is_spot}')
465
497
  order_payload = {
466
498
  "action": {
467
499
  "type": "order",
@@ -471,26 +503,26 @@ class HyperliquidTrader:
471
503
  "b": is_buy,
472
504
  "p": price_str,
473
505
  "s": size_str,
474
- "r": False,
506
+ "r": reduce_only,
475
507
  "t": {"limit": {"tif": tif}},
476
508
  }
477
509
  ],
478
510
  "grouping": "na",
479
511
  }
480
512
  }
481
- # 创建时间戳cid
482
- # Generate a 128-bit client order ID as a hex string (0x-prefixed, 32 hex chars)
483
- cloid = uuid.uuid4().hex
484
- cloid = "0x" + cloid
485
-
486
- order_payload["action"]["orders"][0]["c"] = cloid
513
+ if cloid is not None:
514
+ if not cloid.startswith("0x"):
515
+ cloid = to_cloid(cloid)
516
+
517
+ order_payload["action"]["orders"][0]["c"] = cloid
487
518
 
488
519
  # print(f"Placing order: {order_payload}")
489
- print(order_payload)
520
+ # print(order_payload)
490
521
 
491
522
  if not use_ws:
492
523
  ret = await self._post(_EXCHANGE, order_payload)
493
- if 'error' in str(ret):
524
+ print(ret)
525
+ if 'error' in str(ret) or 'err' in str(ret):
494
526
  raise RuntimeError(f"Failed to place order: {ret}")
495
527
  elif 'filled' in str(ret):
496
528
  return OrderData(
@@ -0,0 +1,284 @@
1
+ from __future__ import annotations
2
+
3
+ import time
4
+ from aiohttp import ClientWebSocketResponse
5
+ import aiohttp
6
+
7
+ def callback(msg, ws: ClientWebSocketResponse = None):
8
+ print("Received message:", msg)
9
+
10
+
11
+ import asyncio
12
+ import pybotters
13
+ from pybotters.store import DataStore, DataStoreCollection
14
+ from pybotters.models.hyperliquid import HyperliquidDataStore
15
+ from typing import TYPE_CHECKING, Awaitable
16
+
17
+ if TYPE_CHECKING:
18
+ from pybotters.typedefs import Item
19
+ from pybotters.ws import ClientWebSocketResponse
20
+
21
+
22
+
23
+ # {'channel': 'orderUpdates', 'data': [{'order': {'coin': 'HYPE', 'side': 'A', 'limitPx': '22.887', 'sz': '1.12', 'oid': 29641480516, 'timestamp': 1746766108031, 'origSz': '1.12', 'reduceOnly': True}, 'status': 'rejected', 'statusTimestamp': 1746766108031}]}
24
+ class OrderStore(DataStore):
25
+ _KEYS = ["oid"]
26
+
27
+ def _onmessage(self, msg: Item) -> None:
28
+
29
+ for rec in msg:
30
+ order = rec["order"]
31
+ item = {
32
+ **order,
33
+ "status": rec.get("status"),
34
+ "px": None,
35
+ 'fee': None,
36
+ "statusTimestamp": rec.get("statusTimestamp"),
37
+ }
38
+
39
+ if item["status"] == "open":
40
+ self._update([item])
41
+ else:
42
+ self._delete([item])
43
+
44
+ class FillStore(DataStore):
45
+ _KEYS = ["oid"]
46
+
47
+ def _onmessage(self, msg: Item) -> None:
48
+ for fill in msg:
49
+ self._update([fill])
50
+
51
+
52
+
53
+ class Account(DataStore):
54
+ _KEYS = ["marginCoin", "value"]
55
+
56
+ def _onmessage(self, data: list[Item]) -> None:
57
+ self._update(
58
+ [
59
+ {
60
+ "marginCoin": 'USDC',
61
+ 'value': float(item['accountValue']),
62
+ 'frozen': float(item['totalMarginUsed']),
63
+ 'available': float(item['accountValue']) - float(item['totalMarginUsed']),
64
+ }
65
+ for item in data
66
+ ]
67
+ )
68
+
69
+ class SpotAccount(DataStore):
70
+
71
+ _KEYS = ["coin"]
72
+
73
+ def _onmessage(self, data: list[Item]) -> None:
74
+ self._update(
75
+ [
76
+ {
77
+ "coin": item['coin'],
78
+ "total": float(item['total']),
79
+ "frozen": float(item['hold']),
80
+ "available": float(item['total']) - float(item['hold']),
81
+ "entryNtl": float(item['entryNtl']),
82
+ }
83
+ for item in data
84
+ ]
85
+ )
86
+
87
+ class PositionStore(DataStore):
88
+ _KEYS = ["coin"]
89
+
90
+ def _onmessage(self, data: list[Item]) -> None:
91
+
92
+ self._clear()
93
+ self._update([
94
+
95
+ {
96
+ "coin": item['position']['coin'],
97
+ "sz": float(item['position']['szi']),
98
+ "px": float(item['position']['entryPx']),
99
+ 'unpnl': float(item['position']['unrealizedPnl']),
100
+ 'rt': float(item['position']['returnOnEquity']),
101
+ 'lv': int(item['position']['leverage']['value']),
102
+ 'update_time': int(time.time() * 1000)
103
+ }
104
+ for item in data
105
+ ])
106
+
107
+
108
+
109
+ class MyHyperStore(HyperliquidDataStore):
110
+ ORDER_TYPE = 'orderUpdates'
111
+ WEBDATA2_TYPE = 'webData2'
112
+ ORDER_FILL_TYPE = 'userFills'
113
+
114
+ def _init(self) -> None:
115
+ self._create("orders", datastore_class=OrderStore)
116
+ self._create("account", datastore_class=Account)
117
+ self._create("positions", datastore_class=PositionStore)
118
+ self._create("spot_account", datastore_class=SpotAccount)
119
+ self._create("fills", datastore_class=FillStore)
120
+ super()._init()
121
+
122
+ def _onmessage(self, msg: Item, ws: ClientWebSocketResponse | None = None) -> None:
123
+
124
+ if msg.get("channel") == self.ORDER_TYPE:
125
+ self.orders._onmessage(msg.get('data', []))
126
+ elif msg.get("channel") == self.WEBDATA2_TYPE:
127
+ # print(msg.get('data', {}).get('clearinghouseState', {}))
128
+ act_data = msg.get('data', {}).get('clearinghouseState', {}).get('crossMarginSummary', [])
129
+ if act_data:
130
+ self.account._onmessage([act_data])
131
+
132
+ pos_data = msg.get('data', {}).get('clearinghouseState', {}).get('assetPositions', [])
133
+ self.positions._onmessage(pos_data)
134
+
135
+ spot_act_data = msg.get('data', {}).get('spotState', {}).get('balances', [])
136
+ self.spot_account._onmessage(spot_act_data)
137
+
138
+ elif msg.get("channel") == self.ORDER_FILL_TYPE:
139
+ fills = msg.get('data', {}).get('fills', [])
140
+ is_snap = msg.get('data', {}).get('isSnapshot', False)
141
+ if not is_snap:
142
+ self.fills._onmessage(fills)
143
+
144
+ super()._onmessage(msg, ws)
145
+
146
+
147
+ async def initialize(self, *aws: tuple[str, Awaitable[aiohttp.ClientResponse]]) -> None:
148
+ for a in aws:
149
+ method, f = a
150
+ resp = await f
151
+ data = await resp.json()
152
+
153
+ if method == "orders":
154
+ self.orders._onmessage(
155
+ [
156
+ {
157
+ 'order': o,
158
+ 'status': "open",
159
+ 'statusTimestamp': int(time.time() * 1000)
160
+ } for o in data
161
+ ]
162
+ )
163
+ elif method == "positions":
164
+ # 处理持仓数据初始化
165
+ pos_data = data.get('assetPositions', [])
166
+ if pos_data:
167
+ self.positions._onmessage(pos_data)
168
+
169
+ # 处理账户数据初始化
170
+ # act_data = data.get('crossMarginSummary', {})
171
+ # if act_data:
172
+ # self.account._onmessage([act_data])
173
+ pass
174
+
175
+ @property
176
+ def orders(self) -> OrderStore:
177
+ """``orders`` data stream.
178
+
179
+ https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/websocket/subscriptions
180
+
181
+ Data structure:
182
+
183
+ .. code:: python
184
+ [
185
+ {
186
+ "coin": "HYPE",
187
+ "side": "A",
188
+ "limitPx": "22.887",
189
+ "sz": "1.12",
190
+ "oid": 29641480516,
191
+ "timestamp": 1746766108031,
192
+ "origSz": "1.12",
193
+ "reduceOnly": True
194
+ "status": "open",
195
+ "statusTimestamp": 1746766108031
196
+ }...
197
+ ]
198
+ """
199
+ return self._get("orders", OrderStore)
200
+ @property
201
+ def account(self) -> Account:
202
+ """``account`` data stream.
203
+ https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/websocket/subscriptions
204
+ Data structure:
205
+ .. code:: python
206
+ [
207
+ {
208
+ "marginCoin": 'USDC',
209
+ 'value': float(item['accountValue']),
210
+ 'frozen': float(item['totalMarginUsed']),
211
+ 'available': float(item['accountValue']) - float(item['totalMarginUsed']),
212
+ }...
213
+ ]
214
+ """
215
+ return self._get("account", Account)
216
+
217
+ @property
218
+ def positions(self) -> PositionStore:
219
+ """
220
+ 获取当前的持仓信息。
221
+ Data structure:
222
+ .. code:: python
223
+ [
224
+ {
225
+ "coin": item['position']['coin'],
226
+ "sz": float(item['position']['szi']),
227
+ "px": float(item['position']['entryPx']),
228
+ 'unpnl': float(item['position']['unrealizedPnl']),
229
+ 'rt': float(item['position']['returnOnEquity']),
230
+ 'lv': int(item['position']['leverage']['value']),
231
+ 'update_time': int(time.time() * 1000)
232
+ }...
233
+ ]
234
+ """
235
+
236
+ return self._get("positions", PositionStore)
237
+
238
+ @property
239
+ def spot_account(self) -> SpotAccount:
240
+ """``spot_account`` data stream.
241
+ https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/websocket/subscriptions
242
+ Data structure:
243
+ .. code:: python
244
+ [
245
+ {
246
+ "coin": 'FEUSD',
247
+ "sz": "21.0",
248
+ "px": "0.9719",
249
+ "unpnl": "0.0",
250
+ "rt": "0.0",
251
+ "lv": 1,
252
+ }...
253
+ ]
254
+ """
255
+ return self._get("spot_account", SpotAccount)
256
+
257
+ @property
258
+ def fills(self) -> FillStore:
259
+ """``fills`` data stream.
260
+ https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/websocket/subscriptions
261
+ Data structure:
262
+ .. code:: python
263
+ [
264
+ {
265
+ "coin": 'FEUSD',
266
+ "px": "0.9719",
267
+ "sz": "21.0",
268
+ "side": 'buy',
269
+ "time": 1679999999999,
270
+ "startPosition": '0.0',
271
+ "dir": 'buy',
272
+ "closedPnl": '0.0',
273
+ "hash": '0x123456789abcdef',
274
+ "oid": 123456789,
275
+ "crossed": True,
276
+ "fee": '-0.0001',
277
+ "tid": 987654321,
278
+ "liquidation": None,
279
+ "feeToken": 'USDC',
280
+ }...
281
+ ]
282
+ """
283
+ return self._get("fills", FillStore)
284
+