xync-client 0.0.155.dev2__tar.gz → 0.0.160__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 (142) hide show
  1. {xync_client-0.0.155.dev2/xync_client.egg-info → xync_client-0.0.160}/PKG-INFO +2 -1
  2. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/pyproject.toml +1 -0
  3. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/tests/TestEx.py +11 -10
  4. xync_client-0.0.160/xync_client/Abc/AdLoader.py +5 -0
  5. xync_client-0.0.160/xync_client/Abc/Agent.py +491 -0
  6. xync_client-0.0.160/xync_client/Abc/Ex.py +696 -0
  7. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/xync_client/Abc/Order.py +7 -14
  8. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/xync_client/Abc/xtype.py +35 -3
  9. xync_client-0.0.160/xync_client/Bybit/InAgent.py +36 -0
  10. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/xync_client/Bybit/agent.py +509 -412
  11. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/xync_client/Bybit/etype/ad.py +47 -34
  12. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/xync_client/Bybit/etype/order.py +34 -49
  13. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/xync_client/Bybit/ex.py +20 -46
  14. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/xync_client/Bybit/order.py +14 -12
  15. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/xync_client/Htx/agent.py +111 -56
  16. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/xync_client/Htx/etype/ad.py +22 -5
  17. xync_client-0.0.160/xync_client/Htx/etype/order.py +194 -0
  18. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/xync_client/Htx/ex.py +16 -16
  19. xync_client-0.0.160/xync_client/Mexc/agent.py +268 -0
  20. xync_client-0.0.160/xync_client/Mexc/api.py +1255 -0
  21. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/xync_client/Mexc/etype/ad.py +52 -1
  22. xync_client-0.0.160/xync_client/Mexc/etype/order.py +354 -0
  23. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/xync_client/Mexc/ex.py +29 -19
  24. xync_client-0.0.160/xync_client/Okx/1.py +14 -0
  25. xync_client-0.0.160/xync_client/Okx/agent.py +39 -0
  26. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/xync_client/Okx/ex.py +8 -8
  27. xync_client-0.0.160/xync_client/Pms/Payeer/agent.py +396 -0
  28. xync_client-0.0.160/xync_client/Pms/Payeer/login.py +2 -0
  29. xync_client-0.0.160/xync_client/Pms/Payeer/trade.py +58 -0
  30. xync_client-0.0.155.dev2/xync_client/Pms/Volet/__init__.py → xync_client-0.0.160/xync_client/Pms/Volet/agent.py +1 -2
  31. xync_client-0.0.160/xync_client/__init__.py +0 -0
  32. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/xync_client/loader.py +1 -0
  33. {xync_client-0.0.155.dev2 → xync_client-0.0.160/xync_client.egg-info}/PKG-INFO +2 -1
  34. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/xync_client.egg-info/SOURCES.txt +7 -3
  35. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/xync_client.egg-info/requires.txt +1 -0
  36. xync_client-0.0.155.dev2/xync_client/Abc/AdLoader.py +0 -299
  37. xync_client-0.0.155.dev2/xync_client/Abc/Agent.py +0 -216
  38. xync_client-0.0.155.dev2/xync_client/Abc/Ex.py +0 -287
  39. xync_client-0.0.155.dev2/xync_client/Bybit/InAgent.py +0 -465
  40. xync_client-0.0.155.dev2/xync_client/Mexc/agent.py +0 -85
  41. xync_client-0.0.155.dev2/xync_client/Mexc/api.py +0 -636
  42. xync_client-0.0.155.dev2/xync_client/Mexc/etype/order.py +0 -639
  43. xync_client-0.0.155.dev2/xync_client/Pms/Payeer/__init__.py +0 -262
  44. xync_client-0.0.155.dev2/xync_client/Pms/Payeer/api.py +0 -25
  45. xync_client-0.0.155.dev2/xync_client/Pms/Payeer/login.py +0 -64
  46. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/.env.sample +0 -0
  47. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/.gitignore +0 -0
  48. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/.pre-commit-config.yaml +0 -0
  49. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/README.md +0 -0
  50. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/__init__.py +0 -0
  51. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/makefile +0 -0
  52. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/setup.cfg +0 -0
  53. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/tests/TestAgent.py +0 -0
  54. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/tests/TestAsset.py +0 -0
  55. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/tests/TestOrder.py +0 -0
  56. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/tests/_todo_refact/Binance/test_binance.py +0 -0
  57. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/tests/_todo_refact/Bybit/test_bybit.py +0 -0
  58. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/tests/_todo_refact/Bybit/test_bybit_p2p.py +0 -0
  59. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/tests/_todo_refact/Gate/test_gate.py +0 -0
  60. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/tests/_todo_refact/Wallet/test_agent.py +0 -0
  61. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/tests/_todo_refact/Wallet/test_ex.py +0 -0
  62. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/tests/_todo_refact/__init__.py +0 -0
  63. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/tests/_todo_refact/_test_ex.py +0 -0
  64. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/xync_client/Abc/Asset.py +0 -0
  65. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/xync_client/Abc/Auth.py +0 -0
  66. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/xync_client/Abc/BaseTest.py +0 -0
  67. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/xync_client/Abc/Exception.py +0 -0
  68. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/xync_client/Abc/HasAbotUid.py +0 -0
  69. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/xync_client/Abc/InAgent.py +0 -0
  70. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/xync_client/Abc/PmAgent.py +0 -0
  71. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/xync_client/Binance/__init__.py +0 -0
  72. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/xync_client/Binance/binance_async.py +0 -0
  73. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/xync_client/Binance/earn_api.py +0 -0
  74. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/xync_client/Binance/etype/ad.py +0 -0
  75. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/xync_client/Binance/etype/pm.py +0 -0
  76. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/xync_client/Binance/ex.py +0 -0
  77. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/xync_client/Binance/exceptions.py +0 -0
  78. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/xync_client/Binance/sapi.py +0 -0
  79. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/xync_client/Binance/web_c2c.py +0 -0
  80. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/xync_client/BingX/__init__.py +0 -0
  81. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/xync_client/BingX/agent.py +0 -0
  82. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/xync_client/BingX/base.py +0 -0
  83. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/xync_client/BingX/etype/ad.py +0 -0
  84. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/xync_client/BingX/etype/pm.py +0 -0
  85. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/xync_client/BingX/ex.py +0 -0
  86. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/xync_client/BingX/req.mjs +0 -0
  87. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/xync_client/BingX/sign.js +0 -0
  88. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/xync_client/BitGet/__init__.py +0 -0
  89. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/xync_client/BitGet/agent.py +0 -0
  90. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/xync_client/BitGet/etype/ad.py +0 -0
  91. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/xync_client/BitGet/ex.py +0 -0
  92. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/xync_client/BitPapa/ex.py +0 -0
  93. {xync_client-0.0.155.dev2/xync_client → xync_client-0.0.160/xync_client/Bybit/etype}/__init__.py +0 -0
  94. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/xync_client/Bybit/etype/cred.py +0 -0
  95. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/xync_client/Bybit/web_earn.py +0 -0
  96. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/xync_client/Bybit/web_p2p.py +0 -0
  97. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/xync_client/Bybit/ws.py +0 -0
  98. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/xync_client/Gate/etype/ad.py +0 -0
  99. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/xync_client/Gate/ex.py +0 -0
  100. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/xync_client/Gate/premarket.py +0 -0
  101. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/xync_client/Gmail/__init__.py +0 -0
  102. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/xync_client/Htx/earn.py +0 -0
  103. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/xync_client/Htx/etype/__init__.py +0 -0
  104. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/xync_client/Htx/etype/cred.py +0 -0
  105. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/xync_client/Htx/etype/pm.py +0 -0
  106. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/xync_client/Htx/etype/test.py +0 -0
  107. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/xync_client/KuCoin/etype/ad.py +0 -0
  108. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/xync_client/KuCoin/etype/pm.py +0 -0
  109. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/xync_client/KuCoin/ex.py +0 -0
  110. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/xync_client/KuCoin/web.py +0 -0
  111. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/xync_client/Mexc/etype/pm.py +0 -0
  112. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/xync_client/Okx/etype/ad.py +0 -0
  113. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/xync_client/Okx/etype/pm.py +0 -0
  114. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/xync_client/Pms/.gitignore +0 -0
  115. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/xync_client/Pms/Alfa/__init__.py +0 -0
  116. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/xync_client/Pms/Alfa/state.json +0 -0
  117. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/xync_client/Pms/MTS/__init__.py +0 -0
  118. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/xync_client/Pms/Ozon/__init__.py +0 -0
  119. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/xync_client/Pms/Payeer/.gitignore +0 -0
  120. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/xync_client/Pms/Sber/__init__.py +0 -0
  121. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/xync_client/Pms/Sber/utils.py +0 -0
  122. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/xync_client/Pms/Tinkoff/__init__.py +0 -0
  123. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/xync_client/Pms/Volet/_todo_req/req.mjs +0 -0
  124. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/xync_client/Pms/Volet/_todo_req/req.py +0 -0
  125. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/xync_client/Pms/Volet/api.py +0 -0
  126. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/xync_client/Pms/Volet/pl.py +0 -0
  127. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/xync_client/Pms/Xync/__main__.py +0 -0
  128. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/xync_client/Pms/Xync/ed.py +0 -0
  129. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/xync_client/Pms/Yandex/__init__.py +0 -0
  130. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/xync_client/TgWallet/agent.py +0 -0
  131. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/xync_client/TgWallet/asset.py +0 -0
  132. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/xync_client/TgWallet/auth.py +0 -0
  133. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/xync_client/TgWallet/ex.py +0 -0
  134. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/xync_client/TgWallet/inAgent.py +0 -0
  135. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/xync_client/TgWallet/order.py +0 -0
  136. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/xync_client/TgWallet/pyd.py +0 -0
  137. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/xync_client/TgWallet/pyro.py +0 -0
  138. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/xync_client/TgWallet/web.py +0 -0
  139. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/xync_client/details.py +0 -0
  140. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/xync_client/pm_unifier.py +0 -0
  141. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/xync_client.egg-info/dependency_links.txt +0 -0
  142. {xync_client-0.0.155.dev2 → xync_client-0.0.160}/xync_client.egg-info/top_level.txt +0 -0
