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.
- {xync_db-0.5.2 → xync_db-0.5.3}/PKG-INFO +1 -1
- xync_db-0.5.3/src/xync_db/migrations/models/0011_pair_scale.py +45 -0
- {xync_db-0.5.2 → xync_db-0.5.3}/src/xync_db/models/__init__.py +24 -15
- {xync_db-0.5.2 → xync_db-0.5.3}/src/xync_db/shared.py +18 -0
- {xync_db-0.5.2 → xync_db-0.5.3}/.gitignore +0 -0
- {xync_db-0.5.2 → xync_db-0.5.3}/README.md +0 -0
- {xync_db-0.5.2 → xync_db-0.5.3}/pyproject.toml +0 -0
- {xync_db-0.5.2 → xync_db-0.5.3}/src/xync_db/__init__.py +0 -0
- {xync_db-0.5.2 → xync_db-0.5.3}/src/xync_db/config.py +0 -0
- {xync_db-0.5.2 → xync_db-0.5.3}/src/xync_db/enums.py +0 -0
- {xync_db-0.5.2 → xync_db-0.5.3}/src/xync_db/exceptions.py +0 -0
- {xync_db-0.5.2 → xync_db-0.5.3}/src/xync_db/graph.py +0 -0
- {xync_db-0.5.2 → xync_db-0.5.3}/src/xync_db/i18n.py +0 -0
- {xync_db-0.5.2 → xync_db-0.5.3}/src/xync_db/login_store.py +0 -0
- {xync_db-0.5.2 → xync_db-0.5.3}/src/xync_db/logo.png +0 -0
- {xync_db-0.5.2 → xync_db-0.5.3}/src/xync_db/migrations/CLAUDE.md +0 -0
- {xync_db-0.5.2 → xync_db-0.5.3}/src/xync_db/migrations/__init__.py +0 -0
- {xync_db-0.5.2 → xync_db-0.5.3}/src/xync_db/migrations/models/0001_initial.py +0 -0
- {xync_db-0.5.2 → xync_db-0.5.3}/src/xync_db/migrations/models/0002_triggers.py +0 -0
- {xync_db-0.5.2 → xync_db-0.5.3}/src/xync_db/migrations/models/0003_files_seed.py +0 -0
- {xync_db-0.5.2 → xync_db-0.5.3}/src/xync_db/migrations/models/0004_all_seed.py +0 -0
- {xync_db-0.5.2 → xync_db-0.5.3}/src/xync_db/migrations/models/0005_credex_del.py +0 -0
- {xync_db-0.5.2 → xync_db-0.5.3}/src/xync_db/migrations/models/0006_credex_orphan.py +0 -0
- {xync_db-0.5.2 → xync_db-0.5.3}/src/xync_db/migrations/models/0007_agent_auth_notify.py +0 -0
- {xync_db-0.5.2 → xync_db-0.5.3}/src/xync_db/migrations/models/0008_ad_exid_ex_uniq.py +0 -0
- {xync_db-0.5.2 → xync_db-0.5.3}/src/xync_db/migrations/models/0009_web3_tables.py +0 -0
- {xync_db-0.5.2 → xync_db-0.5.3}/src/xync_db/migrations/models/0010_updatestate_fix.py +0 -0
- {xync_db-0.5.2 → xync_db-0.5.3}/src/xync_db/migrations/models/__init__.py +0 -0
- {xync_db-0.5.2 → xync_db-0.5.3}/src/xync_db/migrations/models/seed/__init__.py +0 -0
- {xync_db-0.5.2 → xync_db-0.5.3}/src/xync_db/migrations/models/seed/a_actor_seed.py +0 -0
- {xync_db-0.5.2 → xync_db-0.5.3}/src/xync_db/migrations/models/seed/a_tg_seed.py +0 -0
- {xync_db-0.5.2 → xync_db-0.5.3}/src/xync_db/migrations/models/seed/b_htx_seed.py +0 -0
- {xync_db-0.5.2 → xync_db-0.5.3}/src/xync_db/migrations/models/seed/c_net_seed.py +0 -0
- {xync_db-0.5.2 → xync_db-0.5.3}/src/xync_db/migrations/models/seed/xync_files.sql +0 -0
- {xync_db-0.5.2 → xync_db-0.5.3}/src/xync_db/models/CLAUDE.md +0 -0
- {xync_db-0.5.2 → xync_db-0.5.3}/src/xync_db/py.typed +0 -0
- {xync_db-0.5.2 → xync_db-0.5.3}/src/xync_db/typs/__init__.py +0 -0
- {xync_db-0.5.2 → xync_db-0.5.3}/src/xync_db/typs/db/__init__.py +0 -0
- {xync_db-0.5.2 → xync_db-0.5.3}/src/xync_db/typs/db/actor.py +0 -0
- {xync_db-0.5.2 → xync_db-0.5.3}/src/xync_db/typs/db/ad.py +0 -0
- {xync_db-0.5.2 → xync_db-0.5.3}/src/xync_db/typs/db/common.py +0 -0
- {xync_db-0.5.2 → xync_db-0.5.3}/src/xync_db/typs/db/cred.py +0 -0
- {xync_db-0.5.2 → xync_db-0.5.3}/src/xync_db/typs/db/order.py +0 -0
- {xync_db-0.5.2 → xync_db-0.5.3}/src/xync_db/typs/db/pm.py +0 -0
|
@@ -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
|
-
|
|
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,
|
|
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).
|
|
574
|
-
# ⇒ usdt_value = raw /
|
|
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
|
-
#
|
|
632
|
-
#
|
|
633
|
-
#
|
|
634
|
-
|
|
635
|
-
|
|
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
|
|
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
|
|
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
|
-
# спред-заработок мейкера = |сумма ордера − та же сумма по
|
|
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.
|
|
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
|
|
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
|