hyperquant 1.35__tar.gz → 1.37__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.
- {hyperquant-1.35 → hyperquant-1.37}/PKG-INFO +1 -1
- {hyperquant-1.35 → hyperquant-1.37}/pyproject.toml +1 -1
- {hyperquant-1.35 → hyperquant-1.37}/src/hyperquant/broker/models/polymarket.py +179 -0
- {hyperquant-1.35 → hyperquant-1.37}/src/hyperquant/broker/polymarket.py +53 -16
- {hyperquant-1.35 → hyperquant-1.37}/uv.lock +1 -1
- {hyperquant-1.35 → hyperquant-1.37}/.gitignore +0 -0
- {hyperquant-1.35 → hyperquant-1.37}/README.md +0 -0
- {hyperquant-1.35 → hyperquant-1.37}/requirements-dev.lock +0 -0
- {hyperquant-1.35 → hyperquant-1.37}/requirements.lock +0 -0
- {hyperquant-1.35 → hyperquant-1.37}/src/hyperquant/__init__.py +0 -0
- {hyperquant-1.35 → hyperquant-1.37}/src/hyperquant/broker/auth.py +0 -0
- {hyperquant-1.35 → hyperquant-1.37}/src/hyperquant/broker/bitget.py +0 -0
- {hyperquant-1.35 → hyperquant-1.37}/src/hyperquant/broker/bitmart.py +0 -0
- {hyperquant-1.35 → hyperquant-1.37}/src/hyperquant/broker/coinw.py +0 -0
- {hyperquant-1.35 → hyperquant-1.37}/src/hyperquant/broker/deepcoin.py +0 -0
- {hyperquant-1.35 → hyperquant-1.37}/src/hyperquant/broker/edgex.py +0 -0
- {hyperquant-1.35 → hyperquant-1.37}/src/hyperquant/broker/hyperliquid.py +0 -0
- {hyperquant-1.35 → hyperquant-1.37}/src/hyperquant/broker/lbank.py +0 -0
- {hyperquant-1.35 → hyperquant-1.37}/src/hyperquant/broker/lib/edgex_sign.py +0 -0
- {hyperquant-1.35 → hyperquant-1.37}/src/hyperquant/broker/lib/hpstore.py +0 -0
- {hyperquant-1.35 → hyperquant-1.37}/src/hyperquant/broker/lib/hyper_types.py +0 -0
- {hyperquant-1.35 → hyperquant-1.37}/src/hyperquant/broker/lib/util.py +0 -0
- {hyperquant-1.35 → hyperquant-1.37}/src/hyperquant/broker/lighter.py +0 -0
- {hyperquant-1.35 → hyperquant-1.37}/src/hyperquant/broker/models/apexpro.py +0 -0
- {hyperquant-1.35 → hyperquant-1.37}/src/hyperquant/broker/models/bitget.py +0 -0
- {hyperquant-1.35 → hyperquant-1.37}/src/hyperquant/broker/models/bitmart.py +0 -0
- {hyperquant-1.35 → hyperquant-1.37}/src/hyperquant/broker/models/coinw.py +0 -0
- {hyperquant-1.35 → hyperquant-1.37}/src/hyperquant/broker/models/deepcoin.py +0 -0
- {hyperquant-1.35 → hyperquant-1.37}/src/hyperquant/broker/models/edgex.py +0 -0
- {hyperquant-1.35 → hyperquant-1.37}/src/hyperquant/broker/models/hyperliquid.py +0 -0
- {hyperquant-1.35 → hyperquant-1.37}/src/hyperquant/broker/models/lbank.py +0 -0
- {hyperquant-1.35 → hyperquant-1.37}/src/hyperquant/broker/models/lighter.py +0 -0
- {hyperquant-1.35 → hyperquant-1.37}/src/hyperquant/broker/models/ourbit.py +0 -0
- {hyperquant-1.35 → hyperquant-1.37}/src/hyperquant/broker/ourbit.py +0 -0
- {hyperquant-1.35 → hyperquant-1.37}/src/hyperquant/broker/ws.py +0 -0
- {hyperquant-1.35 → hyperquant-1.37}/src/hyperquant/core.py +0 -0
- {hyperquant-1.35 → hyperquant-1.37}/src/hyperquant/datavison/_util.py +0 -0
- {hyperquant-1.35 → hyperquant-1.37}/src/hyperquant/datavison/binance.py +0 -0
- {hyperquant-1.35 → hyperquant-1.37}/src/hyperquant/datavison/coinglass.py +0 -0
- {hyperquant-1.35 → hyperquant-1.37}/src/hyperquant/datavison/okx.py +0 -0
- {hyperquant-1.35 → hyperquant-1.37}/src/hyperquant/db.py +0 -0
- {hyperquant-1.35 → hyperquant-1.37}/src/hyperquant/draw.py +0 -0
- {hyperquant-1.35 → hyperquant-1.37}/src/hyperquant/logkit.py +0 -0
- {hyperquant-1.35 → hyperquant-1.37}/src/hyperquant/notikit.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: hyperquant
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.37
|
|
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,8 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
|
+
from dataclasses import dataclass, field
|
|
5
|
+
from heapq import heappop, heappush
|
|
4
6
|
from typing import TYPE_CHECKING, Any, Iterable
|
|
5
7
|
|
|
6
8
|
from pybotters.store import DataStore, DataStoreCollection
|
|
@@ -332,6 +334,163 @@ class Book(DataStore):
|
|
|
332
334
|
limit=limit,
|
|
333
335
|
)
|
|
334
336
|
|
|
337
|
+
@dataclass
|
|
338
|
+
class _SideBook:
|
|
339
|
+
is_ask: bool
|
|
340
|
+
levels: dict[float, tuple[str, str]] = field(default_factory=dict)
|
|
341
|
+
heap: list[tuple[float, float]] = field(default_factory=list)
|
|
342
|
+
|
|
343
|
+
def clear(self) -> None:
|
|
344
|
+
self.levels.clear()
|
|
345
|
+
self.heap.clear()
|
|
346
|
+
|
|
347
|
+
def update_levels(
|
|
348
|
+
self, updates: Iterable[dict[str, Any]] | None, *, snapshot: bool
|
|
349
|
+
) -> None:
|
|
350
|
+
if updates is None:
|
|
351
|
+
return
|
|
352
|
+
|
|
353
|
+
if snapshot:
|
|
354
|
+
self.clear()
|
|
355
|
+
|
|
356
|
+
for entry in updates:
|
|
357
|
+
price, size = self._extract(entry)
|
|
358
|
+
price_val = self._to_float(price)
|
|
359
|
+
size_val = self._to_float(size)
|
|
360
|
+
if price_val is None or size_val is None:
|
|
361
|
+
continue
|
|
362
|
+
|
|
363
|
+
if size_val <= 0:
|
|
364
|
+
self.levels.pop(price_val, None)
|
|
365
|
+
continue
|
|
366
|
+
|
|
367
|
+
self.levels[price_val] = (str(price), str(size))
|
|
368
|
+
priority = price_val if self.is_ask else -price_val
|
|
369
|
+
heappush(self.heap, (priority, price_val))
|
|
370
|
+
|
|
371
|
+
def best(self) -> tuple[str, str] | None:
|
|
372
|
+
while self.heap:
|
|
373
|
+
_, price = self.heap[0]
|
|
374
|
+
level = self.levels.get(price)
|
|
375
|
+
if level is not None:
|
|
376
|
+
return level
|
|
377
|
+
heappop(self.heap)
|
|
378
|
+
return None
|
|
379
|
+
|
|
380
|
+
@staticmethod
|
|
381
|
+
def _extract(entry: Any) -> tuple[Any, Any]:
|
|
382
|
+
if isinstance(entry, dict):
|
|
383
|
+
price = entry.get("price", entry.get("p"))
|
|
384
|
+
size = entry.get("size", entry.get("q"))
|
|
385
|
+
return price, size
|
|
386
|
+
if isinstance(entry, (list, tuple)) and len(entry) >= 2:
|
|
387
|
+
return entry[0], entry[1]
|
|
388
|
+
return None, None
|
|
389
|
+
|
|
390
|
+
@staticmethod
|
|
391
|
+
def _to_float(value: Any) -> float | None:
|
|
392
|
+
try:
|
|
393
|
+
return float(value)
|
|
394
|
+
except (TypeError, ValueError):
|
|
395
|
+
return None
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
class BBO(DataStore):
|
|
399
|
+
_KEYS = ["s", "S"]
|
|
400
|
+
|
|
401
|
+
def _init(self) -> None:
|
|
402
|
+
self._book: dict[str, dict[str, _SideBook]] = {}
|
|
403
|
+
self.id_to_alias: dict[str, str] = {}
|
|
404
|
+
|
|
405
|
+
def update_aliases(self, mapping: dict[str, str]) -> None:
|
|
406
|
+
if not mapping:
|
|
407
|
+
return
|
|
408
|
+
self.id_to_alias.update(mapping)
|
|
409
|
+
|
|
410
|
+
def _alias(self, asset_id: str | None) -> tuple[str, str | None] | tuple[None, None]:
|
|
411
|
+
if asset_id is None:
|
|
412
|
+
return None, None
|
|
413
|
+
alias = self.id_to_alias.get(asset_id)
|
|
414
|
+
return asset_id, alias
|
|
415
|
+
|
|
416
|
+
def _side(self, symbol: str, side: str) -> _SideBook:
|
|
417
|
+
symbol_book = self._book.setdefault(symbol, {})
|
|
418
|
+
side_book = symbol_book.get(side)
|
|
419
|
+
if side_book is None:
|
|
420
|
+
side_book = _SideBook(is_ask=(side == "a"))
|
|
421
|
+
symbol_book[side] = side_book
|
|
422
|
+
return side_book
|
|
423
|
+
|
|
424
|
+
def _sync_side(
|
|
425
|
+
self, symbol: str, side: str, best: tuple[str, str] | None, alias: str | None
|
|
426
|
+
) -> None:
|
|
427
|
+
key = {"s": symbol, "S": side}
|
|
428
|
+
current = self.get(key)
|
|
429
|
+
|
|
430
|
+
if best is None:
|
|
431
|
+
if current:
|
|
432
|
+
self._delete([key])
|
|
433
|
+
return
|
|
434
|
+
|
|
435
|
+
price, size = best
|
|
436
|
+
payload = {"s": symbol, "S": side, "p": price, "q": size}
|
|
437
|
+
if alias is not None:
|
|
438
|
+
payload["alias"] = alias
|
|
439
|
+
|
|
440
|
+
if current:
|
|
441
|
+
cur_price = current.get("p")
|
|
442
|
+
cur_size = current.get("q")
|
|
443
|
+
cur_alias = current.get("alias")
|
|
444
|
+
|
|
445
|
+
if cur_price == price:
|
|
446
|
+
# price unchanged -> only update quantities / alias changes
|
|
447
|
+
if cur_size != size or (alias is not None and cur_alias != alias):
|
|
448
|
+
self._update([payload])
|
|
449
|
+
return
|
|
450
|
+
|
|
451
|
+
# price changed -> delete old then insert new level to trigger change watchers
|
|
452
|
+
self._delete([key])
|
|
453
|
+
|
|
454
|
+
self._insert([payload])
|
|
455
|
+
|
|
456
|
+
def _from_price_changes(self, msg: dict[str, Any]) -> None:
|
|
457
|
+
price_changes = msg.get("price_changes") or []
|
|
458
|
+
touched: dict[str, str | None] = {}
|
|
459
|
+
for change in price_changes:
|
|
460
|
+
asset_id = change.get("asset_id") or change.get("token_id")
|
|
461
|
+
symbol, alias = self._alias(asset_id)
|
|
462
|
+
if symbol is None:
|
|
463
|
+
continue
|
|
464
|
+
side = "b" if str(change.get("side") or "").upper() == "BUY" else "a"
|
|
465
|
+
side_book = self._side(symbol, side)
|
|
466
|
+
side_book.update_levels([change], snapshot=False)
|
|
467
|
+
touched[symbol] = alias
|
|
468
|
+
|
|
469
|
+
for symbol, alias in touched.items():
|
|
470
|
+
asks = self._side(symbol, "a")
|
|
471
|
+
bids = self._side(symbol, "b")
|
|
472
|
+
self._sync_side(symbol, "a", asks.best(), alias)
|
|
473
|
+
self._sync_side(symbol, "b", bids.best(), alias)
|
|
474
|
+
|
|
475
|
+
def _from_snapshot(self, msg: dict[str, Any]) -> None:
|
|
476
|
+
asset_id = msg.get("asset_id") or msg.get("token_id")
|
|
477
|
+
symbol, alias = self._alias(asset_id)
|
|
478
|
+
if symbol is None:
|
|
479
|
+
return
|
|
480
|
+
asks = self._side(symbol, "a")
|
|
481
|
+
bids = self._side(symbol, "b")
|
|
482
|
+
asks.update_levels(msg.get("asks"), snapshot=True)
|
|
483
|
+
bids.update_levels(msg.get("bids"), snapshot=True)
|
|
484
|
+
self._sync_side(symbol, "a", asks.best(), alias)
|
|
485
|
+
self._sync_side(symbol, "b", bids.best(), alias)
|
|
486
|
+
|
|
487
|
+
def _on_message(self, msg: dict[str, Any]) -> None:
|
|
488
|
+
msg_type = (msg.get("event_type") or msg.get("type") or "").lower()
|
|
489
|
+
if msg_type == "book":
|
|
490
|
+
self._from_snapshot(msg)
|
|
491
|
+
elif msg_type == "price_change":
|
|
492
|
+
self._from_price_changes(msg)
|
|
493
|
+
|
|
335
494
|
|
|
336
495
|
class Detail(DataStore):
|
|
337
496
|
"""Market metadata keyed by Polymarket token id."""
|
|
@@ -446,6 +605,7 @@ class PolymarketDataStore(DataStoreCollection):
|
|
|
446
605
|
|
|
447
606
|
def _init(self) -> None:
|
|
448
607
|
self._create("book", datastore_class=Book)
|
|
608
|
+
self._create("bbo", datastore_class=BBO)
|
|
449
609
|
self._create("detail", datastore_class=Detail)
|
|
450
610
|
self._create("position", datastore_class=Position)
|
|
451
611
|
self._create("order", datastore_class=Order)
|
|
@@ -627,6 +787,14 @@ class PolymarketDataStore(DataStoreCollection):
|
|
|
627
787
|
|
|
628
788
|
return self._get("fill")
|
|
629
789
|
|
|
790
|
+
@property
|
|
791
|
+
def bbo(self) -> BBO:
|
|
792
|
+
"""Best Bid and Offer DataStore
|
|
793
|
+
_key: s (asset_id), S (side)
|
|
794
|
+
|
|
795
|
+
"""
|
|
796
|
+
return self._get("bbo")
|
|
797
|
+
|
|
630
798
|
@property
|
|
631
799
|
def trade(self) -> Trade:
|
|
632
800
|
"""
|
|
@@ -683,3 +851,14 @@ class PolymarketDataStore(DataStoreCollection):
|
|
|
683
851
|
self.position.on_trade(m)
|
|
684
852
|
elif msg_type == 'orders_matched':
|
|
685
853
|
self.trade._on_message(m)
|
|
854
|
+
|
|
855
|
+
def onmessage_for_bbo(self, msg: Any, ws: ClientWebSocketResponse | None = None) -> None:
|
|
856
|
+
# 判定msg是否为list
|
|
857
|
+
lst_msg = msg if isinstance(msg, list) else [msg]
|
|
858
|
+
for m in lst_msg:
|
|
859
|
+
raw_type = m.get("event_type") or m.get("type")
|
|
860
|
+
if not raw_type:
|
|
861
|
+
continue
|
|
862
|
+
msg_type = str(raw_type).lower()
|
|
863
|
+
if msg_type in {"book", "price_change"}:
|
|
864
|
+
self.bbo._on_message(m)
|
|
@@ -2,7 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
4
|
from contextlib import suppress
|
|
5
|
-
from datetime import UTC, datetime
|
|
5
|
+
from datetime import UTC, datetime, timedelta
|
|
6
6
|
from functools import lru_cache
|
|
7
7
|
import os
|
|
8
8
|
from typing import Any, Iterable, Iterator, Literal, Mapping, Sequence
|
|
@@ -12,6 +12,7 @@ import json
|
|
|
12
12
|
import aiohttp
|
|
13
13
|
import pybotters
|
|
14
14
|
import pybotters.ws
|
|
15
|
+
import pytz
|
|
15
16
|
|
|
16
17
|
from .models.polymarket import PolymarketDataStore
|
|
17
18
|
from .auth import Auth
|
|
@@ -22,6 +23,7 @@ GAMMA_EVENTS_API = "https://gamma-api.polymarket.com/events"
|
|
|
22
23
|
DEFAULT_DATA_ENDPOINT = "https://data-api.polymarket.com"
|
|
23
24
|
RTS_DATA_ENDPOINT = "wss://ws-live-data.polymarket.com/"
|
|
24
25
|
DEFAULT_BASE_SLUG = "btc-updown-15m"
|
|
26
|
+
HOURLY_BITCOIN_BASE_SLUG = "bitcoin-up-or-down"
|
|
25
27
|
DEFAULT_INTERVAL = 15 * 60
|
|
26
28
|
DEFAULT_WINDOW = 8
|
|
27
29
|
API_NAME = "polymarket"
|
|
@@ -38,6 +40,7 @@ DEFAULT_POLYGON_RPCS = (
|
|
|
38
40
|
"https://polygon-rpc.com",
|
|
39
41
|
"https://rpc.ankr.com/polygon",
|
|
40
42
|
)
|
|
43
|
+
_EASTERN_TZ = pytz.timezone("US/Eastern")
|
|
41
44
|
|
|
42
45
|
def parse_field(value):
|
|
43
46
|
"""尝试将字符串 JSON 转为对象,否则原样返回"""
|
|
@@ -73,6 +76,21 @@ def _accepting_orders(market: Mapping[str, Any]) -> bool:
|
|
|
73
76
|
return bool(accepting)
|
|
74
77
|
|
|
75
78
|
|
|
79
|
+
def _compose_hourly_slug(base_slug: str, *, now: datetime | None = None) -> str:
|
|
80
|
+
tz_now = now or datetime.now(_EASTERN_TZ)
|
|
81
|
+
if tz_now.tzinfo is None:
|
|
82
|
+
tz_now = _EASTERN_TZ.localize(tz_now)
|
|
83
|
+
else:
|
|
84
|
+
tz_now = tz_now.astimezone(_EASTERN_TZ)
|
|
85
|
+
|
|
86
|
+
tz_now = (tz_now + timedelta(seconds=5)).replace(minute=0, second=0, microsecond=0)
|
|
87
|
+
month_str = tz_now.strftime("%B").lower()
|
|
88
|
+
day = tz_now.day
|
|
89
|
+
hour_12 = tz_now.strftime("%I").lstrip("0") or "0"
|
|
90
|
+
am_pm = tz_now.strftime("%p").lower()
|
|
91
|
+
return f"{base_slug}-{month_str}-{day}-{hour_12}{am_pm}-et"
|
|
92
|
+
|
|
93
|
+
|
|
76
94
|
class Polymarket:
|
|
77
95
|
"""Polymarket CLOB client with REST helpers, stores and WS subscriptions."""
|
|
78
96
|
|
|
@@ -223,12 +241,12 @@ class Polymarket:
|
|
|
223
241
|
self.store.orders._on_response(orders)
|
|
224
242
|
|
|
225
243
|
|
|
226
|
-
|
|
227
244
|
|
|
228
245
|
async def sub_books(
|
|
229
246
|
self,
|
|
230
247
|
token_ids: Sequence[str] | str,
|
|
231
248
|
wsapp: pybotters.ws.WebSocketApp | None = None,
|
|
249
|
+
only_bbo: bool = False,
|
|
232
250
|
) -> pybotters.ws.WebSocketApp:
|
|
233
251
|
"""Subscribe to public order-book updates for the provided token ids."""
|
|
234
252
|
|
|
@@ -236,11 +254,12 @@ class Polymarket:
|
|
|
236
254
|
payload = {"type": "market", "assets_ids": tokens}
|
|
237
255
|
if wsapp:
|
|
238
256
|
await wsapp.current_ws.send_json(payload)
|
|
239
|
-
|
|
257
|
+
hdrl_json = self.store.onmessage_for_bbo if only_bbo else self.store.onmessage
|
|
258
|
+
|
|
240
259
|
self._ws_public = self.client.ws_connect(
|
|
241
260
|
self.ws_public,
|
|
242
261
|
send_json=payload,
|
|
243
|
-
hdlr_json=
|
|
262
|
+
hdlr_json=hdrl_json,
|
|
244
263
|
)
|
|
245
264
|
await self._ws_public._event.wait()
|
|
246
265
|
return self._ws_public
|
|
@@ -1170,6 +1189,33 @@ class Polymarket:
|
|
|
1170
1189
|
https://docs.polymarket.com/api-reference/markets/get-market-by-id
|
|
1171
1190
|
"""
|
|
1172
1191
|
|
|
1192
|
+
async def _try_slug(slug: str | None) -> tuple[str, dict, dict] | None:
|
|
1193
|
+
if not slug:
|
|
1194
|
+
return None
|
|
1195
|
+
event = await self._fetch_event(slug)
|
|
1196
|
+
if not event:
|
|
1197
|
+
return None
|
|
1198
|
+
|
|
1199
|
+
event = {k: parse_field(v) for k, v in event.items()}
|
|
1200
|
+
for market in event.get("markets", []):
|
|
1201
|
+
if not _accepting_orders(market):
|
|
1202
|
+
continue
|
|
1203
|
+
market = {k: parse_field(v) for k, v in market.items()}
|
|
1204
|
+
return slug, event, market
|
|
1205
|
+
return None
|
|
1206
|
+
|
|
1207
|
+
if base_slug == HOURLY_BITCOIN_BASE_SLUG:
|
|
1208
|
+
hourly_slug = _compose_hourly_slug(base_slug)
|
|
1209
|
+
hourly_match = await _try_slug(hourly_slug)
|
|
1210
|
+
if hourly_match:
|
|
1211
|
+
return hourly_match
|
|
1212
|
+
|
|
1213
|
+
# 1小时市场等特殊 slug(比如 bitcoin-up-or-down-november-18-10am-et)
|
|
1214
|
+
# 直接传入完整 slug 即可,不再拼接时间戳
|
|
1215
|
+
direct_match = await _try_slug(base_slug)
|
|
1216
|
+
if direct_match:
|
|
1217
|
+
return direct_match
|
|
1218
|
+
|
|
1173
1219
|
now_ts = int(datetime.now(UTC).timestamp())
|
|
1174
1220
|
base_ts = (now_ts // interval) * interval
|
|
1175
1221
|
|
|
@@ -1178,18 +1224,9 @@ class Polymarket:
|
|
|
1178
1224
|
if ts < 0:
|
|
1179
1225
|
continue
|
|
1180
1226
|
slug = f"{base_slug}-{ts}"
|
|
1181
|
-
|
|
1182
|
-
if
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
event = {k: parse_field(v) for k, v in event.items()}
|
|
1188
|
-
|
|
1189
|
-
for market in event.get("markets", []):
|
|
1190
|
-
if _accepting_orders(market):
|
|
1191
|
-
market = {k: parse_field(v) for k, v in market.items()}
|
|
1192
|
-
return slug, event, market
|
|
1227
|
+
result = await _try_slug(slug)
|
|
1228
|
+
if result:
|
|
1229
|
+
return result
|
|
1193
1230
|
|
|
1194
1231
|
raise RuntimeError(
|
|
1195
1232
|
f"未在 {base_slug} 的 +/-{window} 个区间内找到可交易的市场"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|