xync-db 0.5.2__tar.gz → 0.5.3__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 (44) hide show
  1. {xync_db-0.5.2 → xync_db-0.5.3}/PKG-INFO +1 -1
  2. xync_db-0.5.3/src/xync_db/migrations/models/0011_pair_scale.py +45 -0
  3. {xync_db-0.5.2 → xync_db-0.5.3}/src/xync_db/models/__init__.py +24 -15
  4. {xync_db-0.5.2 → xync_db-0.5.3}/src/xync_db/shared.py +18 -0
  5. {xync_db-0.5.2 → xync_db-0.5.3}/.gitignore +0 -0
  6. {xync_db-0.5.2 → xync_db-0.5.3}/README.md +0 -0
  7. {xync_db-0.5.2 → xync_db-0.5.3}/pyproject.toml +0 -0
  8. {xync_db-0.5.2 → xync_db-0.5.3}/src/xync_db/__init__.py +0 -0
  9. {xync_db-0.5.2 → xync_db-0.5.3}/src/xync_db/config.py +0 -0
  10. {xync_db-0.5.2 → xync_db-0.5.3}/src/xync_db/enums.py +0 -0
  11. {xync_db-0.5.2 → xync_db-0.5.3}/src/xync_db/exceptions.py +0 -0
  12. {xync_db-0.5.2 → xync_db-0.5.3}/src/xync_db/graph.py +0 -0
  13. {xync_db-0.5.2 → xync_db-0.5.3}/src/xync_db/i18n.py +0 -0
  14. {xync_db-0.5.2 → xync_db-0.5.3}/src/xync_db/login_store.py +0 -0
  15. {xync_db-0.5.2 → xync_db-0.5.3}/src/xync_db/logo.png +0 -0
  16. {xync_db-0.5.2 → xync_db-0.5.3}/src/xync_db/migrations/CLAUDE.md +0 -0
  17. {xync_db-0.5.2 → xync_db-0.5.3}/src/xync_db/migrations/__init__.py +0 -0
  18. {xync_db-0.5.2 → xync_db-0.5.3}/src/xync_db/migrations/models/0001_initial.py +0 -0
  19. {xync_db-0.5.2 → xync_db-0.5.3}/src/xync_db/migrations/models/0002_triggers.py +0 -0
  20. {xync_db-0.5.2 → xync_db-0.5.3}/src/xync_db/migrations/models/0003_files_seed.py +0 -0
  21. {xync_db-0.5.2 → xync_db-0.5.3}/src/xync_db/migrations/models/0004_all_seed.py +0 -0
  22. {xync_db-0.5.2 → xync_db-0.5.3}/src/xync_db/migrations/models/0005_credex_del.py +0 -0
  23. {xync_db-0.5.2 → xync_db-0.5.3}/src/xync_db/migrations/models/0006_credex_orphan.py +0 -0
  24. {xync_db-0.5.2 → xync_db-0.5.3}/src/xync_db/migrations/models/0007_agent_auth_notify.py +0 -0
  25. {xync_db-0.5.2 → xync_db-0.5.3}/src/xync_db/migrations/models/0008_ad_exid_ex_uniq.py +0 -0
  26. {xync_db-0.5.2 → xync_db-0.5.3}/src/xync_db/migrations/models/0009_web3_tables.py +0 -0
  27. {xync_db-0.5.2 → xync_db-0.5.3}/src/xync_db/migrations/models/0010_updatestate_fix.py +0 -0
  28. {xync_db-0.5.2 → xync_db-0.5.3}/src/xync_db/migrations/models/__init__.py +0 -0
  29. {xync_db-0.5.2 → xync_db-0.5.3}/src/xync_db/migrations/models/seed/__init__.py +0 -0
  30. {xync_db-0.5.2 → xync_db-0.5.3}/src/xync_db/migrations/models/seed/a_actor_seed.py +0 -0
  31. {xync_db-0.5.2 → xync_db-0.5.3}/src/xync_db/migrations/models/seed/a_tg_seed.py +0 -0
  32. {xync_db-0.5.2 → xync_db-0.5.3}/src/xync_db/migrations/models/seed/b_htx_seed.py +0 -0
  33. {xync_db-0.5.2 → xync_db-0.5.3}/src/xync_db/migrations/models/seed/c_net_seed.py +0 -0
  34. {xync_db-0.5.2 → xync_db-0.5.3}/src/xync_db/migrations/models/seed/xync_files.sql +0 -0
  35. {xync_db-0.5.2 → xync_db-0.5.3}/src/xync_db/models/CLAUDE.md +0 -0
  36. {xync_db-0.5.2 → xync_db-0.5.3}/src/xync_db/py.typed +0 -0
  37. {xync_db-0.5.2 → xync_db-0.5.3}/src/xync_db/typs/__init__.py +0 -0
  38. {xync_db-0.5.2 → xync_db-0.5.3}/src/xync_db/typs/db/__init__.py +0 -0
  39. {xync_db-0.5.2 → xync_db-0.5.3}/src/xync_db/typs/db/actor.py +0 -0
  40. {xync_db-0.5.2 → xync_db-0.5.3}/src/xync_db/typs/db/ad.py +0 -0
  41. {xync_db-0.5.2 → xync_db-0.5.3}/src/xync_db/typs/db/common.py +0 -0
  42. {xync_db-0.5.2 → xync_db-0.5.3}/src/xync_db/typs/db/cred.py +0 -0
  43. {xync_db-0.5.2 → xync_db-0.5.3}/src/xync_db/typs/db/order.py +0 -0
  44. {xync_db-0.5.2 → xync_db-0.5.3}/src/xync_db/typs/db/pm.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: xync-db
