xync-client 0.0.25.dev3__tar.gz → 0.0.25.dev6__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.dev3/xync_client.egg-info → xync_client-0.0.25.dev6}/PKG-INFO +1 -1
- {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/tests/TestEx.py +26 -11
- {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/xync_client/Abc/Agent.py +13 -29
- {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/xync_client/Abc/BaseTest.py +14 -7
- {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/xync_client/Abc/Ex.py +26 -14
- {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/xync_client/TgWallet/agent.py +60 -87
- {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/xync_client/TgWallet/ex.py +39 -24
- xync_client-0.0.25.dev6/xync_client/TgWallet/pyd.py +206 -0
- {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6/xync_client.egg-info}/PKG-INFO +1 -1
- xync_client-0.0.25.dev3/xync_client/TgWallet/pyd.py +0 -202
- {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/.env.sample +0 -0
- {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/.gitignore +0 -0
- {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/.pre-commit-config.yaml +0 -0
- {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/README.md +0 -0
- {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/makefile +0 -0
- {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/pyproject.toml +0 -0
- {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/setup.cfg +0 -0
- {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/tests/TestAgent.py +0 -0
- {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/tests/TestAsset.py +0 -0
- {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/tests/TestOrder.py +0 -0
- {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/tests/_todo_refact/Binance/test_binance.py +0 -0
- {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/tests/_todo_refact/Bybit/test_bybit.py +0 -0
- {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/tests/_todo_refact/Bybit/test_bybit_p2p.py +0 -0
- {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/tests/_todo_refact/Gate/test_gate.py +0 -0
- {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/tests/_todo_refact/Htx/test_htx_p2p.py +0 -0
- {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/tests/_todo_refact/Wallet/test_agent.py +0 -0
- {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/tests/_todo_refact/Wallet/test_ex.py +0 -0
- {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/tests/_todo_refact/__init__.py +0 -0
- {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/tests/_todo_refact/_test_ex.py +0 -0
- {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/xync_client/Abc/Asset.py +0 -0
- {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/xync_client/Abc/AuthTrait.py +0 -0
- {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/xync_client/Abc/Base.py +0 -0
- {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/xync_client/Abc/InAgent.py +0 -0
- {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/xync_client/Abc/Order.py +0 -0
- {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/xync_client/Binance/__init__.py +0 -0
- {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/xync_client/Binance/binance_async.py +0 -0
- {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/xync_client/Binance/earn_api.py +0 -0
- {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/xync_client/Binance/ex.py +0 -0
- {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/xync_client/Binance/exceptions.py +0 -0
- {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/xync_client/Binance/sapi.py +0 -0
- {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/xync_client/Binance/web_c2c.py +0 -0
- {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/xync_client/BingX/__init__.py +0 -0
- {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/xync_client/BingX/agent.py +0 -0
- {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/xync_client/BingX/base.py +0 -0
- {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/xync_client/BingX/ex.py +0 -0
- {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/xync_client/BingX/req.mjs +0 -0
- {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/xync_client/BingX/sign.js +0 -0
- {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/xync_client/BingX/test/main.py +0 -0
- {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/xync_client/BitGet/__init__.py +0 -0
- {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/xync_client/BitGet/agent.py +0 -0
- {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/xync_client/BitGet/ex.py +0 -0
- {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/xync_client/BitGet/req.mjs +0 -0
- {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/xync_client/Bybit/agent.py +0 -0
- {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/xync_client/Bybit/ex.py +0 -0
- {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/xync_client/Bybit/web_earn.py +0 -0
- {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/xync_client/Bybit/web_p2p.py +0 -0
- {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/xync_client/Gate/ex.py +0 -0
- {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/xync_client/Gate/premarket.py +0 -0
- {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/xync_client/Htx/agent.py +0 -0
- {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/xync_client/Htx/earn.py +0 -0
- {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/xync_client/Htx/ex.py +0 -0
- {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/xync_client/KuCoin/pub.py +0 -0
- {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/xync_client/KuCoin/web.py +0 -0
- {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/xync_client/Okx/ex.py +0 -0
- {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/xync_client/TgWallet/asset.py +0 -0
- {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/xync_client/TgWallet/auth.py +0 -0
- {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/xync_client/TgWallet/inAgent.py +0 -0
- {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/xync_client/TgWallet/order.py +0 -0
- {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/xync_client/TgWallet/pyro.py +0 -0
- {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/xync_client/TgWallet/web.py +0 -0
- {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/xync_client/__init__.py +0 -0
- {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/xync_client/loader.py +0 -0
- {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/xync_client.egg-info/SOURCES.txt +0 -0
- {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/xync_client.egg-info/dependency_links.txt +0 -0
- {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/xync_client.egg-info/requires.txt +0 -0
- {xync_client-0.0.25.dev3 → xync_client-0.0.25.dev6}/xync_client.egg-info/top_level.txt +0 -0
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
|
|
3
3
|
import pytest
|
|
4
|
+
from xync_schema.pydantic import PmPyd, BaseAd
|
|
5
|
+
|
|
4
6
|
from xync_client.Abc.BaseTest import BaseTest
|
|
5
7
|
from xync_schema.enums import ExStatus, ExType, ExAction
|
|
6
8
|
from xync_schema.models import Ex, TestEx as ExTest
|
|
7
9
|
|
|
8
|
-
from xync_client.Abc.Base import BaseClient,
|
|
10
|
+
from xync_client.Abc.Base import BaseClient, FlatDict, MapOfIdsList
|
|
9
11
|
from xync_client.Abc.Ex import BaseExClient
|
|
10
12
|
|
|
11
13
|
|
|
12
14
|
@pytest.mark.asyncio(loop_scope="session")
|
|
13
15
|
class TestEx(BaseTest):
|
|
16
|
+
ad: dict[str, BaseAd] = {}
|
|
17
|
+
|
|
14
18
|
@pytest.fixture
|
|
15
19
|
async def clients(self) -> list[BaseClient]:
|
|
16
20
|
exs = await Ex.filter(status__gt=ExStatus.plan)
|
|
@@ -26,15 +30,16 @@ class TestEx(BaseTest):
|
|
|
26
30
|
ok = self.is_flat_dict(curs)
|
|
27
31
|
t, _ = await ExTest.update_or_create({"ok": ok}, ex=client.ex, action=ExAction.curs)
|
|
28
32
|
assert t.ok, "No curs"
|
|
29
|
-
logging.info(f"{client.ex.name}:{ExAction.curs.name} - ok")
|
|
33
|
+
logging.info(f"{client.ex.name}: {ExAction.curs.name} - ok")
|
|
30
34
|
|
|
31
35
|
# 20
|
|
32
36
|
async def test_pms(self, clients: list[BaseExClient]):
|
|
33
37
|
for client in clients:
|
|
34
|
-
pms:
|
|
35
|
-
|
|
38
|
+
pms: dict[int | str, PmPyd] = await client.pms()
|
|
39
|
+
ok = self.is_dict_of_objects(pms, PmPyd)
|
|
40
|
+
t, _ = await ExTest.update_or_create({"ok": ok}, ex=client.ex, action=ExAction.pms)
|
|
36
41
|
assert t.ok, "No pms"
|
|
37
|
-
logging.info(f"{client.ex.name}:{ExAction.pms.name} - ok")
|
|
42
|
+
logging.info(f"{client.ex.name}: {ExAction.pms.name} - ok")
|
|
38
43
|
|
|
39
44
|
# 21
|
|
40
45
|
async def test_cur_pms_map(self, clients: list[BaseExClient]):
|
|
@@ -43,7 +48,7 @@ class TestEx(BaseTest):
|
|
|
43
48
|
ok = self.is_map_of_ids(cur_pms)
|
|
44
49
|
t, _ = await ExTest.update_or_create({"ok": ok}, ex=client.ex, action=ExAction.cur_pms_map)
|
|
45
50
|
assert t.ok, "No pms for cur"
|
|
46
|
-
logging.info(f"{client.ex.name}:{ExAction.cur_pms_map.name} - ok")
|
|
51
|
+
logging.info(f"{client.ex.name}: {ExAction.cur_pms_map.name} - ok")
|
|
47
52
|
|
|
48
53
|
# 22
|
|
49
54
|
async def test_coins(self, clients: list[BaseExClient]):
|
|
@@ -52,7 +57,7 @@ class TestEx(BaseTest):
|
|
|
52
57
|
ok = self.is_flat_dict(coins)
|
|
53
58
|
t, _ = await ExTest.update_or_create({"ok": ok}, ex=client.ex, action=ExAction.coins)
|
|
54
59
|
assert t.ok, "No coins"
|
|
55
|
-
logging.info(f"{client.ex.name}:{ExAction.coins.name} - ok")
|
|
60
|
+
logging.info(f"{client.ex.name}: {ExAction.coins.name} - ok")
|
|
56
61
|
|
|
57
62
|
# 23
|
|
58
63
|
async def test_pairs(self, clients: list[BaseExClient]):
|
|
@@ -61,13 +66,23 @@ class TestEx(BaseTest):
|
|
|
61
66
|
ok = self.is_map_of_ids(pairs)
|
|
62
67
|
t, _ = await ExTest.update_or_create({"ok": ok}, ex=client.ex, action=ExAction.pairs)
|
|
63
68
|
assert t.ok, "No coins"
|
|
64
|
-
logging.info(f"{client.ex.name}:{ExAction.pairs.name} - ok")
|
|
69
|
+
logging.info(f"{client.ex.name}: {ExAction.pairs.name} - ok")
|
|
65
70
|
|
|
66
71
|
# 24
|
|
67
72
|
async def test_ads(self, clients: list[BaseExClient]):
|
|
68
73
|
for client in clients:
|
|
69
|
-
ads:
|
|
70
|
-
ok = self.
|
|
74
|
+
ads: list[BaseAd] = await client.ads("USDT", "RUB", False)
|
|
75
|
+
ok = self.is_list_of_objects(ads, BaseAd)
|
|
71
76
|
t, _ = await ExTest.update_or_create({"ok": ok}, ex=client.ex, action=ExAction.ads)
|
|
72
77
|
assert t.ok, "No ads"
|
|
73
|
-
|
|
78
|
+
self.ad[client.ex.name] = ads[0] # for further use in test_ad
|
|
79
|
+
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[client.ex.name].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")
|
|
@@ -3,24 +3,21 @@ from abc import abstractmethod
|
|
|
3
3
|
from pydantic import BaseModel
|
|
4
4
|
from tortoise.exceptions import IntegrityError
|
|
5
5
|
from x_model import HTTPException, FailReason
|
|
6
|
+
from xync_client.Abc.Ex import BaseExClient
|
|
6
7
|
|
|
7
8
|
from xync_client.Abc.Base import BaseClient
|
|
8
|
-
from xync_client.Abc.Ex import ListOfDicts
|
|
9
9
|
|
|
10
10
|
from xync_client.Abc.AuthTrait import BaseAuthTrait
|
|
11
11
|
from xync_schema.models import OrderStatus, Coin, Cur, Ad, AdStatus, Fiat, Agent, Pmex, Pmcur, Fiatex
|
|
12
|
-
from xync_schema.pydantic import FiatNew, FiatUpd
|
|
12
|
+
from xync_schema.pydantic import FiatNew, FiatUpd, BaseAd
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
class BaseAgentClient(BaseClient, BaseAuthTrait): # todo: inherit form Base or from Ex Client?
|
|
16
16
|
def __init__(self, agent: Agent):
|
|
17
17
|
self.agent: Agent = agent
|
|
18
|
-
|
|
18
|
+
self.ex_client: BaseExClient = agent.ex.client(agent) # todo: really need?
|
|
19
19
|
super().__init__(agent.ex) # , "host_p2p"
|
|
20
20
|
|
|
21
|
-
@abstractmethod
|
|
22
|
-
def pm_type_map(self, type_: Pmex) -> str: ...
|
|
23
|
-
|
|
24
21
|
@abstractmethod
|
|
25
22
|
async def start_listen(self) -> bool: ...
|
|
26
23
|
|
|
@@ -28,7 +25,7 @@ class BaseAgentClient(BaseClient, BaseAuthTrait): # todo: inherit form Base or
|
|
|
28
25
|
@abstractmethod
|
|
29
26
|
async def get_orders(
|
|
30
27
|
self, status: OrderStatus = OrderStatus.created, coin: Coin = None, cur: Cur = None, is_sell: bool = None
|
|
31
|
-
) ->
|
|
28
|
+
) -> list: ...
|
|
32
29
|
|
|
33
30
|
# 3N: [T] - Уведомление об одобрении запроса на сделку
|
|
34
31
|
@abstractmethod
|
|
@@ -50,7 +47,7 @@ class BaseAgentClient(BaseClient, BaseAuthTrait): # todo: inherit form Base or
|
|
|
50
47
|
async def request_canceled_notify(self) -> int: ... # id
|
|
51
48
|
|
|
52
49
|
# # # Fiat
|
|
53
|
-
async def
|
|
50
|
+
async def _fiat_epyd2pydin(
|
|
54
51
|
self, fiat: FiatNew | FiatUpd
|
|
55
52
|
) -> tuple[int | str, str, str, str, str]: # exid,cur,dtl,name,typ
|
|
56
53
|
if not (pmex := await Pmex.get_or_none(ex=self.agent.ex, pm_id=fiat.pm_id).prefetch_related("pm")):
|
|
@@ -60,17 +57,17 @@ class BaseAgentClient(BaseClient, BaseAuthTrait): # todo: inherit form Base or
|
|
|
60
57
|
# and then get this pm again
|
|
61
58
|
pmex = await Pmex.get(ex=self.agent.ex, pm_id=fiat.pm_id).prefetch_related("pm")
|
|
62
59
|
cur = await Cur[fiat.cur_id]
|
|
63
|
-
return pmex.exid, cur.ticker, fiat.detail, fiat.name or pmex.name, self.pm_type_map(pmex)
|
|
60
|
+
return pmex.exid, cur.ticker, fiat.detail, fiat.name or pmex.name, self.ex_client.pm_type_map(pmex)
|
|
64
61
|
|
|
65
62
|
async def fiat_new_pyd2args(
|
|
66
63
|
self, fiat: FiatNew
|
|
67
64
|
) -> tuple[int | str, str, str, str, str, None]: # exid,cur,dtl,name,typ
|
|
68
|
-
return await self.
|
|
65
|
+
return await self._fiat_epyd2pydin(fiat) + (None,)
|
|
69
66
|
|
|
70
67
|
async def fiat_upd_pyd2args(
|
|
71
68
|
self, fiat: FiatNew, fid: int
|
|
72
69
|
) -> tuple[int | str, str, str, str, str, int]: # *new_p2args,id
|
|
73
|
-
return await self.
|
|
70
|
+
return await self._fiat_epyd2pydin(fiat) + (fid,)
|
|
74
71
|
|
|
75
72
|
@property
|
|
76
73
|
@abstractmethod
|
|
@@ -83,7 +80,7 @@ class BaseAgentClient(BaseClient, BaseAuthTrait): # todo: inherit form Base or
|
|
|
83
80
|
|
|
84
81
|
# 25: Список реквизитов моих платежных методов
|
|
85
82
|
@abstractmethod
|
|
86
|
-
async def fiats(self) ->
|
|
83
|
+
async def fiats(self) -> list: ... # {fiat.exid: {fiat}}
|
|
87
84
|
|
|
88
85
|
@staticmethod
|
|
89
86
|
async def fiat_pyd2db(fiat_pyd: FiatNew | FiatUpd, uid: int, fid: int = None) -> tuple[Fiat, bool]:
|
|
@@ -117,24 +114,11 @@ class BaseAgentClient(BaseClient, BaseAuthTrait): # todo: inherit form Base or
|
|
|
117
114
|
# # # Ad
|
|
118
115
|
# 29: Список моих объявлений
|
|
119
116
|
@abstractmethod
|
|
120
|
-
async def my_ads(self, status: AdStatus = None) ->
|
|
117
|
+
async def my_ads(self, status: AdStatus = None) -> list[BaseAd]: ...
|
|
121
118
|
|
|
122
119
|
# 30: Создание объявления
|
|
123
120
|
@abstractmethod
|
|
124
|
-
async def ad_new(
|
|
125
|
-
self,
|
|
126
|
-
coin: Coin,
|
|
127
|
-
cur: Cur,
|
|
128
|
-
is_sell: bool,
|
|
129
|
-
fiats: list[Fiat],
|
|
130
|
-
amount: str,
|
|
131
|
-
price: float,
|
|
132
|
-
min_fiat: str,
|
|
133
|
-
is_float: bool = True,
|
|
134
|
-
details: str = None,
|
|
135
|
-
autoreply: str = None,
|
|
136
|
-
status: AdStatus = AdStatus.active,
|
|
137
|
-
) -> Ad.pyd(): ...
|
|
121
|
+
async def ad_new(self, ad: BaseAd) -> Ad: ...
|
|
138
122
|
|
|
139
123
|
# 31: Редактирование объявления
|
|
140
124
|
@abstractmethod
|
|
@@ -149,11 +133,11 @@ class BaseAgentClient(BaseClient, BaseAuthTrait): # todo: inherit form Base or
|
|
|
149
133
|
details: str = None,
|
|
150
134
|
autoreply: str = None,
|
|
151
135
|
status: AdStatus = None,
|
|
152
|
-
) -> Ad
|
|
136
|
+
) -> Ad: ...
|
|
153
137
|
|
|
154
138
|
# 32: Удаление
|
|
155
139
|
@abstractmethod
|
|
156
|
-
async def ad_del(self,
|
|
140
|
+
async def ad_del(self, ad_id: int) -> bool: ...
|
|
157
141
|
|
|
158
142
|
# 33: Вкл/выкл объявления
|
|
159
143
|
@abstractmethod
|
|
@@ -1,11 +1,8 @@
|
|
|
1
|
-
from abc import abstractmethod
|
|
2
|
-
|
|
3
|
-
# from asyncio import AbstractEventLoop
|
|
4
|
-
from typing import TypeGuard
|
|
5
|
-
|
|
6
1
|
import pytest
|
|
7
2
|
|
|
8
3
|
# import uvloop
|
|
4
|
+
from abc import abstractmethod
|
|
5
|
+
from typing import TypeGuard
|
|
9
6
|
from tortoise.backends.asyncpg import AsyncpgDBClient
|
|
10
7
|
from x_model import init_db
|
|
11
8
|
from xync_client.Abc.Base import BaseClient, DictOfDicts, ListOfDicts, FlatDict, MapOfIdsList
|
|
@@ -16,11 +13,9 @@ from xync_client.loader import PG_DSN
|
|
|
16
13
|
|
|
17
14
|
class BaseTest:
|
|
18
15
|
# loop: AbstractEventLoop
|
|
19
|
-
|
|
20
16
|
# @pytest.fixture(scope="session", autouse=True)
|
|
21
17
|
# def event_loop_policy(self):
|
|
22
18
|
# return uvloop.EventLoopPolicy()
|
|
23
|
-
|
|
24
19
|
@pytest.fixture(scope="session", autouse=True)
|
|
25
20
|
async def cn(self) -> AsyncpgDBClient:
|
|
26
21
|
cn: AsyncpgDBClient = await init_db(PG_DSN, models, True)
|
|
@@ -37,12 +32,24 @@ class BaseTest:
|
|
|
37
32
|
return False
|
|
38
33
|
return all(isinstance(k, int | str) and isinstance(v, dict) for k, v in dct.items())
|
|
39
34
|
|
|
35
|
+
@staticmethod
|
|
36
|
+
def is_dict_of_objects(dct: dict, typ: type, not_empty: bool = True) -> TypeGuard[dict]: # todo: Generic
|
|
37
|
+
if not_empty and not len(dct):
|
|
38
|
+
return False
|
|
39
|
+
return all(isinstance(k, int | str) and isinstance(v, typ) for k, v in dct.items())
|
|
40
|
+
|
|
40
41
|
@staticmethod
|
|
41
42
|
def is_list_of_dicts(lst: ListOfDicts, not_empty: bool = True) -> TypeGuard[ListOfDicts]:
|
|
42
43
|
if not_empty and not len(lst):
|
|
43
44
|
return False
|
|
44
45
|
return all(isinstance(el, dict) for el in lst)
|
|
45
46
|
|
|
47
|
+
@staticmethod
|
|
48
|
+
def is_list_of_objects(lst: list, typ: type, not_empty: bool = True) -> TypeGuard[list]:
|
|
49
|
+
if not_empty and not len(lst):
|
|
50
|
+
return False
|
|
51
|
+
return all(isinstance(el, typ) for el in lst)
|
|
52
|
+
|
|
46
53
|
@staticmethod
|
|
47
54
|
def is_flat_dict(dct: FlatDict, not_empty: bool = True) -> TypeGuard[FlatDict]:
|
|
48
55
|
if not_empty and not len(dct):
|
|
@@ -2,11 +2,10 @@ import logging
|
|
|
2
2
|
import re
|
|
3
3
|
from abc import abstractmethod
|
|
4
4
|
|
|
5
|
-
from xync_schema.pydantic import AdPydIn, PmPyd
|
|
5
|
+
from xync_schema.pydantic import AdPydIn, PmPyd, BaseAd, CurEpyd
|
|
6
|
+
from xync_schema.models import Ex, Coin, Cur, Pm, Pmex, Curex, Pmcur, Pmcurex, Coinex, PmexBank, Ad, Agent
|
|
6
7
|
|
|
7
|
-
from
|
|
8
|
-
|
|
9
|
-
from xync_client.Abc.Base import BaseClient, DictOfDicts, FlatDict, ListOfDicts, MapOfIdsList
|
|
8
|
+
from xync_client.Abc.Base import BaseClient, DictOfDicts, FlatDict, MapOfIdsList
|
|
10
9
|
|
|
11
10
|
|
|
12
11
|
class BaseExClient(BaseClient):
|
|
@@ -22,11 +21,14 @@ class BaseExClient(BaseClient):
|
|
|
22
21
|
"GTB Bank (Guarantee Trust Bank)": "GTBank",
|
|
23
22
|
}
|
|
24
23
|
|
|
25
|
-
def __init__(self, ex: Ex):
|
|
24
|
+
def __init__(self, ex: Ex, _: Agent = None):
|
|
26
25
|
self.ex: Ex = ex
|
|
27
26
|
self.acronyms: dict[str, str] = {} # for pm norm
|
|
28
27
|
super().__init__(ex) # , "host_p2p"
|
|
29
28
|
|
|
29
|
+
@abstractmethod
|
|
30
|
+
def pm_type_map(self, type_: Pmex) -> str: ...
|
|
31
|
+
|
|
30
32
|
# 19: Список поддерживаемых валют тейкера
|
|
31
33
|
@abstractmethod
|
|
32
34
|
async def curs(self) -> FlatDict: # {cur.exid: cur.ticker}
|
|
@@ -55,9 +57,17 @@ class BaseExClient(BaseClient):
|
|
|
55
57
|
@abstractmethod
|
|
56
58
|
async def ads(
|
|
57
59
|
self, coin_exid: str, cur_exid: str, is_sell: bool, pm_exids: list[str | int] = None, amount: int = None
|
|
58
|
-
) ->
|
|
60
|
+
) -> list[BaseAd]: # {ad.id: ad}
|
|
59
61
|
...
|
|
60
62
|
|
|
63
|
+
# 42: Объява по id
|
|
64
|
+
@abstractmethod
|
|
65
|
+
async def ad(self, ad_id: int) -> BaseAd: ...
|
|
66
|
+
|
|
67
|
+
# Преобразрование объекта объявления из формата биржи в формат xync
|
|
68
|
+
@abstractmethod
|
|
69
|
+
async def ad_epyd2pydin(self, ad: BaseAd) -> AdPydIn: ... # my_uid: for MyAd
|
|
70
|
+
|
|
61
71
|
def _pmnorm(self, s: str) -> str:
|
|
62
72
|
def get_and_remove_acro(title: str) -> str:
|
|
63
73
|
acr = "".join(word[0] for word in title.split(" ") if len(word) > 1 and word.istitle())
|
|
@@ -159,12 +169,14 @@ class BaseExClient(BaseClient):
|
|
|
159
169
|
await PmexBank.update_or_create({"name": b.name}, exid=b.exid, pmex=pmex)
|
|
160
170
|
|
|
161
171
|
# Curs
|
|
162
|
-
|
|
163
|
-
curs:
|
|
172
|
+
cur_pyds: list[CurEpyd] = await self.curs()
|
|
173
|
+
curs: dict[int | str, Cur] = {
|
|
174
|
+
cur_pyd.exid: (await Cur.update_or_create({"rate": cur_pyd.rate}, ticker=cur_pyd.ticker))[0]
|
|
175
|
+
for cur_pyd in cur_pyds
|
|
176
|
+
}
|
|
177
|
+
curexs: list[Curex] = [Curex(**c.model_dump(), cur=curs[c.exid], ex=self.ex) for c in cur_pyds]
|
|
164
178
|
# Curex
|
|
165
|
-
|
|
166
|
-
curexs = [Curex(cur=c, ex=self.ex, exid=k, minimum=cur_mins[k]) for k, c in curs.items()] # , p2p=True
|
|
167
|
-
await Curex.bulk_create(curexs, update_fields=["minimum"], on_conflict=["cur_id", "ex_id"])
|
|
179
|
+
await Curex.bulk_create(curexs, update_fields=["minimum", "rounding_scale"], on_conflict=["cur_id", "ex_id"])
|
|
168
180
|
|
|
169
181
|
cur2pms = await self.cur_pms_map()
|
|
170
182
|
# # Link PayMethods with currencies
|
|
@@ -192,7 +204,7 @@ class BaseExClient(BaseClient):
|
|
|
192
204
|
|
|
193
205
|
# Сохранение чужого объявления (с Pm-ами) в бд
|
|
194
206
|
@staticmethod
|
|
195
|
-
async def
|
|
196
|
-
ad_db, _ = await Ad.update_or_create(
|
|
197
|
-
await ad_db.pms.add(*
|
|
207
|
+
async def ad_pydin2db(ad_pydin: AdPydIn) -> Ad:
|
|
208
|
+
ad_db, _ = await Ad.update_or_create(ad_pydin.model_dump(exclude_none=True), id=ad_pydin.id)
|
|
209
|
+
await ad_db.pms.add(*ad_pydin.payMeths)
|
|
198
210
|
return ad_db
|
|
@@ -2,17 +2,30 @@ from asyncio import run
|
|
|
2
2
|
from enum import StrEnum
|
|
3
3
|
|
|
4
4
|
from x_model import init_db, HTTPException, FailReason
|
|
5
|
-
from xync_client.pyd import FiatXpyd
|
|
6
5
|
from xync_schema import models
|
|
7
6
|
|
|
8
|
-
from xync_client.TgWallet.pyd import Banks, FiatEpyd, Attrs, AttrsV2,
|
|
7
|
+
from xync_client.TgWallet.pyd import Banks, FiatEpyd, Attrs, AttrsV2, AdFullEpyd, MyAdEpyd, MyAdEpydIn, MyAdFullEpyd
|
|
9
8
|
from xync_client.loader import PG_DSN
|
|
10
9
|
from xync_schema.enums import AdStatus
|
|
11
10
|
|
|
12
11
|
from xync_client.Abc.Base import ListOfDicts
|
|
13
12
|
from xync_client.TgWallet.auth import AuthClient
|
|
14
|
-
from xync_schema.models import
|
|
15
|
-
|
|
13
|
+
from xync_schema.models import (
|
|
14
|
+
Cur,
|
|
15
|
+
Coin,
|
|
16
|
+
OrderStatus,
|
|
17
|
+
Pmex,
|
|
18
|
+
Fiat,
|
|
19
|
+
Ad,
|
|
20
|
+
Pmcur,
|
|
21
|
+
Fiatex,
|
|
22
|
+
FiatBank,
|
|
23
|
+
PmexBank,
|
|
24
|
+
Agent,
|
|
25
|
+
Curex,
|
|
26
|
+
Coinex,
|
|
27
|
+
)
|
|
28
|
+
from xync_schema.pydantic import FiatNew, FiatPydIn
|
|
16
29
|
|
|
17
30
|
from xync_client.Abc.Agent import BaseAgentClient
|
|
18
31
|
from xync_client.TgWallet.ex import ExClient
|
|
@@ -27,9 +40,6 @@ class Exceptions(StrEnum):
|
|
|
27
40
|
|
|
28
41
|
|
|
29
42
|
class AgentClient(BaseAgentClient, AuthClient):
|
|
30
|
-
def pm_type_map(self, pm: Pm) -> str:
|
|
31
|
-
return "V2" if pm.name.startswith("SBP") else "V1"
|
|
32
|
-
|
|
33
43
|
# 0: Получение ордеров в статусе status, по монете coin, в валюте coin, в направлении is_sell
|
|
34
44
|
async def orders(
|
|
35
45
|
self, status: OrderStatus = OrderStatus.created, coin: Coin = None, cur: Cur = None, is_sell: bool = None
|
|
@@ -51,7 +61,7 @@ class AgentClient(BaseAgentClient, AuthClient):
|
|
|
51
61
|
ex_client: ExClient = self.agent.ex.client()
|
|
52
62
|
ad: AdFullEpyd = await ex_client.ad(ad_id=ad_id)
|
|
53
63
|
fiats = await self.fiats()
|
|
54
|
-
fiats_pms = {fiat["paymentMethod"]["code"]: fiat["id"] for fiat in fiats
|
|
64
|
+
fiats_pms = {fiat["paymentMethod"]["code"]: fiat["id"] for fiat in fiats}
|
|
55
65
|
if not (pms := ad.get("paymentMethods")):
|
|
56
66
|
print(ad)
|
|
57
67
|
ad_pms = [pm["code"] for pm in pms]
|
|
@@ -117,20 +127,20 @@ class AgentClient(BaseAgentClient, AuthClient):
|
|
|
117
127
|
resp = await self._post("/p2p/public-api/v3/payment-details/get/by-user-id")
|
|
118
128
|
return [FiatEpyd(**fiat) for fiat in resp["data"]]
|
|
119
129
|
|
|
120
|
-
async def
|
|
130
|
+
async def _fiat_epyd2pydin(self, fiat: FiatEpyd) -> FiatPydIn:
|
|
121
131
|
if not (pmex := await Pmex.get_or_none(exid=fiat.paymentMethod.code, ex=self.agent.ex)):
|
|
122
132
|
raise HTTPException(FailReason.body, f"No Pmex {fiat.paymentMethod.code} on ex#{self.agent.ex.name}", 404)
|
|
123
133
|
if not (pmcur := await Pmcur.get_or_none(cur__ticker=fiat.currency, pm_id=pmex.pm_id)):
|
|
124
134
|
raise HTTPException(
|
|
125
135
|
FailReason.body, f"No Pmcur with cur#{fiat.currency} and pm#{fiat.paymentMethod.code}", 404
|
|
126
136
|
)
|
|
127
|
-
ftx =
|
|
137
|
+
ftx = FiatPydIn(
|
|
128
138
|
id=fiat.id,
|
|
129
139
|
detail="",
|
|
130
140
|
name=fiat.name,
|
|
131
141
|
amount=0,
|
|
132
142
|
user_id=self.agent.user_id,
|
|
133
|
-
|
|
143
|
+
pmcur=pmcur,
|
|
134
144
|
)
|
|
135
145
|
for val in fiat.attributes.values:
|
|
136
146
|
if val.name != "BANKS":
|
|
@@ -143,7 +153,7 @@ class AgentClient(BaseAgentClient, AuthClient):
|
|
|
143
153
|
# 25: Список реквизитов моих платежных методов
|
|
144
154
|
async def set_fiatexs(self) -> list[Fiatex]:
|
|
145
155
|
fiat_epyds = await self.fiats()
|
|
146
|
-
fiat_xpyds: list[
|
|
156
|
+
fiat_xpyds: list[FiatPydIn] = [await self._fiat_epyd2pydin(f) for f in fiat_epyds]
|
|
147
157
|
fxs: list[Fiatex] = []
|
|
148
158
|
for fx in fiat_xpyds:
|
|
149
159
|
df, unq = fx.args()
|
|
@@ -209,77 +219,18 @@ class AgentClient(BaseAgentClient, AuthClient):
|
|
|
209
219
|
return del_fiat
|
|
210
220
|
|
|
211
221
|
# 29: Список моих объявлений
|
|
212
|
-
async def my_ads(self, status: AdStatus = None) ->
|
|
222
|
+
async def my_ads(self, status: AdStatus = None) -> list[MyAdEpyd]:
|
|
213
223
|
mapping = {AdStatus.defActive: "INACTIVE", AdStatus.active: "ACTIVE"}
|
|
214
224
|
ads = await self._post(
|
|
215
225
|
"/p2p/public-api/v2/offer/user-own/list",
|
|
216
226
|
{"offset": 0, "limit": 20}, # , "offerType": "SALE"|"PURCHASE"
|
|
217
227
|
)
|
|
218
|
-
return (
|
|
219
|
-
[
|
|
220
|
-
MyAdEpydSale(**ad) if ad["type"] == "SALE" else MyAdEpydPurchase(**ad)
|
|
221
|
-
for ad in ads["data"]
|
|
222
|
-
if not status or (status and ad["status"] == mapping[status])
|
|
223
|
-
]
|
|
224
|
-
if status
|
|
225
|
-
else ads["data"]
|
|
226
|
-
)
|
|
228
|
+
return [MyAdEpyd(**ad) for ad in ads["data"] if not status or (status and ad["status"] == mapping[status])]
|
|
227
229
|
|
|
228
230
|
# 30: Создание объявления
|
|
229
|
-
async def ad_new(self, ad:
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
# is_sell: bool,
|
|
233
|
-
# fiats: list[Fiat],
|
|
234
|
-
# amount: str,
|
|
235
|
-
# price: str,
|
|
236
|
-
# min_fiat: str,
|
|
237
|
-
# is_float: bool = False,
|
|
238
|
-
# details: str = None,
|
|
239
|
-
# autoreply: str = None,
|
|
240
|
-
# status: AdStatus = AdStatus.active,
|
|
241
|
-
# ) -> Ad.pyd():
|
|
242
|
-
dct = {
|
|
243
|
-
"type": ad.type,
|
|
244
|
-
"initVolume": {"currencyCode": coin, "amount": amount},
|
|
245
|
-
"orderRoundingRequired": True,
|
|
246
|
-
"price": {
|
|
247
|
-
"type": "FLOATING" if is_float else "FIXED",
|
|
248
|
-
"baseCurrencyCode": coin,
|
|
249
|
-
"quoteCurrencyCode": cur,
|
|
250
|
-
"value": price,
|
|
251
|
-
},
|
|
252
|
-
"orderAmountLimits": {"min": min_fiat},
|
|
253
|
-
"paymentConfirmTimeout": "PT15M",
|
|
254
|
-
"comment": "", # todo: real recipient name
|
|
255
|
-
"paymentMethodCodes": fiats,
|
|
256
|
-
}
|
|
257
|
-
if is_sell:
|
|
258
|
-
dct["paymentDetailsIds"] = [f.id for f in fiats]
|
|
259
|
-
else:
|
|
260
|
-
pm_ids = [f.pmcur.pm_id for f in fiats]
|
|
261
|
-
pmexs = await Pmex.filter(ex=self.agent.ex, pm_id__in=pm_ids)
|
|
262
|
-
dct["paymentMethodCodes"] = [pmex.exid for pmex in pmexs]
|
|
263
|
-
|
|
264
|
-
create = await self._post(
|
|
265
|
-
"/p2p/public-api/v2/offer/create",
|
|
266
|
-
{
|
|
267
|
-
"type": typ,
|
|
268
|
-
"initVolume": {"currencyCode": coin.ticker, "amount": f"{amount}"},
|
|
269
|
-
"orderRoundingRequired": False,
|
|
270
|
-
"price": {
|
|
271
|
-
"type": "FLOATING" if is_float else "FIXED",
|
|
272
|
-
"baseCurrencyCode": coin.ticker,
|
|
273
|
-
"quoteCurrencyCode": cur,
|
|
274
|
-
"value": price,
|
|
275
|
-
},
|
|
276
|
-
"orderAmountLimits": {"min": min_fiat},
|
|
277
|
-
"paymentConfirmTimeout": "PT15M" if is_sell else "PT3H",
|
|
278
|
-
"comment": "",
|
|
279
|
-
"paymentDetailsIds": fiats,
|
|
280
|
-
},
|
|
281
|
-
)
|
|
282
|
-
return create
|
|
231
|
+
async def ad_new(self, ad: MyAdEpydIn) -> MyAdFullEpyd:
|
|
232
|
+
create = await self._post("/p2p/public-api/v2/offer/create", ad.model_dump())
|
|
233
|
+
return MyAdFullEpyd(**create["data"])
|
|
283
234
|
|
|
284
235
|
async def _get_my_ad(self, offer_id: int):
|
|
285
236
|
get_own = await self._post("/p2p/public-api/v2/offer/get-user-own/", {"offerId": offer_id})
|
|
@@ -365,21 +316,43 @@ class AgentClient(BaseAgentClient, AuthClient):
|
|
|
365
316
|
|
|
366
317
|
async def main():
|
|
367
318
|
await init_db(PG_DSN, models, True)
|
|
368
|
-
agents
|
|
319
|
+
agents: tuple[Agent, Agent, Agent] = (
|
|
320
|
+
await Agent.filter(ex_id=34, auth__isnull=False).order_by("user_id").limit(3).prefetch_related("ex")
|
|
321
|
+
)
|
|
369
322
|
my, taker, maker = agents
|
|
370
|
-
mycl
|
|
323
|
+
mycl: AgentClient = my.client()
|
|
324
|
+
tcl: AgentClient = taker.client()
|
|
325
|
+
mcl: AgentClient = maker.client()
|
|
371
326
|
|
|
372
|
-
await
|
|
327
|
+
# fiatexs = await tcl.set_fiatexs()
|
|
328
|
+
# fiatexs = await mcl.set_fiatexs()
|
|
373
329
|
await mycl.my_ads()
|
|
330
|
+
coinex = await Coinex.get(coin__ticker="USDT")
|
|
331
|
+
taker_fiats = await Fiat.filter(user_id=taker.user_id)
|
|
332
|
+
maker_fiats = await Fiat.filter(user_id=maker.user_id)
|
|
333
|
+
mf: Fiat = max(
|
|
334
|
+
{mf for mf in maker_fiats if mf.pmcur_id in {tf.pmcur_id for tf in taker_fiats}}, key=lambda x: x.amount
|
|
335
|
+
)
|
|
336
|
+
curex = await Curex.get(ex=maker.ex, cur_id=(await mf.pmcur).cur_id)
|
|
337
|
+
mfiatex = await Fiatex.get(ex=maker.ex, fiat=mf)
|
|
338
|
+
ad_in = MyAdEpydIn(
|
|
339
|
+
type="SALE",
|
|
340
|
+
initVolume={"currencyCode": coinex.exid, "amount": f"{coinex.minimum}"},
|
|
341
|
+
orderRoundingRequired=True,
|
|
342
|
+
price={
|
|
343
|
+
"type": "FLOATING",
|
|
344
|
+
"baseCurrencyCode": coinex.exid,
|
|
345
|
+
"quoteCurrencyCode": curex.exid,
|
|
346
|
+
"value": "120",
|
|
347
|
+
},
|
|
348
|
+
orderAmountLimits={"min": str(curex.minimum)},
|
|
349
|
+
paymentConfirmTimeout="PT15M",
|
|
350
|
+
comment="test1",
|
|
351
|
+
paymentDetailsIds=[mfiatex.exid],
|
|
352
|
+
)
|
|
353
|
+
ad: MyAdFullEpyd = await mcl.ad_new(ad_in)
|
|
354
|
+
await mcl.ex_client.set_ad(ad)
|
|
374
355
|
|
|
375
|
-
await Coin.get(ticker="USDT")
|
|
376
|
-
await Fiat.filter(user_id=taker.user_id)
|
|
377
|
-
await Fiat.filter(user_id=maker.user_id)
|
|
378
|
-
# fiatex = await Fiatex.get(exid=my_fiat['id']).prefetch_related('fiat')
|
|
379
|
-
# ad = await tcl.ad_new(
|
|
380
|
-
# coin=coin, cur=fiatex.fiat.currency, is_sell=True, fiats=[mf.pmcur for mf in maker_fiats], amount="10", price="120", min_fiat="500", is_float=True
|
|
381
|
-
# )
|
|
382
|
-
# ad: AdPyd = AdPyd(**ad["data"])
|
|
383
356
|
# print(ad)
|
|
384
357
|
await tcl.close(), await mcl.close()
|
|
385
358
|
|