xync-client 0.0.25.dev39__tar.gz → 0.0.25.dev41__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_client-0.0.25.dev39/xync_client.egg-info → xync_client-0.0.25.dev41}/PKG-INFO +1 -1
- {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/tests/TestAgent.py +12 -1
- {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/tests/TestEx.py +21 -22
- xync_client-0.0.25.dev41/xync_client/Abc/Ex.py +361 -0
- {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client/Htx/agent.py +3 -2
- {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client/Htx/etype/ad.py +4 -3
- xync_client-0.0.25.dev41/xync_client/Htx/ex.py +157 -0
- {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client/TgWallet/ex.py +17 -16
- {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41/xync_client.egg-info}/PKG-INFO +1 -1
- xync_client-0.0.25.dev39/xync_client/Abc/Ex.py +0 -203
- xync_client-0.0.25.dev39/xync_client/Htx/ex.py +0 -91
- {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/.env.sample +0 -0
- {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/.gitignore +0 -0
- {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/.pre-commit-config.yaml +0 -0
- {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/README.md +0 -0
- {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/makefile +0 -0
- {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/pyproject.toml +0 -0
- {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/setup.cfg +0 -0
- {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/tests/TestAsset.py +0 -0
- {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/tests/TestOrder.py +0 -0
- {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/tests/_todo_refact/Binance/test_binance.py +0 -0
- {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/tests/_todo_refact/Bybit/test_bybit.py +0 -0
- {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/tests/_todo_refact/Bybit/test_bybit_p2p.py +0 -0
- {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/tests/_todo_refact/Gate/test_gate.py +0 -0
- {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/tests/_todo_refact/Htx/test_htx_p2p.py +0 -0
- {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/tests/_todo_refact/Wallet/test_agent.py +0 -0
- {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/tests/_todo_refact/Wallet/test_ex.py +0 -0
- {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/tests/_todo_refact/__init__.py +0 -0
- {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/tests/_todo_refact/_test_ex.py +0 -0
- {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client/Abc/Agent.py +0 -0
- {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client/Abc/Asset.py +0 -0
- {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client/Abc/AuthTrait.py +0 -0
- {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client/Abc/Base.py +0 -0
- {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client/Abc/BaseTest.py +0 -0
- {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client/Abc/InAgent.py +0 -0
- {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client/Abc/Order.py +0 -0
- {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client/Binance/__init__.py +0 -0
- {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client/Binance/binance_async.py +0 -0
- {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client/Binance/earn_api.py +0 -0
- {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client/Binance/ex.py +0 -0
- {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client/Binance/exceptions.py +0 -0
- {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client/Binance/sapi.py +0 -0
- {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client/Binance/web_c2c.py +0 -0
- {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client/BingX/__init__.py +0 -0
- {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client/BingX/agent.py +0 -0
- {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client/BingX/base.py +0 -0
- {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client/BingX/ex.py +0 -0
- {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client/BingX/pyd.py +0 -0
- {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client/BingX/req.mjs +0 -0
- {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client/BingX/sign.js +0 -0
- {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client/BingX/test/main.py +0 -0
- {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client/BitGet/__init__.py +0 -0
- {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client/BitGet/agent.py +0 -0
- {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client/BitGet/ex.py +0 -0
- {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client/BitGet/req.mjs +0 -0
- {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client/Bybit/agent.py +0 -0
- {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client/Bybit/ex.py +0 -0
- {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client/Bybit/web_earn.py +0 -0
- {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client/Bybit/web_p2p.py +0 -0
- {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client/Gate/ex.py +0 -0
- {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client/Gate/premarket.py +0 -0
- {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client/Htx/earn.py +0 -0
- {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client/Htx/etype/__init__.py +0 -0
- {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client/Htx/etype/cred.py +0 -0
- {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client/Htx/etype/pm.py +0 -0
- {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client/KuCoin/pub.py +0 -0
- {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client/KuCoin/web.py +0 -0
- {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client/Okx/ex.py +0 -0
- {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client/Okx/pyd.py +0 -0
- {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client/TgWallet/agent.py +0 -0
- {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client/TgWallet/asset.py +0 -0
- {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client/TgWallet/auth.py +0 -0
- {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client/TgWallet/inAgent.py +0 -0
- {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client/TgWallet/order.py +0 -0
- {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client/TgWallet/pyd.py +0 -0
- {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client/TgWallet/pyro.py +0 -0
- {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client/TgWallet/web.py +0 -0
- {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client/__init__.py +0 -0
- {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client/loader.py +0 -0
- {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client/pyro.py +0 -0
- {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client.egg-info/SOURCES.txt +0 -0
- {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client.egg-info/dependency_links.txt +0 -0
- {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client.egg-info/requires.txt +0 -0
- {xync_client-0.0.25.dev39 → xync_client-0.0.25.dev41}/xync_client.egg-info/top_level.txt +0 -0
|
@@ -3,7 +3,7 @@ import logging
|
|
|
3
3
|
import pytest
|
|
4
4
|
from xync_schema.enums import ExStatus, ExAction
|
|
5
5
|
from xync_schema.models import Ex, TestEx, Fiat, Ad, Coin, Cur
|
|
6
|
-
from xync_schema.
|
|
6
|
+
from xync_schema.types import FiatNew, BaseAd
|
|
7
7
|
|
|
8
8
|
from xync_client.Abc.Base import DictOfDicts, ListOfDicts
|
|
9
9
|
from xync_client.Abc.BaseTest import BaseTest
|
|
@@ -25,6 +25,17 @@ class TestAgent(BaseTest):
|
|
|
25
25
|
yield clients
|
|
26
26
|
[(await taker.close(), await maker.close()) for taker, maker in clients]
|
|
27
27
|
|
|
28
|
+
# 42
|
|
29
|
+
async def test_ad(self, clients: list[BaseExClient]):
|
|
30
|
+
for client in clients:
|
|
31
|
+
if not self.ad.get(client.ex.id):
|
|
32
|
+
await self.test_ads(clients)
|
|
33
|
+
ad: BaseAd = await client.ad(self.ad[client.ex.id].id)
|
|
34
|
+
ok = isinstance(ad, BaseAd)
|
|
35
|
+
t, _ = await TestEx.update_or_create({"ok": ok}, ex=client.ex, action=ExAction.ad)
|
|
36
|
+
assert t.ok, "No ad"
|
|
37
|
+
logging.info(f"{client.ex.name}: {ExAction.ad.name} - ok")
|
|
38
|
+
|
|
28
39
|
# 0
|
|
29
40
|
async def test_get_orders(self, clients: list[BaseAgentClient]):
|
|
30
41
|
for taker, maker in clients:
|
|
@@ -1,19 +1,21 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
|
|
3
3
|
import pytest
|
|
4
|
-
from xync_schema.
|
|
4
|
+
from xync_schema.types import Pm, BaseAd, CurE, CoinE
|
|
5
5
|
|
|
6
6
|
from xync_client.Abc.BaseTest import BaseTest
|
|
7
7
|
from xync_schema.enums import ExStatus, ExType, ExAction
|
|
8
8
|
from xync_schema.models import Ex, TestEx as ExTest
|
|
9
9
|
|
|
10
|
-
from xync_client.Abc.Base import BaseClient,
|
|
10
|
+
from xync_client.Abc.Base import BaseClient, MapOfIdsList
|
|
11
11
|
from xync_client.Abc.Ex import BaseExClient
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
@pytest.mark.asyncio(loop_scope="session")
|
|
15
15
|
class TestEx(BaseTest):
|
|
16
|
-
|
|
16
|
+
coins: dict[int, dict[str, CoinE]] = {}
|
|
17
|
+
curs: dict[int, dict[str, CurE]] = {}
|
|
18
|
+
ad: dict[int, BaseAd] = {}
|
|
17
19
|
|
|
18
20
|
@pytest.fixture
|
|
19
21
|
async def clients(self) -> list[BaseClient]:
|
|
@@ -26,17 +28,18 @@ class TestEx(BaseTest):
|
|
|
26
28
|
# 19
|
|
27
29
|
async def test_curs(self, clients: list[BaseExClient]):
|
|
28
30
|
for client in clients:
|
|
29
|
-
curs:
|
|
30
|
-
ok = self.
|
|
31
|
+
curs: dict[str, CurE] = await client.curs()
|
|
32
|
+
ok = self.is_dict_of_objects(curs, CurE)
|
|
31
33
|
t, _ = await ExTest.update_or_create({"ok": ok}, ex=client.ex, action=ExAction.curs)
|
|
32
34
|
assert t.ok, "No curs"
|
|
35
|
+
self.curs[client.ex.id] = curs
|
|
33
36
|
logging.info(f"{client.ex.name}: {ExAction.curs.name} - ok")
|
|
34
37
|
|
|
35
38
|
# 20
|
|
36
39
|
async def test_pms(self, clients: list[BaseExClient]):
|
|
37
40
|
for client in clients:
|
|
38
|
-
pms: dict[int | str,
|
|
39
|
-
ok = self.is_dict_of_objects(pms,
|
|
41
|
+
pms: dict[int | str, Pm] = await client.pms()
|
|
42
|
+
ok = self.is_dict_of_objects(pms, Pm)
|
|
40
43
|
t, _ = await ExTest.update_or_create({"ok": ok}, ex=client.ex, action=ExAction.pms)
|
|
41
44
|
assert t.ok, "No pms"
|
|
42
45
|
logging.info(f"{client.ex.name}: {ExAction.pms.name} - ok")
|
|
@@ -53,17 +56,18 @@ class TestEx(BaseTest):
|
|
|
53
56
|
# 22
|
|
54
57
|
async def test_coins(self, clients: list[BaseExClient]):
|
|
55
58
|
for client in clients:
|
|
56
|
-
coins:
|
|
57
|
-
ok = self.
|
|
59
|
+
coins: dict[str, CoinE] = await client.coins()
|
|
60
|
+
ok = self.is_dict_of_objects(coins, CoinE)
|
|
58
61
|
t, _ = await ExTest.update_or_create({"ok": ok}, ex=client.ex, action=ExAction.coins)
|
|
59
62
|
assert t.ok, "No coins"
|
|
63
|
+
self.coins[client.ex.id] = coins
|
|
60
64
|
logging.info(f"{client.ex.name}: {ExAction.coins.name} - ok")
|
|
61
65
|
|
|
62
66
|
# 23
|
|
63
67
|
async def test_pairs(self, clients: list[BaseExClient]):
|
|
64
68
|
for client in clients:
|
|
65
|
-
|
|
66
|
-
ok = self.is_map_of_ids(
|
|
69
|
+
pairs_buy, pairs_sell = await client.pairs()
|
|
70
|
+
ok = self.is_map_of_ids(pairs_buy) and self.is_map_of_ids(pairs_sell)
|
|
67
71
|
t, _ = await ExTest.update_or_create({"ok": ok}, ex=client.ex, action=ExAction.pairs)
|
|
68
72
|
assert t.ok, "No coins"
|
|
69
73
|
logging.info(f"{client.ex.name}: {ExAction.pairs.name} - ok")
|
|
@@ -71,18 +75,13 @@ class TestEx(BaseTest):
|
|
|
71
75
|
# 24
|
|
72
76
|
async def test_ads(self, clients: list[BaseExClient]):
|
|
73
77
|
for client in clients:
|
|
74
|
-
|
|
78
|
+
self.coins[client.ex.id] = self.coins.get(client.ex.id) or await client.coins()
|
|
79
|
+
self.curs[client.ex.id] = self.curs.get(client.ex.id) or await client.curs()
|
|
80
|
+
coin: int | str = self.coins[client.ex.id]["USDT"].exid
|
|
81
|
+
cur: int | str = self.curs[client.ex.id]["EUR"].exid
|
|
82
|
+
ads: list[BaseAd] = await client.ads(coin, cur, False)
|
|
75
83
|
ok = self.is_list_of_objects(ads, BaseAd)
|
|
76
84
|
t, _ = await ExTest.update_or_create({"ok": ok}, ex=client.ex, action=ExAction.ads)
|
|
77
85
|
assert t.ok, "No ads"
|
|
78
|
-
self.ad[client.ex.
|
|
86
|
+
self.ad[client.ex.id] = ads[0] # for further use in test_ad
|
|
79
87
|
logging.info(f"{client.ex.name}: {ExAction.ads.name} - ok")
|
|
80
|
-
|
|
81
|
-
# 42
|
|
82
|
-
async def test_ad(self, clients: list[BaseExClient]):
|
|
83
|
-
for client in clients:
|
|
84
|
-
ad: BaseAd = await client.ad(self.ad.get(client.ex.name, (await client.ads("USDT", "RUB", False))[0]).id)
|
|
85
|
-
ok = isinstance(ad, BaseAd)
|
|
86
|
-
t, _ = await ExTest.update_or_create({"ok": ok}, ex=client.ex, action=ExAction.ad)
|
|
87
|
-
assert t.ok, "No ad"
|
|
88
|
-
logging.info(f"{client.ex.name}: {ExAction.ad.name} - ok")
|
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import re
|
|
3
|
+
from abc import abstractmethod
|
|
4
|
+
|
|
5
|
+
import msgspec
|
|
6
|
+
from msgspec import Struct
|
|
7
|
+
from tortoise.exceptions import MultipleObjectsReturned, IntegrityError
|
|
8
|
+
from x_model.models import PydIn
|
|
9
|
+
from xync_schema import types
|
|
10
|
+
from xync_schema import models
|
|
11
|
+
|
|
12
|
+
from xync_client.Abc.Base import BaseClient, MapOfIdsList
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class PmUni(PydIn):
|
|
16
|
+
_unq: list[str] = ["norm", "country"]
|
|
17
|
+
|
|
18
|
+
norm: str
|
|
19
|
+
acronym: str = None
|
|
20
|
+
country: str = None
|
|
21
|
+
alias: str = None
|
|
22
|
+
extra: str = None
|
|
23
|
+
bank: bool = None
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class PmUnifier:
|
|
27
|
+
pms: dict[str, PmUni] = {} # {origin: normalized}
|
|
28
|
+
|
|
29
|
+
pm_map: dict[str, str] = {
|
|
30
|
+
"Юmoney": "YooMoney",
|
|
31
|
+
# "Local Bank (R-Green)": "Sberbank",
|
|
32
|
+
# "Local Bank (S-Green)": "Sberbank",
|
|
33
|
+
# "Local Card (Red)": "Alfa-Bank",
|
|
34
|
+
# "Local Card (Yellow)": "Tinkoff",
|
|
35
|
+
# "Local Card M-redTS": "MTS-bank",
|
|
36
|
+
# "Local Card-Green": "Sberbank",
|
|
37
|
+
# "Local Card-Yellow": "Tinkoff",
|
|
38
|
+
# "GTB Bank (Guarantee Trust Bank)": "GTBank",
|
|
39
|
+
}
|
|
40
|
+
re_bank = [
|
|
41
|
+
r"^bank (?!of )|bank$",
|
|
42
|
+
r" banka$",
|
|
43
|
+
r" bankas$",
|
|
44
|
+
r" bankası$",
|
|
45
|
+
r" banca$",
|
|
46
|
+
r"^banco(?! de | del )| banco$",
|
|
47
|
+
]
|
|
48
|
+
re_extra = [
|
|
49
|
+
r"\(card\)$|\bpay$|\bmoney$|\bwallet$|\bcash$",
|
|
50
|
+
r"b\.s\.c\.|k\.s\.c|s\.a$| sv$| gt$",
|
|
51
|
+
]
|
|
52
|
+
re_glut = [
|
|
53
|
+
r"\.io$|\.com$",
|
|
54
|
+
r"l'|d’|d'",
|
|
55
|
+
]
|
|
56
|
+
i18n_map = {
|
|
57
|
+
# " ": " ",
|
|
58
|
+
"nationale": "national",
|
|
59
|
+
"а": "a",
|
|
60
|
+
"á": "a",
|
|
61
|
+
"â": "a",
|
|
62
|
+
"о": "o",
|
|
63
|
+
"ó": "o",
|
|
64
|
+
"ō": "o",
|
|
65
|
+
"ú": "u",
|
|
66
|
+
"ü": "u",
|
|
67
|
+
"ų": "u",
|
|
68
|
+
"с": "c",
|
|
69
|
+
"č": "c",
|
|
70
|
+
"ç": "c",
|
|
71
|
+
"é": "e",
|
|
72
|
+
"è": "e",
|
|
73
|
+
"ş": "s",
|
|
74
|
+
"š": "s",
|
|
75
|
+
"ř": "r",
|
|
76
|
+
"í": "i",
|
|
77
|
+
}
|
|
78
|
+
rms = " -:`'’′"
|
|
79
|
+
|
|
80
|
+
def __init__(self, countries: list[str]):
|
|
81
|
+
self.cts = countries
|
|
82
|
+
|
|
83
|
+
def countries(self, name: str):
|
|
84
|
+
for ct in self.cts:
|
|
85
|
+
# Если имя кончается на "Название_страны" в скобках
|
|
86
|
+
if (
|
|
87
|
+
ct
|
|
88
|
+
and self.pms[name].norm.endswith((ct + ")", ct))
|
|
89
|
+
and not self.pms[name].norm.endswith((" of " + ct, " of the " + ct, " and " + ct, " de " + ct))
|
|
90
|
+
):
|
|
91
|
+
self.pms[name].norm = self.pms[name].norm.replace(ct, "")
|
|
92
|
+
self.pms[name].country = ct
|
|
93
|
+
self.clear(name)
|
|
94
|
+
return
|
|
95
|
+
|
|
96
|
+
def extra(self, name: str):
|
|
97
|
+
for r in self.re_extra:
|
|
98
|
+
if match := re.search(r, self.pms[name].norm):
|
|
99
|
+
self.pms[name].norm = self.pms[name].norm.replace(match.group(), "")
|
|
100
|
+
self.pms[name].extra = match.group()
|
|
101
|
+
self.clear(name)
|
|
102
|
+
return
|
|
103
|
+
|
|
104
|
+
def alias(self, name: str):
|
|
105
|
+
if match := re.search(r"\(.+\)$", self.pms[name].norm):
|
|
106
|
+
self.pms[name].norm = self.pms[name].norm.replace(match.group(), "")
|
|
107
|
+
self.pms[name].alias = match.group()[1:-1]
|
|
108
|
+
self.clear(name)
|
|
109
|
+
return
|
|
110
|
+
|
|
111
|
+
def bank(self, name: str):
|
|
112
|
+
for r in self.re_bank:
|
|
113
|
+
if match := re.search(r, self.pms[name].norm):
|
|
114
|
+
self.pms[name].norm = self.pms[name].norm.replace(match.group(), "")
|
|
115
|
+
self.pms[name].bank = True
|
|
116
|
+
self.clear(name)
|
|
117
|
+
return
|
|
118
|
+
|
|
119
|
+
def acro(self, name: str):
|
|
120
|
+
acr = "".join(
|
|
121
|
+
wrd[0]
|
|
122
|
+
for wrd in self.pms[name].norm.split(" ")
|
|
123
|
+
if not wrd.isupper()
|
|
124
|
+
and not wrd.startswith(("(", "the"))
|
|
125
|
+
and len(wrd) > 2
|
|
126
|
+
or (len(wrd) > 1 and wrd.istitle())
|
|
127
|
+
).upper()
|
|
128
|
+
if len(acr) >= 2 and (
|
|
129
|
+
f"({acr})" in self.pms[name].norm
|
|
130
|
+
or self.pms[name].norm.startswith((acr + " ", acr + ":"))
|
|
131
|
+
or self.pms[name].norm.endswith(" " + acr)
|
|
132
|
+
):
|
|
133
|
+
self.pms[name].norm = self.pms[name].norm.replace(acr, "", 1).replace("()", "", 1).strip()
|
|
134
|
+
self.pms[name].acronym = acr
|
|
135
|
+
self.clear(name)
|
|
136
|
+
|
|
137
|
+
def slim(self, name: str):
|
|
138
|
+
for rm in self.re_glut:
|
|
139
|
+
self.pms[name].norm = re.sub(rm, "", self.pms[name].norm)
|
|
140
|
+
|
|
141
|
+
def i18n(self, name: str):
|
|
142
|
+
for src, trgt in self.i18n_map.items():
|
|
143
|
+
self.pms[name].norm = self.pms[name].norm.replace(src, trgt)
|
|
144
|
+
|
|
145
|
+
def clear(self, name: str):
|
|
146
|
+
self.pms[name].norm = self.pms[name].norm.replace("()", "").replace(" ", " ").strip(" -:")
|
|
147
|
+
|
|
148
|
+
def __call__(self, s: str) -> PmUni:
|
|
149
|
+
# если в словаре замен есть текущее назвние - меняем, иначе берем строку до запятой
|
|
150
|
+
self.pms[s] = PmUni(norm=self.pm_map.get(s, s.split(",")[0]))
|
|
151
|
+
# вырезаем мусорные добавки
|
|
152
|
+
self.slim(s)
|
|
153
|
+
# заменяем локальные символы на англ:
|
|
154
|
+
self.i18n(s)
|
|
155
|
+
# находим и вырезаем аббревиатуру, если есть
|
|
156
|
+
self.acro(s)
|
|
157
|
+
# уменьшаем все буквы
|
|
158
|
+
self.pms[s].norm = self.pms[s].norm.lower()
|
|
159
|
+
# находим и вырезаем страны, если есть
|
|
160
|
+
self.countries(s)
|
|
161
|
+
self.bank(s)
|
|
162
|
+
self.extra(s)
|
|
163
|
+
self.alias(s)
|
|
164
|
+
self.bank(s)
|
|
165
|
+
self.countries(s)
|
|
166
|
+
self.extra(s)
|
|
167
|
+
# вырезаем каждый символ rms
|
|
168
|
+
[self.pms[s].norm.replace(rm, "") for rm in self.rms]
|
|
169
|
+
|
|
170
|
+
return self.pms[s]
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
class BaseExClient(BaseClient):
|
|
174
|
+
@abstractmethod
|
|
175
|
+
def pm_type_map(self, type_: models.Pmex) -> str: ...
|
|
176
|
+
|
|
177
|
+
# 19: Список поддерживаемых валют тейкера
|
|
178
|
+
@abstractmethod
|
|
179
|
+
async def curs(self) -> dict[str, types.CurE]: # {cur.ticker: cur}
|
|
180
|
+
...
|
|
181
|
+
|
|
182
|
+
# 20: Список платежных методов
|
|
183
|
+
@abstractmethod
|
|
184
|
+
async def pms(self, cur: models.Cur = None) -> dict[int | str, types.Pm]: # {pm.exid: pm}
|
|
185
|
+
...
|
|
186
|
+
|
|
187
|
+
# 21: Список платежных методов по каждой валюте
|
|
188
|
+
@abstractmethod
|
|
189
|
+
async def cur_pms_map(self) -> MapOfIdsList: # {cur.exid: [pm.exid]}
|
|
190
|
+
...
|
|
191
|
+
|
|
192
|
+
# 22: Список торгуемых монет (с ограничениям по валютам, если есть)
|
|
193
|
+
@abstractmethod
|
|
194
|
+
async def coins(self) -> dict[str, types.CoinE]: # {coin.ticker: coin}
|
|
195
|
+
...
|
|
196
|
+
|
|
197
|
+
# 23: Список пар валюта/монет
|
|
198
|
+
@abstractmethod
|
|
199
|
+
async def pairs(self) -> tuple[MapOfIdsList, MapOfIdsList]: ...
|
|
200
|
+
|
|
201
|
+
# 24: Список объяв по (buy/sell, cur, coin, pm)
|
|
202
|
+
@abstractmethod
|
|
203
|
+
async def ads(
|
|
204
|
+
self, coin_exid: str, cur_exid: str, is_sell: bool, pm_exids: list[str | int] = None, amount: int = None
|
|
205
|
+
) -> list[types.BaseAd]: # {ad.id: ad}
|
|
206
|
+
...
|
|
207
|
+
|
|
208
|
+
# 42: Чужая объява по id
|
|
209
|
+
@abstractmethod
|
|
210
|
+
async def ad(self, ad_id: int) -> types.BaseAd: ...
|
|
211
|
+
|
|
212
|
+
# Преобразрование объекта объявления из формата биржи в формат xync
|
|
213
|
+
@abstractmethod
|
|
214
|
+
async def ad_epyd2pydin(self, ad: types.BaseAd) -> types.BaseAdIn: ... # my_uid: for MyAd
|
|
215
|
+
|
|
216
|
+
# 99: Страны
|
|
217
|
+
async def countries(self) -> list[Struct]:
|
|
218
|
+
return []
|
|
219
|
+
|
|
220
|
+
# Импорт Pm-ов (с Pmcur-, Pmex- и Pmcurex-ами) и валют (с Curex-ами) с биржи в бд
|
|
221
|
+
async def set_pmcurexs(self):
|
|
222
|
+
# Curs
|
|
223
|
+
cur_pyds: dict[str, types.CurE] = await self.curs()
|
|
224
|
+
curs: dict[int | str, models.Cur] = {
|
|
225
|
+
cur_pyd.exid: (await models.Cur.update_or_create({"rate": cur_pyd.rate}, ticker=tkr))[0]
|
|
226
|
+
for tkr, cur_pyd in cur_pyds.items()
|
|
227
|
+
}
|
|
228
|
+
curexs = [models.Curex(**c.model_dump(), cur=curs[c.exid], ex=self.ex) for c in cur_pyds.values()]
|
|
229
|
+
# Curex
|
|
230
|
+
await models.Curex.bulk_create(
|
|
231
|
+
curexs, update_fields=["minimum", "rounding_scale"], on_conflict=["cur_id", "ex_id"]
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
countries = await self.countries()
|
|
235
|
+
|
|
236
|
+
cur_map = {
|
|
237
|
+
172: "CNY",
|
|
238
|
+
8: "KRW",
|
|
239
|
+
25: "MMK",
|
|
240
|
+
}
|
|
241
|
+
for c in countries:
|
|
242
|
+
if c.cur_id not in curs:
|
|
243
|
+
cur, _ = await models.Cur.get_or_create(id=c.cur_id, ticker=cur_map[c.cur_id])
|
|
244
|
+
c.cur_id = cur.id
|
|
245
|
+
# Country preparing
|
|
246
|
+
# countries = sorted(
|
|
247
|
+
# (c for c in countries if c.code not in (999, 9999, 441624, 999999)), key=lambda x: x.name
|
|
248
|
+
# ) # sort and filter
|
|
249
|
+
cnts = {
|
|
250
|
+
"BosniaandHerzegovina": "BA",
|
|
251
|
+
"Brunei": "BN",
|
|
252
|
+
"Congo": "CD",
|
|
253
|
+
"Djibouti": "DJ",
|
|
254
|
+
"Guinea": "GN",
|
|
255
|
+
"Iraq": "IQ",
|
|
256
|
+
"Kyrgyzstan": "KG",
|
|
257
|
+
"ComorosIslands": "KM",
|
|
258
|
+
"Liberia": "LR",
|
|
259
|
+
"Libya": "LY",
|
|
260
|
+
"Yemen": "YE",
|
|
261
|
+
"Zimbabwe": "ZW",
|
|
262
|
+
"United States of America": "US",
|
|
263
|
+
"Lebanon": "LB",
|
|
264
|
+
"Central African Republic": "XA",
|
|
265
|
+
"Laos": "LA",
|
|
266
|
+
"Tanzania": "TZ",
|
|
267
|
+
"Bangladesh": "BD",
|
|
268
|
+
}
|
|
269
|
+
[setattr(c, "short", cnts.get(c.name, c.short)) for c in countries] # add missed shortNames
|
|
270
|
+
# Countries create
|
|
271
|
+
cntrs: [models.Country] = [models.Country(**msgspec.to_builtins(c)) for c in countries]
|
|
272
|
+
# ids only for HTX
|
|
273
|
+
await models.Country.bulk_create(cntrs, ignore_conflicts=True)
|
|
274
|
+
# todo: curexcountry
|
|
275
|
+
|
|
276
|
+
# Pms
|
|
277
|
+
pms_epyds: dict[int | str, types.Pm] = {
|
|
278
|
+
k: v for k, v in sorted((await self.pms()).items(), key=lambda x: x[1].name)
|
|
279
|
+
} # sort by name
|
|
280
|
+
pms: dict[int | str, models.Pm] = dict({})
|
|
281
|
+
prev = 0, "", "" # id, normd-name, orig-name
|
|
282
|
+
cntrs = [c.lower() for c in await models.Country.all().values_list("name", flat=True)]
|
|
283
|
+
uni = PmUnifier(cntrs)
|
|
284
|
+
for k, pm in pms_epyds.items():
|
|
285
|
+
pmu: PmUni = uni(pm.name)
|
|
286
|
+
country = await models.Country.get(name__iexact=cnt) if (cnt := pmu.country) else None
|
|
287
|
+
if prev[2] == pm.name and pmu.country == prev[3]: # оригинальное имя не уникально на этой бирже
|
|
288
|
+
logging.warning(f"Pm: '{pm.name}' duplicated with ids {prev[0]}: {k} on {self.ex.name}")
|
|
289
|
+
# новый Pm не добавляем, а берем старый с этим названием
|
|
290
|
+
pm_ = pms.get(prev[0], await models.Pm.get_or_none(norm=prev[1], country=country))
|
|
291
|
+
# и добавляем Pmex для него
|
|
292
|
+
await models.Pmex.update_or_create({"name": pm.name}, ex=self.ex, exid=k, pm=pm_)
|
|
293
|
+
elif (
|
|
294
|
+
prev[1] == pmu.norm and pmu.country == prev[3]
|
|
295
|
+
): # 2 разных оригинальных имени на этой бирже совпали при нормализации
|
|
296
|
+
logging.error(
|
|
297
|
+
f"Pm: {pm.name}&{prev[2]} overnormd as {pmu.norm} with ids {prev[0]}: {k} on {self.ex.name}"
|
|
298
|
+
)
|
|
299
|
+
# новый Pm не добавляем, только Pmex для него
|
|
300
|
+
# новый Pm не добавляем, а берем старый с этим названием
|
|
301
|
+
pm_ = pms.get(prev[0], await models.Pm.get_or_none(norm=prev[1], country=country))
|
|
302
|
+
# и добавляем.обновляем Pmex для него
|
|
303
|
+
await models.Pmex.update_or_create({"pm": pm_}, ex=self.ex, exid=k, name=pm.name)
|
|
304
|
+
else:
|
|
305
|
+
# todo: add logo and all other
|
|
306
|
+
d = {**pmu.df_unq(), "country": country}
|
|
307
|
+
if pm.logo:
|
|
308
|
+
d["defaults"]["logo"] = pm.logo
|
|
309
|
+
if pm.type_:
|
|
310
|
+
d["defaults"]["type_"] = pm.type_
|
|
311
|
+
try:
|
|
312
|
+
pms[k], _ = await models.Pm.update_or_create(**d)
|
|
313
|
+
except (MultipleObjectsReturned, IntegrityError) as e:
|
|
314
|
+
print(d)
|
|
315
|
+
raise e
|
|
316
|
+
prev = k, pmu.norm, pm.name, pmu.country
|
|
317
|
+
# Pmexs
|
|
318
|
+
pmexs = [models.Pmex(exid=k, ex=self.ex, pm=pm, name=pms_epyds[k].name) for k, pm in pms.items()]
|
|
319
|
+
await models.Pmex.bulk_create(pmexs, on_conflict=["ex_id", "exid"], update_fields=["pm_id", "name", "name_"])
|
|
320
|
+
# Pmex banks
|
|
321
|
+
for k, pm in pms_epyds.items():
|
|
322
|
+
if banks := pm.banks:
|
|
323
|
+
pmex = await models.Pmex.get(ex=self.ex, exid=k) # pm=pms[k],
|
|
324
|
+
for b in banks:
|
|
325
|
+
await models.PmexBank.update_or_create({"name": b.name}, exid=b.exid, pmex=pmex)
|
|
326
|
+
|
|
327
|
+
cur2pms = await self.cur_pms_map()
|
|
328
|
+
# # Link PayMethods with currencies
|
|
329
|
+
pmcurs = set()
|
|
330
|
+
for cur_id, exids in cur2pms.items():
|
|
331
|
+
for exid in exids:
|
|
332
|
+
pmcurs.add(
|
|
333
|
+
(
|
|
334
|
+
await models.Pmcur.update_or_create(
|
|
335
|
+
cur=curs[cur_id],
|
|
336
|
+
pm=pms.get(exid)
|
|
337
|
+
or (await models.Pmex.get(ex=self.ex, exid=exid).prefetch_related("pm")).pm,
|
|
338
|
+
)
|
|
339
|
+
)[0]
|
|
340
|
+
)
|
|
341
|
+
# pmcurexs = [Pmcurex(pmcur=pmcur, ex=self.ex) for pmcur in pmcurs]
|
|
342
|
+
# await Pmcurex.bulk_create(pmcurexs)
|
|
343
|
+
|
|
344
|
+
# Импорт монет (с Coinex-ами) с биржи в бд
|
|
345
|
+
async def set_coinexs(self):
|
|
346
|
+
coins: dict[str, types.CoinE] = await self.coins()
|
|
347
|
+
coins_db: dict[int, models.Coin] = {
|
|
348
|
+
c.exid: (await models.Coin.update_or_create(ticker=c.ticker))[0] for c in coins.values()
|
|
349
|
+
}
|
|
350
|
+
coinexs: list[models.Coinex] = [
|
|
351
|
+
models.Coinex(coin=coins_db[c.exid], ex=self.ex, exid=c.exid, minimum=c.minimum) for c in coins.values()
|
|
352
|
+
]
|
|
353
|
+
await models.Coinex.bulk_create(coinexs, update_fields=["minimum"], on_conflict=["coin_id", "ex_id"])
|
|
354
|
+
|
|
355
|
+
# Сохранение чужого объявления (с Pm-ами) в бд
|
|
356
|
+
async def ad_pydin2db(self, ad_pydin: types.BaseAdIn) -> models.Ad:
|
|
357
|
+
df, unq = ad_pydin.args()
|
|
358
|
+
ad_db, _ = await models.Ad.update_or_create(df, **unq)
|
|
359
|
+
if getattr(ad_pydin, "pms_", None): # if it ListItem, not Full One # todo: remove?
|
|
360
|
+
await ad_db.pms.add(*ad_pydin.pms_)
|
|
361
|
+
return ad_db
|
|
@@ -83,8 +83,9 @@ class Private(BaseAgentClient):
|
|
|
83
83
|
async def ads_switch(self) -> bool:
|
|
84
84
|
pass
|
|
85
85
|
|
|
86
|
-
async def get_user(self, user_id) -> dict:
|
|
87
|
-
|
|
86
|
+
async def get_user(self, user_id: int) -> dict:
|
|
87
|
+
user = (await self._get(f"/-/x/otc/v1/user/{user_id}/info"))["data"]
|
|
88
|
+
return user
|
|
88
89
|
|
|
89
90
|
async def send_user_msg(self, msg: str, file=None) -> bool:
|
|
90
91
|
pass
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from typing import Literal
|
|
2
2
|
|
|
3
3
|
from pydantic import BaseModel
|
|
4
|
+
from xync_schema.types import BaseAd
|
|
4
5
|
|
|
5
6
|
|
|
6
7
|
class TradeRule(BaseModel):
|
|
@@ -56,7 +57,7 @@ class PayName(BaseModel):
|
|
|
56
57
|
id: int
|
|
57
58
|
|
|
58
59
|
|
|
59
|
-
class Resp(
|
|
60
|
+
class Resp(BaseAd):
|
|
60
61
|
blockType: int
|
|
61
62
|
chargeType: bool
|
|
62
63
|
coinId: int
|
|
@@ -71,12 +72,12 @@ class Resp(BaseModel):
|
|
|
71
72
|
labelName: str | None = None
|
|
72
73
|
maxTradeLimit: str
|
|
73
74
|
merchantLevel: int
|
|
74
|
-
merchantTags: str | None
|
|
75
|
+
merchantTags: list[str] | None
|
|
75
76
|
minTradeLimit: str
|
|
76
77
|
orderCompleteRate: str
|
|
77
78
|
payMethod: str
|
|
78
79
|
payMethods: list[PayMethod]
|
|
79
|
-
payName: list[PayName] # приходит массив объектов внутри строки
|
|
80
|
+
payName: str # list[PayName] # приходит массив объектов внутри строки
|
|
80
81
|
payTerm: int
|
|
81
82
|
price: str
|
|
82
83
|
seaViewRoom: str | None = None
|