3
- Version: 0.5.2
3
+ Version: 0.5.3
4
4
  Summary: Xync DB schema: Tortoise ORM models, enums, pydantic DTOs.
5
5
  Author-email: Mike Artemiev <mixartemev@gmail.com>
6
6
  License: LicenseRef-EULA
@@ -0,0 +1,45 @@
1
+ """`Pair.rate` переезжает с фиксированного cur.scale на индивидуальный масштаб пары.
2
+
3
+ Было: rate = real * 10^cur.scale — точность курса зашита в масштаб валюты (у BTC/RUB хвост
4
+ из незначащих знаков, у дешёвых монет точности не хватает). Стало: rate — 20-битная мантисса,
5
+ новая колонка `scale` — 4-битная (−8..7) степень десятки: real = rate / 10^scale. Пишется
6
+ единственной точкой квантования `xync_db.shared.best_scale`; читается `Pair.real()`.
7
+
8
+ Бэкфилл переводит существующие строки в новое представление без изменения реального значения
9
+ (с точностью до 20-битной мантиссы ≈ 6 значащих цифр) — та же математика, что в best_scale.
10
+ """
11
+
12
+ from typing import ClassVar
13
+
14
+ from tortoise.migrations.migration import Migration as BaseMigration
15
+ from tortoise.migrations.operations import RunSQL
16
+
17
+ PAIR_SCALE_SQL = """
18
+ ALTER TABLE "pair" ADD COLUMN "scale" SMALLINT NOT NULL DEFAULT 0;
19
+ COMMENT ON COLUMN "pair"."rate" IS '20-bit мантисса курса: real = rate / 10^scale (пишется через best_scale)';
20
+ COMMENT ON COLUMN "pair"."scale" IS '4-bit (-8..7) степень десятки мантиссы rate';
21
+ UPDATE "pair" p
22
+ SET "scale" = q."s", "rate" = round(q."rr" * power(10, q."s"))::bigint
23
+ FROM (
24
+ SELECT p2."id",
25
+ p2."rate" / power(10, c."scale") AS "rr",
26
+ GREATEST(-8, LEAST(7, floor(log((1048575 / (p2."rate" / power(10, c."scale")))::numeric))))::smallint AS "s"
27
+ FROM "pair" p2 JOIN "cur" c ON c."id" = p2."cur_id"
28
+ WHERE p2."rate" IS NOT NULL AND p2."rate" > 0
29
+ ) q
30
+ WHERE p."id" = q."id";
31
+ ALTER TABLE "pair" ALTER COLUMN "rate" TYPE INT USING "rate"::int;
32
+ """
33
+
34
+ PAIR_SCALE_SQL_DOWN = """
35
+ ALTER TABLE "pair" ALTER COLUMN "rate" TYPE BIGINT USING "rate"::bigint;
36
+ UPDATE "pair" p SET "rate" = round(p."rate" * power(10, c."scale" - p."scale"))::bigint
37
+ FROM "cur" c WHERE c."id" = p."cur_id" AND p."rate" IS NOT NULL;
38
+ ALTER TABLE "pair" DROP COLUMN "scale";
39
+ COMMENT ON COLUMN "pair"."rate" IS NULL;
40
+ """
41
+
42
+
43
+ class Migration(BaseMigration):
44
+ dependencies: ClassVar = [("models", "0010_updatestate_fix")]
45
+ operations: ClassVar = [RunSQL(PAIR_SCALE_SQL, PAIR_SCALE_SQL_DOWN)]
@@ -275,11 +275,18 @@ class CoinEx(BaseModel):
275
275
  class Pair(Model):