@@ -1,11 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: xync-client
3
- Version: 0.0.155.dev2
3
+ Version: 0.0.160
4
4
  Author-email: Mike Artemiev <mixartemev@gmail.com>
5
5
  Project-URL: Homepage, https://gitlab.com/XyncNet/client
6
6
  Project-URL: Repository, https://gitlab.com/XyncNet/client
7
7
  Requires-Python: >=3.11
8
8
  Requires-Dist: asynchuobi
9
+ Requires-Dist: blackboxprotobuf
9
10
  Requires-Dist: bs4
10
11
  Requires-Dist: bybit-p2p
11
12
  Requires-Dist: google-api-python-client
@@ -8,6 +8,7 @@ dynamic = ["version"]
8
8
 
9
9
  dependencies = [
10
10
  "asynchuobi",
11
+ "blackboxprotobuf",
11
12
  "bs4",
12
13
  "bybit-p2p",
13
14
  "google-api-python-client",
@@ -4,12 +4,13 @@ import pytest
4
4
  from pyro_client.client.file import FileClient
5
5
  from x_client.aiohttp import Client as HttpClient
6
6
  from xync_schema.xtype import BaseAd
7
+ from xync_schema.models import PmEx, ExStat
7
8
 
8
9
  from xync_client.Abc.BaseTest import BaseTest
9
10
  from xync_schema.enums import ExStatus, ExType, ExAction
10
11
  from xync_schema import models
11
12
  from xync_client.Abc.Ex import BaseExClient
12
- from xync_client.loader import TOKEN
13
+ from xync_client.loader import NET_TOKEN
13
14
 
14
15
 
15
16
  @pytest.mark.asyncio(loop_scope="session")
@@ -18,7 +19,7 @@ class TestEx(BaseTest):
18
19
  async def clients(self) -> list[HttpClient]:
19
20
  exs = await models.Ex.filter(status__gt=ExStatus.plan).prefetch_related("pm_reps")
20
21
  [await ex for ex in exs if ex.typ == ExType.tg]
21
- async with FileClient(TOKEN) as b:
22
+ async with FileClient(NET_TOKEN) as b:
22
23
  b: FileClient
23
24
  clients: list[BaseExClient] = [ex.client(b) for ex in exs]
24
25
  yield clients
@@ -66,14 +67,14 @@ class TestEx(BaseTest):
66
67
  # logging.info(f"{client.ex.name}: {ExAction.curs.name} - ok")
67
68
  #
68
69
  # # 20
69
- # async def test_pms(self, clients: list[BaseExClient]):
70
- # for client in clients:
71
- # pms: dict[int | str, PmEx] = await client.pms()
72
- # ok = self.is_dict_of_objects(pms, PmEx)
73
- # t, _ = await ExStat.update_or_create({"ok": ok}, ex=client.ex, action=ExAction.pms)
74
- # assert t.ok, "No pms"
75
- # logging.info(f"{client.ex.name}: {ExAction.pms.name} - ok")
76
- #
70
+ async def test_pms(self, clients: list[BaseExClient]):
71
+ for client in clients:
72
+ pms: dict[int | str, PmEx] = await client.pms()
73
+ ok = self.is_dict_of_objects(pms, PmEx)
74
+ t, _ = await ExStat.update_or_create({"ok": ok}, ex=client.ex, action=ExAction.pms)
75
+ assert t.ok, "No pms"
76
+ logging.info(f"{client.ex.name}: {ExAction.pms.name} - ok")
77
+
77
78
  # # 21
78
79
  # async def test_cur_pms_map(self, clients: list[BaseExClient]):
79
80
  # for client in clients:
@@ -0,0 +1,5 @@
1
+ from xync_schema import models
2
+
3
+
4
+ class AdLoader:
5
+ ex: models.Ex
@@ -0,0 +1,491 @@
1
+ import logging
2
+ from abc import abstractmethod
3
+ from asyncio import create_task, sleep
4
+ from collections import defaultdict
5
+ from typing import Literal
6
+
7
+ from pydantic import BaseModel
8
+ from pyro_client.client.file import FileClient
9
+ from x_client import df_hdrs
10
+ from x_client.aiohttp import Client as HttpClient
11
+ from xync_bot import XyncBot
12
+ from xync_client.Abc.PmAgent import PmAgentClient
13
+
14
+ from xync_client.Abc.InAgent import BaseInAgentClient
15
+
16
+ from xync_client.Bybit.etype.order import TakeAdReq
17
+ from xync_schema import models
18
+ from xync_schema.models import OrderStatus, Coin, Cur, Ad, AdStatus, Actor, Agent
19
+ from xync_schema.xtype import BaseAd
20
+
21
+ from xync_client.Abc.Ex import BaseExClient
22
+ from xync_client.Abc.xtype import CredExOut, BaseOrderReq, BaseAdUpdate, AdUpd, GetAds
23
+ from xync_client.Gmail import GmClient
24
+
25
+
26
+ class BaseAgentClient(HttpClient, BaseInAgentClient):
27
+ actor: Actor
28
+ agent: Agent
29
+ bbot: XyncBot
30
+ fbot: FileClient
31
+ ex_client: BaseExClient
32
+ orders: dict[int, tuple[models.Order, BaseModel]] = {} # pending
33
+ pm_clients: dict[int, PmAgentClient] # {pm_id: PmAgentClient}
34
+ api: HttpClient
35
+ cred_x2e: dict[int, int] = {}
36
+ cred_e2x: dict[int, int] = {}
37
+
38
+ def __init__(
39
+ self,
40
+ agent: Agent, # agent.actor.person.user
41
+ ex_client: BaseExClient,
42
+ fbot: FileClient,
43
+ bbot: XyncBot,
44
+ pm_clients: dict[int, PmAgentClient] = None,
45
+ headers: dict[str, str] = df_hdrs,
46
+ cookies: dict[str, str] = None,
47
+ proxy: models.Proxy = None,
48
+ ):
49
+ self.bbot = bbot
50
+ self.fbot = fbot
51
+ self.agent: Agent = agent
52
+ self.actor: Actor = agent.actor
53
+ self.gmail = agent.actor.person.user.gmail and GmClient(agent.actor.person.user)
54
+ self.ex_client: BaseExClient = ex_client
55
+ self.pm_clients: dict[int, PmAgentClient] = defaultdict()
56
+ super().__init__(self.actor.ex.host_p2p, headers, cookies, proxy) # and proxy.str()
57
+ # start
58
+ create_task(self.start())
59
+
60
+ async def x2e_cred(self, cred_id: int) -> int: # cred.exid
61
+ if not self.cred_x2e.get(cred_id):
62
+ self.cred_x2e[cred_id] = (await models.CredEx.get(cred_id=cred_id)).exid
63
+ self.cred_e2x[self.cred_x2e[cred_id]] = cred_id
64
+ return self.cred_x2e[cred_id]
65
+
66
+ async def e2x_cred(self, exid: int) -> int: # cred.id
67
+ if not self.cred_e2x.get(exid):
68
+ self.cred_e2x[exid] = (await models.CredEx.get(exid=exid, ex=self.ex_client.ex)).cred_id
69
+ self.cred_x2e[self.cred_e2x[exid]] = exid
70
+ return self.cred_e2x[exid]
71
+
72
+ async def start(self):
73
+ if self.agent.status & 1: # race
74
+ for race in await models.Race.filter(started=True, road__ad__maker_id=self.agent.actor_id).prefetch_related(
75
+ "road__ad__pair_side__pair__cur", "road__credexs__cred"
76
+ ):
77
+ create_task(self.racing(race))
78
+ if self.agent.status & 2: # listen
79
+ await self.start_listen()
80
+
81
+ async def racing(self, race: models.Race):
82
+ pair = race.road.ad.pair_side.pair
83
+ taker_side: int = not race.road.ad.pair_side.is_sell
84
+ # конвертим наши параметры гонки в ex-овые для конкретной биржи текущего агента
85
+ coinex: models.CoinEx = await models.CoinEx.get(coin_id=pair.coin_id, ex=self.actor.ex).prefetch_related("coin")
86
+ curex: models.CurEx = await models.CurEx.get(cur_id=pair.cur_id, ex=self.actor.ex).prefetch_related("cur")
87
+ creds = [c.cred for c in race.road.credexs]
88
+ pm_ids = [pm.id for pm in race.road.ad.pms]
89
+ pmexs: list[models.PmEx] = [pmex for pm in race.road.ad.pms for pmex in pm.pmexs if pmex.ex_id == 4]
90
+ post_pm_ids = {c.cred.ovr_pm_id for c in race.road.credexs if c.cred.ovr_pm_id}
91
+ post_pmexs = set(await models.PmEx.filter(pm_id__in=post_pm_ids, ex=self.actor.ex).prefetch_related("pm"))
92
+
93
+ k = (-1) ** taker_side # on_buy=1, on_sell=-1
94
+ sleep_sec = 3 # 1 if set(pms) & {"volet"} and coinex.coin_id == 1 else 5
95
+ _lstat, volume = None, 0
96
+
97
+ # погнали цикл гонки
98
+ while self.actor.person.user.status > 0: # todo: separate agents, not whole user.activity
99
+ # подгружаем из бд обновления по текущей гонке
100
+ await race.refresh_from_db()
101
+ if not race.started: # пока выключена
102
+ await sleep(5)
103
+ continue
104
+
105
+ # конверт бд int фильтровочной суммы в float конкретной биржи
106
+ amt = race.filter_amount * 10**-curex.cur.scale if race.filter_amount else None
107
+ ceils = await self.get_ceils(coinex, curex, pmexs, 0.003, 0, amt, post_pmexs)
108
+ race.ceil = int(ceils[taker_side] * 10**curex.scale)
109
+ await race.save()
110
+
111
+ last_vol = volume
112
+ if taker_side: # гонка в стакане продажи - мы покупаем монету за ФИАТ
113
+ fiat = max(await models.Fiat.filter(cred_id__in=[c.id for c in creds]), key=lambda x: x.amount)
114
+ volume = (fiat.amount * 10**-curex.cur.scale) / (race.road.ad.price * 10**-curex.scale)
115
+ else: # гонка в стакане покупки - мы продаем МОНЕТУ за фиат
116
+ asset = await models.Asset.get(addr__actor=self.actor, addr__coin_id=coinex.coin_id)
117
+ volume = asset.free * 10**-coinex.scale
118
+ volume = str(round(volume, coinex.scale))
119
+ get_ads_req = GetAds(
120
+ coin_id=pair.coin_id, cur_id=pair.cur_id, is_sell=bool(taker_side), pm_ids=pm_ids, amount=amt, limit=50
121
+ )
122
+ try:
123
+ ads: list[Ad] = await self.ex_client.ads(get_ads_req)
124
+ except Exception:
125
+ await sleep(1)
126
+ ads: list[Ad] = await self.ads(coinex, curex, taker_side, pmexs, amt, 50, race.vm_filter, post_pmexs)
127
+
128
+ self.overprice_filter(ads, race.ceil * 10**-curex.scale, k) # обрезаем сверху все ads дороже нашего потолка
129
+
130
+ if not ads:
131
+ print(coinex.exid, curex.exid, taker_side, "no ads!")
132
+ await sleep(15)
133
+ continue
134
+ # определяем наше текущее место в уже обрезанном списке ads
135
+ if not (cur_plc := [i for i, ad in enumerate(ads) if int(ad.userId) == self.actor.exid]):
136
+ logging.warning(f"No racing in {pmexs[0].name} {'-' if taker_side else '+'}{coinex.exid}/{curex.exid}")
137
+ await sleep(15)
138
+ continue
139
+ (cur_plc,) = cur_plc # может упасть если в списке > 1 наш ad
140
+ [(await self.ex_client.cond_load(ad, race.road.ad.pair_side, True))[0] for ad in ads[:cur_plc]]
141
+ # rivals = [
142
+ # (await models.RaceStat.update_or_create({"place": plc, "price": ad.price, "premium": ad.premium}, ad=ad))[
143
+ # 0
144
+ # ]
145
+ # for plc, ad in enumerate(rads)
146
+ # ]
147
+ mad: Ad = ads.pop(cur_plc)
148
+ # if (
149
+ # not (lstat := lstat or await race.stats.order_by("-created_at").first())
150
+ # or lstat.place != cur_plc
151
+ # or lstat.price != float(mad.price)
152
+ # or set(rivals) != set(await lstat.rivals)
153
+ # ):
154
+ # lstat = await models.RaceStat.create(race=race, place=cur_plc, price=mad.price, premium=mad.premium)
155
+ # await lstat.rivals.add(*rivals)
156
+ if not ads:
157
+ await sleep(60)
158
+ continue
159
+ if not (cad := self.get_cad(ads, race.ceil * 10**-curex.scale, k, race.target_place, cur_plc)):
160
+ continue
161
+ new_price = round(float(cad.price) - k * step(mad, cad, curex.scale), curex.scale)
162
+ if (
163
+ float(mad.price) == new_price and volume == last_vol
164
+ ): # Если место уже нужное или нужная цена и так уже стоит
165
+ print(
166
+ f"{'v' if taker_side else '^'}{mad.price}",
167
+ end=f"[{race.ceil * 10**-curex.scale}+{cur_plc}] ",
168
+ flush=True,
169
+ )
170
+ await sleep(sleep_sec)
171
+ continue
172
+ if cad.priceType: # Если цена конкурента плавающая, то повышаем себе не цену, а %
173
+ new_premium = (float(mad.premium) or float(cad.premium)) - k * step(mad, cad, 2)
174
+ # if float(mad.premium) == new_premium: # Если нужный % и так уже стоит
175
+ # if mad.priceType and cur_plc != race.target_place:
176
+ # new_premium -= k * step(mad, cad, 2)
177
+ # elif volume == last_vol:
178
+ # print(end="v" if taker_side else "^", flush=True)
179
+ # await sleep(sleep_sec)
180
+ # continue
181
+ mad.premium = str(round(new_premium, 2))
182
+ mad.priceType = cad.priceType
183
+ mad.quantity = volume
184
+ mad.maxAmount = str(2_000_000 if curex.cur_id == 1 else 40_000)
185
+ # req = AdUpdateRequest.model_validate(
186
+ # {
187
+ # **mad.model_dump(),
188
+ # "price": str(round(new_price, curex.scale)),
189
+ # "paymentIds": [str(cx.exid) for cx in race.road.credexs],
190
+ # }
191
+ # )
192
+ # try:
193
+ # print(
194
+ # f"c{race.ceil * 10**-curex.scale}+{cur_plc} {coinex.coin.ticker}{'-' if taker_side else '+'}{req.price}{curex.cur.ticker}"
195
+ # f"{[pm.norm for pm in race.road.ad.pms]}{f'({req.premium}%)' if req.premium != '0' else ''} "
196
+ # f"t{race.target_place} ;",
197
+ # flush=True,
198
+ # )
199
+ # _res = self.ad_upd(req)
200
+ # except FailedRequestError as e:
201
+ # if ExcCode(e.status_code) == ExcCode.FixPriceLimit:
202
+ # if limits := re.search(
203
+ # r"The fixed price set is lower than ([0-9]+\.?[0-9]{0,2}) or higher than ([0-9]+\.?[0-9]{0,2})",
204
+ # e.message,
205
+ # ):
206
+ # req.price = limits.group(1 if taker_side else 2)
207
+ # if req.price != mad.price:
208
+ # _res = self.ad_upd(req)
209
+ # else:
210
+ # raise e
211
+ # elif ExcCode(e.status_code) == ExcCode.InsufficientBalance:
212
+ # asset = await models.Asset.get(addr__actor=self.actor, addr__coin_id=coinex.coin_id)
213
+ # req.quantity = str(round(asset.free * 10**-coinex.scale, coinex.scale))
214
+ # _res = self.ad_upd(req)
215
+ # elif ExcCode(e.status_code) == ExcCode.RareLimit:
216
+ # if not (
217
+ # sads := [
218
+ # ma
219
+ # for ma in self.my_ads(False)
220
+ # if (
221
+ # ma.currencyId == curex.exid
222
+ # and ma.tokenId == coinex.exid
223
+ # and taker_side != ma.side
224
+ # and set(ma.payments) == set([pe.exid for pe in pmexs])
225
+ # )
226
+ # ]
227
+ # ):
228
+ # logging.error(f"Need reserve Ad {'sell' if taker_side else 'buy'} {coinex.exid}/{curex.exid}")
229
+ # await sleep(90)
230
+ # continue
231
+ # self.ad_del(ad_id=int(mad.id))
232
+ # req.id = sads[0].id
233
+ # req.actionType = "ACTIVE"
234
+ # self.api.update_ad(**req.model_dump())
235
+ # logging.warning(f"Ad#{mad.id} recreated")
236
+ # # elif ExcCode(e.status_code) == ExcCode.Timestamp:
237
+ # # await sleep(3)
238
+ # else:
239
+ # raise e
240
+ # except (ReadTimeoutError, ConnectionDoesNotExistError):
241
+ # logging.warning("Connection failed. Restarting..")
242
+ await sleep(6)
243
+
244
+ async def get_books(
245
+ self,
246
+ coinex: models.CoinEx,
247
+ curex: models.CurEx,
248
+ pmexs: list[models.PmEx],
249
+ amount: int,
250
+ post_pmexs: list[models.PmEx] = None,
251
+ ) -> tuple[list[Ad], list[Ad]]:
252
+ buy: list[Ad] = await self.ads(coinex, curex, False, pmexs, amount, 40, False, post_pmexs)
253
+ sell: list[Ad] = await self.ads(coinex, curex, True, pmexs, amount, 30, False, post_pmexs)
254
+ return buy, sell
255
+
256
+ async def get_spread(
257
+ self, bb: list[Ad], sb: list[Ad], perc: float, place: int = 0
258
+ ) -> tuple[tuple[float, float], float, int] | None:
259
+ if len(bb) and len(sb):
260
+ buy_price, sell_price = float(bb[place].price), float(sb[place].price)
261
+ half_spread = (buy_price - sell_price) / (buy_price + sell_price)
262
+ if half_spread * 2 < perc:
263
+ return await self.get_spread(bb, sb, perc, place)
264
+ return (buy_price, sell_price), half_spread, place
265
+ return None
266
+
267
+ async def get_ceils(
268
+ self,
269
+ coinex: models.CoinEx,
270
+ curex: models.CurEx,
271
+ pmexs: list[models.PmEx],
272
+ min_prof=0.02,
273
+ place: int = 0,
274
+ amount: int = None,
275
+ post_pmexs: set[models.PmEx] = None,
276
+ ) -> tuple[float, float]: # todo: refact to Pairex
277
+ for pmc_id in {pmx.pm_id for pmx in pmexs} | set(self.pm_clients.keys()):
278
+ if ceils := self.pm_clients[pmc_id].get_ceils():
279
+ return ceils
280
+ bb, sb = await self.get_books(coinex, curex, pmexs, amount, post_pmexs)
281
+ perc = list(post_pmexs or pmexs)[0].pm.fee * 0.0001 + min_prof
282
+ (bf, sf), _hp, _zplace = await self.get_spread(bb, sb, perc, place)
283
+ mdl = (bf + sf) / 2 # middle price
284
+ bc, sc = mdl + mdl * (perc / 2), mdl - mdl * (perc / 2)
285
+ return bc, sc
286
+
287
+ async def mad_upd(self, mad: Ad, attrs: dict, cxids: list[str]):
288
+ if not [setattr(mad, k, v) for k, v in attrs.items() if getattr(mad, k) != v]:
289
+ print(end="v" if mad.side else "^", flush=True)
290
+ return await sleep(5)
291
+ # req = AdUpdateRequest.model_validate({**mad.model_dump(), "paymentIds": cxids})
292
+ # try:
293
+ # return self.ad_upd(req)
294
+ # except FailedRequestError as e:
295
+ # if ExcCode(e.status_code) == ExcCode.FixPriceLimit:
296
+ # if limits := re.search(
297
+ # r"The fixed price set is lower than ([0-9]+\.?[0-9]{0,2}) or higher than ([0-9]+\.?[0-9]{0,2})",
298
+ # e.message,
299
+ # ):
300
+ # return await self.mad_upd(mad, {"price": limits.group(1 if mad.side else 2)}, cxids)
301
+ # elif ExcCode(e.status_code) == ExcCode.RareLimit:
302
+ # await sleep(180)
303
+ # else:
304
+ # raise e
305
+ # except (ReadTimeoutError, ConnectionDoesNotExistError):
306
+ # logging.warning("Connection failed. Restarting..")
307
+ # print("-" if mad.side else "+", end=req.price, flush=True)
308
+ await sleep(60)
309
+
310
+ def overprice_filter(self, ads: list[Ad], ceil: float, k: Literal[-1, 1]):
311
+ # вырезаем ads с ценами выше потолка
312
+ if ads and (ceil - float(ads[0].price)) * k > 0:
313
+ if int(ads[0].userId) != self.actor.exid:
314
+ ads.pop(0)
315
+ self.overprice_filter(ads, ceil, k)
316
+
317
+ def get_cad(self, ads: list[Ad], ceil: float, k: Literal[-1, 1], target_place: int, cur_plc: int) -> Ad:
318
+ if not ads:
319
+ return None
320
+ # чью цену будем обгонять, предыдущей или слещующей объявы?
321
+ # cad: Ad = ads[place] if cur_plc > place else ads[cur_plc]
322
+ # переделал пока на жесткую установку целевого места, даже если текущее выше:
323
+ if len(ads) <= target_place:
324
+ logging.error(f"target place {target_place} not found in ads {len(ads)}-lenght list")
325
+ target_place = len(ads) - 1
326
+ cad: Ad = ads[target_place]
327
+ # а цена обгоняемой объявы не выше нашего потолка?
328
+ if (float(cad.price) - ceil) * k <= 0:
329
+ # тогда берем следующую
330
+ ads.pop(target_place)
331
+ cad = self.get_cad(ads, ceil, k, target_place, cur_plc)
332
+ # todo: добавить фильтр по лимитам min-max
333
+ return cad
334
+
335
+ # 0: Получшение ордеров в статусе status, по монете coin, в валюте coin, в направлении is_sell: bool
336
+ @abstractmethod
337
+ async def get_orders(
338
+ self, status: OrderStatus = OrderStatus.created, coin: Coin = None, cur: Cur = None, is_sell: bool = None
339
+ ) -> list: ...
340
+
341
+ # 1: [T] Запрос на старт сделки
342
+ @abstractmethod
343
+ async def order_request(self, order_req: BaseOrderReq) -> dict: ...
344
+
345
+ # async def start_order(self, order: Order) -> OrderOutClient:
346
+ # return OrderOutClient(self, order)
347
+
348
+ # 1N: [M] - Запрос мейкеру на сделку
349
+ @abstractmethod
350
+ async def order_request_ask(self) -> dict: ... # , ad: Ad, amount: float, pm: Pm, taker: Agent
351
+
352
+ # 2N: [M] - Уведомление об отмене запроса на сделку
353
+ @abstractmethod
354
+ async def request_canceled_notify(self) -> int: ... # id
355
+
356
+ # # # Cred
357
+ @property
358
+ @abstractmethod
359
+ def fiat_pyd(self) -> BaseModel.__class__: ...
360
+
361
+ @abstractmethod
362
+ def fiat_args2pyd(
363
+ self, exid: int | str, cur: str, detail: str, name: str, fid: int, typ: str, extra=None
364
+ ) -> fiat_pyd: ...
365
+
366
+ # 25: Список реквизитов моих платежных методов
367
+ @abstractmethod
368
+ async def creds(self) -> list[CredExOut]: ... # {credex.exid: {cred}}
369
+
370
+ # Создание реквизита на бирже
371
+ async def cred_new(self, cred: models.Cred) -> models.CredEx: ...
372
+
373
+ # await models.Actor.get_or_create({"name": cred.exid}, ex=self.ex_client.ex, exid=self.agent.actor.exid)
374
+ # cred_db: Cred = (await self.cred_pyd2db(cred, self.agent.user_id))[0]
375
+ # if not (credex := models.CredEx.get_or_none(cred=cred_db, ex=self.agent.ex)):
376
+ # credex, _ = models.CredEx.update_or_create({}, cred=cred_db, ex=self.agent.ex)
377
+ # return credex
378
+
379
+ # 27: Редактирование реквизита моего платежного метода
380
+ @abstractmethod
381
+ async def cred_upd(self, cred: models.Cred, exid: int) -> models.CredEx: ...
382
+
383
+ # 28: Удаление реквизита моего платежного метода
384
+ @abstractmethod
385
+ async def cred_del(self, exid: int) -> int: ...
386
+
387
+ # # # Ad
388
+ # 29: Список моих объявлений
389
+ @abstractmethod
390
+ async def my_ads(self, status: AdStatus = None) -> list[BaseAd]: ...
391
+
392
+ @abstractmethod
393
+ async def x2e_req_ad_upd(self, xreq: AdUpd) -> BaseAdUpdate: ...
394
+
395
+ # 30: Создание объявления
396
+ @abstractmethod
397
+ async def ad_new(self, ad: BaseAd) -> Ad: ...
398
+
399
+ async def ad_upd(self, xreq: AdUpd) -> Ad:
400
+ xreq.credexs = await models.CredEx.filter(
401
+ ex_id=self.actor.ex_id,
402
+ cred__pmcur__pm_id__in=xreq.pm_ids,
403
+ cred__pmcur__cur_id=xreq.cur_id,
404
+ cred__person_id=self.actor.person_id,
405
+ ).prefetch_related("cred__pmcur")
406
+ # xreq.credexs = credexs
407
+ ereq = await self.x2e_req_ad_upd(xreq)
408
+ return await self._ad_upd(ereq)
409
+
410
+ # 31: Редактирование объявления
411
+ @abstractmethod
412
+ async def _ad_upd(self, ad: BaseAdUpdate) -> Ad: ...
413
+
414
+ # 32: Удаление
415
+ @abstractmethod
416
+ async def ad_del(self, ad_id: int) -> bool: ...
417
+
418
+ # 33: Вкл/выкл объявления
419
+ @abstractmethod
420
+ async def ad_switch(self, offer_id: int, active: bool) -> bool: ...
421
+
422
+ # 34: Вкл/выкл всех объявлений
423
+ @abstractmethod
424
+ async def ads_switch(self, active: bool) -> bool: ...
425
+
426
+ # # # User
427
+ # 35: Получить объект юзера по его ид
428
+ @abstractmethod
429
+ async def get_user(self, user_id) -> dict: ...
430
+
431
+ # 36: Отправка сообщения юзеру с приложенным файлом
432
+ @abstractmethod
433
+ async def send_user_msg(self, msg: str, file=None) -> bool: ...
434
+
435
+ # 37: (Раз)Блокировать юзера
436
+ @abstractmethod
437
+ async def block_user(self, is_blocked: bool = True) -> bool: ...
438
+
439
+ # 38: Поставить отзыв юзеру
440
+ @abstractmethod
441
+ async def rate_user(self, positive: bool) -> bool: ...
442
+
443
+ # 39: Балансы моих монет
444
+ @abstractmethod
445
+ async def my_assets(self) -> dict: ...
446
+
447
+ @abstractmethod
448
+ async def take_ad(self, req: TakeAdReq): ...
449
+
450
+ # Сохранение объявления (с Pm/Cred-ами) в бд
451
+ # async def ad_pydin2db(self, ad_pydin: AdSaleIn | AdBuyIn) -> Ad:
452
+ # ad_db = await self.ex_client.ad_pydin2db(ad_pydin)
453
+ # await ad_db.credexs.add(*getattr(ad_pydin, "credexs_", []))
454
+ # await ad_db.pmexs.add(*getattr(ad_pydin, "pmexs_", []))
455
+ # return ad_db
456
+
457
+ # @staticmethod
458
+ # async def cred_e2db(cred_in: BaseUpd, banks: list[str] = None) -> bool:
459
+ # cred_db, _ = await models.Cred.update_or_create(**cred_in.df_unq())
460
+ # credex_in = models.CredEx.validate({"exid": cred_in.id, "cred_id": cred_db.id})
461
+ # credex_db, _ = await models.CredEx.update_or_create(**credex_in.df_unq())
462
+ # if banks: # only for SBP
463
+ # await cred_db.banks.add(*[await PmExBank.get(exid=b) for b in banks])
464
+ # return True
465
+
466
+ @abstractmethod
467
+ async def _start_listen(self): ...
468
+
469
+ @abstractmethod
470
+ async def load_pending_orders(self): ...
471
+
472
+ async def start_listen(self):
473
+ create_task(self._start_listen())
474
+ await self.load_pending_orders()
475
+
476
+
477
+ def step_is_need(mad, cad) -> bool:
478
+ # todo: пока не решен непонятный кейс, почему то конкурент по всем параметрам слабже, но в списке ранжируется выше.
479
+ # текущая версия: recentExecuteRate округляется до целого, но на бэке байбита его дробная часть больше
480
+ return (
481
+ bool(set(cad.authTag) & {"VA2", "BA"})
482
+ or cad.recentExecuteRate > mad.recentExecuteRate
483
+ or (
484
+ cad.recentExecuteRate
485
+ == mad.recentExecuteRate # and cad.finishNum > mad.finishNum # пока прибавляем для равных
486
+ )
487
+ )
488
+
489
+
490
+ def step(mad, cad, scale: int = 2) -> float:
491
+ return float(int(step_is_need(mad, cad)) * 10**-scale).__round__(scale)