276
276
  id = SmallIntField(True)
277
277
  coin: ForeignKeyRelation[Coin] = ForeignKeyField("models.Coin", "pairs", on_update=CASCADE)
278
+ coin_id: int
278
279
  cur: ForeignKeyRelation[Cur] = ForeignKeyField("models.Cur", "pairs", on_update=CASCADE)
279
- rate: int | None = BigIntField(null=True)
280
+ cur_id: int
281
+ rate: int | None = IntField(null=True) # 20-битная мантисса курса (пишется ТОЛЬКО через best_scale)
282
+ scale: int = SmallIntField(default=0) # 4-битная (−8..7) степень десятки мантиссы — своя у каждой пары
280
283
 
281
284
  _name: ClassVar = {"coin__ticker", "cur__ticker"}
282
285
 
286
+ def real(self) -> float:
287
+ """Реальный курс (цена 1 coin в cur): rate/10^scale — единственный способ читать rate."""
288
+ return (self.rate or 0) * 10**-self.scale
289
+
283
290
  class Meta:
284
291
  table_description = "Coin/Currency pairs"
285
292
  unique_together = (("coin", "cur"),)
@@ -563,15 +570,15 @@ class User(TgUser, TsTrait):
563
570
  def _assemble_rich(
564
571
  ss: dict[int, dict[int, int]],
565
572
  cur_meta: dict[int, tuple[str, int]],
566
- usdt_rate: dict[int, int | None],
573
+ usdt_rate: dict[int, float | None],
567
574
  coins_by_user: dict[int, dict[int, dict]],
568
575
  names: dict[int, tuple[str, str | None]],
569
576
  ) -> list[dict]:
570
577
  # Pure assembly for `rich_list` (no DB) — kept separate so the ranking logic is unit-testable.
571
578
  # ss: {uid: {cur_id: raw}} raw = amount * 10^cur.scale (XyncPay fiat balances)
572
579
  # cur_meta: {cur_id: (ticker, scale)}
573
- # usdt_rate: {cur_id: Pair(USDT, cur).rate} rate = price_of_1_USDT_in_cur * 10^cur.scale
574
- # ⇒ usdt_value = raw / rate (the cur.scale factors cancel)
580
+ # usdt_rate: {cur_id: Pair(USDT, cur).real()} real price of 1 USDT in cur
581
+ # ⇒ usdt_value = (raw / 10^cur.scale) / rate
575
582
  # coins_by_user: {uid: {coin_id: {"ticker", "amount", "rate"}}} amount already in whole coins
576
583
  # names: {uid: (display_name, "@handle" | None)}
577
584
  out: list[dict] = []
@@ -583,13 +590,13 @@ class User(TgUser, TsTrait):
583
590
  if cid not in cur_meta:
584
591
  continue
585
592
  ticker, scale = cur_meta[cid]
586
- rate = usdt_rate.get(cid) or 0
593
+ rate = usdt_rate.get(cid) or 0.0
587
594
  curs.append(
588
595
  {
589
596
  "id": cid,
590
597
  "ticker": ticker,
591
598
  "amount": round(raw / 10**scale, scale),
592
- "usdt": raw / rate if rate else 0.0,
599
+ "usdt": raw / 10**scale / rate if rate else 0.0,
593
600
  }
594
601
  )
595
602
  curs.sort(key=lambda b: b["usdt"], reverse=True)
@@ -628,16 +635,17 @@ class User(TgUser, TsTrait):
628
635
  ss = await Transaction.snapshot() # {uid: {cur_id: raw}}
629
636
  cur_meta = {c.id: (c.ticker, c.scale) for c in await Cur.all()}
630
637
  # All rates live in Pair (Coin.rate is unpopulated). USDT = coin 1 is the base unit.
631
- # usdt_rate[cur] = Pair(USDT, cur).rate → fiat value: raw / usdt_rate[cur]
632
- # coin_price[coin] = price of 1 coin in USDT = Pair(coin, X).rate / Pair(USDT, X).rate
633
- # for any cur X priced in both (the cur.scale cancels); first such X wins.
634
- pairs = await Pair.all().values("coin_id", "cur_id", "rate")
635
- usdt_rate = {p["cur_id"]: p["rate"] for p in pairs if p["coin_id"] == 1 and p["rate"]}
638
+ # Pair.real() реальный курс (мантисса rate / 10^индивидуальный pair.scale):
639
+ # usdt_rate[cur] = Pair(USDT, cur).real() → fiat value: real_amount / usdt_rate[cur]
640
+ # coin_price[coin] = price of 1 coin in USDT = Pair(coin, X).real() / Pair(USDT, X).real()
641
+ # for any cur X priced in both; first such X wins.
642
+ pairs = await Pair.all()
643
+ usdt_rate = {p.cur_id: p.real() for p in pairs if p.coin_id == 1 and p.rate}
636
644
  coin_price: dict[int, float] = {1: 1.0}
637
645
  for p in pairs:
638
- if p["coin_id"] == 1 or not p["rate"] or p["cur_id"] not in usdt_rate:
646
+ if p.coin_id == 1 or not p.rate or p.cur_id not in usdt_rate:
639
647
  continue
640
- coin_price.setdefault(p["coin_id"], p["rate"] / usdt_rate[p["cur_id"]])
648
+ coin_price.setdefault(p.coin_id, p.real() / usdt_rate[p.cur_id])
641
649
  # Asset.free is scaled per-exchange (/10^coinex.scale), so normalise each row before summing.
642
650
  cx_scale = {
643
651
  (r["ex_id"], r["coin_id"]): r["scale"] for r in await CoinEx.all().values("ex_id", "coin_id", "scale")
@@ -2206,9 +2214,10 @@ class Order(Model, TsTrait):
2206
2214
 
2207
2215
  # партнерские отчисления:
2208
2216
  if self.ad.maker.person.user.status == UserStatus.HOT_PARTNER:
2209
- # спред-заработок мейкера = |сумма ордера − та же сумма по мид-курсу| (всё уже в cur.scale)
2217
+ # спред-заработок мейкера = |сумма ордера − та же сумма по мид-курсу|: amount в cur.scale,
2218
+ # мид-курс — реальный Pair.real() → в cur.scale домножением на 10^cur.scale
2210
2219
  qty = pair.coin.real(await excl.coin_e2x(pair.coin_id, self.quantity))
2211
- earn = abs(amount - pair.rate * qty)
2220
+ earn = abs(amount - pair.real() * qty * 10**pair.cur.scale)
2212
2221
  fee = int(earn * (0.7 - self.ad.maker.person.user.bonus * 0.0001)) # 1% = 100
2213
2222
  b = await self.ad.maker.person.user.balance(pair.cur_id)
2214
2223
  if b < fee + (0 if self.ad.pair_side.is_sell else amount):
@@ -1,4 +1,5 @@
1
1
  from io import BytesIO
2
+ from math import floor, log10
2
3
  from pathlib import Path
3
4
  from struct import pack, unpack
4
5
 
@@ -110,3 +111,20 @@ def obj_pop(obj, name, default=None):
110
111
 
111
112
  def k_space_sep(n: int) -> str:
112
113
  return f"{n:,}".replace(",", " ")
114
+
115
+
116
+ def int_range(bits: int) -> tuple[int, int]:
117
+ if bits < 1:
118
+ raise ValueError("bits must be >= 1")
119
+ return -(1 << (bits - 1)), (1 << (bits - 1)) - 1
120
+
121
+
122
+ def best_scale(price: float, mantissa_uint_bits: int = 20, exp_int_bits: int = 4) -> tuple[int, int]:
123
+ """(мантисса, степень) для цены: price ≈ мантисса / 10^степень, мантисса максимально
124
+ точная в mantissa_uint_bits битах, степень знаковая в exp_int_bits битах (Pair.rate/scale)."""
125
+ max_mantissa = (1 << mantissa_uint_bits) - 1 # for 20 is: 1048575
126
+ if price <= 0:
127
+ raise ValueError("price must be > 0")
128
+ mn, mx = int_range(exp_int_bits)
129
+ scale = max(mn, min(mx, floor(log10(max_mantissa / price))))
130
+ return round(price * 10**scale), scale
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