xync-client 0.0.147__py3-none-any.whl → 0.0.148__py3-none-any.whl

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.

Potentially problematic release.


This version of xync-client might be problematic. Click here for more details.

@@ -0,0 +1,285 @@
1
+ import logging
2
+ import re
3
+ from asyncio import sleep
4
+ from collections import defaultdict
5
+ from difflib import SequenceMatcher
6
+
7
+ from xync_schema import models
8
+ from xync_schema.xtype import BaseAd
9
+
10
+
11
+ class AdLoader:
12
+ ex: models.Ex
13
+ all_conds: dict[int, tuple[str, set[int]]] = {}
14
+ cond_sims: dict[int, int] = defaultdict(set)
15
+ rcond_sims: dict[int, set[int]] = defaultdict(set) # backward
16
+ tree: dict = {}
17
+
18
+ async def old_conds_load(self):
19
+ # пока не порешали рейс-кондишн, очищаем сиротские условия при каждом запуске
20
+ # [await c.delete() for c in await Cond.filter(ads__isnull=True)]
21
+ self.all_conds = {
22
+ c.id: (c.raw_txt, {a.maker.exid for a in c.ads})
23
+ for c in await models.Cond.all().prefetch_related("ads__maker")
24
+ }
25
+ for curr, old in await models.CondSim.filter().values_list("cond_id", "cond_rel_id"):
26
+ self.cond_sims[curr] = old
27
+ self.rcond_sims[old] |= {curr}
28
+
29
+ self.build_tree()
30
+ a = set()
31
+
32
+ def check_tree(tre):
33
+ for p, c in tre.items():
34
+ a.add(p)
35
+ check_tree(c)
36
+
37
+ for pr, ch in self.tree.items():
38
+ check_tree(ch)
39
+ if ct := set(self.tree.keys()) & a:
40
+ logging.exception(f"cycle cids: {ct}")
41
+
42
+ async def person_name_update(self, name: str, exid: int) -> models.Person:
43
+ if actor := await models.Actor.get_or_none(exid=exid, ex=self.ex).prefetch_related("person"):
44
+ actor.person.name = name
45
+ await actor.person.save()
46
+ return actor.person
47
+ # return await models.Person.create(note=f'{actor.ex_id}:{actor.exid}:{name}') # no person for just ads with no orders
48
+ raise ValueError(f"Agent #{exid} not found")
49
+
50
+ async def ad_load(
51
+ self,
52
+ pad: BaseAd,
53
+ cid: int = None,
54
+ ps: models.PairSide = None,
55
+ maker: models.Actor = None,
56
+ coinex: models.CoinEx = None,
57
+ curex: models.CurEx = None,
58
+ rname: str = None,
59
+ pms_from_cond: bool = False,
60
+ ) -> models.Ad:
61
+ if not maker:
62
+ if not (maker := await models.Actor.get_or_none(exid=pad.userId, ex=self.ex)):
63
+ person = await models.Person.create(name=rname, note=f"{self.ex.id}:{pad.userId}:{pad.nickName}")
64
+ maker = await models.Actor.create(name=pad.nickName, person=person, exid=pad.userId, ex=self.ex)
65
+ if rname:
66
+ await self.person_name_update(rname, int(pad.userId))
67
+ ps = ps or await models.PairSide.get_or_none(
68
+ is_sell=pad.side,
69
+ pair__coin__ticker=pad.tokenId,
70
+ pair__cur__ticker=pad.currencyId,
71
+ ).prefetch_related("pair")
72
+ # if not ps or not ps.pair:
73
+ # ... # THB/USDC: just for initial filling
74
+ ad_upd = models.Ad.validate(pad.model_dump(by_alias=True))
75
+ cur_scale = 10 ** (curex or await models.CurEx.get(coin_id=ps.pair.cur_id, ex=self.ex)).scale
76
+ coin_scale = 10 ** (coinex or await models.CoinEx.get(coin_id=ps.pair.coin_id, ex=self.ex)).scale
77
+ df_unq = ad_upd.df_unq(
78
+ maker_id=maker.id,
79
+ pair_side_id=ps.id,
80
+ amount=int(float(pad.quantity) * float(pad.price) * cur_scale),
81
+ quantity=int(float(pad.quantity) * coin_scale),
82
+ min_fiat=int(float(pad.minAmount) * cur_scale),
83
+ max_fiat=pad.maxAmount and int(float(pad.maxAmount) * cur_scale),
84
+ price=int(float(pad.price) * cur_scale),
85
+ premium=int(float(pad.premium) * 100),
86
+ cond_id=cid,
87
+ )
88
+ ad_db, _ = await models.Ad.update_or_create(**df_unq)
89
+ if not pms_from_cond:
90
+ await ad_db.pms.add(*(await models.Pm.filter(pmexs__ex=self.ex, pmexs__exid__in=pad.payments)))
91
+ return ad_db
92
+
93
+ async def cond_load( # todo: refact from Bybit Ad format to universal
94
+ self,
95
+ ad: BaseAd,
96
+ ps: models.PairSide = None,
97
+ force: bool = False,
98
+ rname: str = None,
99
+ coinex: models.CoinEx = None,
100
+ curex: models.CurEx = None,
101
+ pms_from_cond: bool = False,
102
+ ) -> tuple[models.Ad, bool]:
103
+ _sim, cid = None, None
104
+ ad_db = await models.Ad.get_or_none(exid=ad.id, maker__ex=self.ex).prefetch_related("cond")
105
+ # если точно такое условие уже есть в бд
106
+ if not (cleaned := clean(ad.remark)) or (cid := {oc[0]: ci for ci, oc in self.all_conds.items()}.get(cleaned)):
107
+ # и объява с таким ид уже есть, но у нее другое условие
108
+ if ad_db and ad_db.cond_id != cid:
109
+ # то обновляем ид ее условия
110
+ ad_db.cond_id = cid
111
+ await ad_db.save()
112
+ logging.info(f"{ad.nickName} upd cond#{ad_db.cond_id}->{cid}")
113
+ # old_cid = ad_db.cond_id # todo: solve race-condition, а пока что очищаем при каждом запуске
114
+ # if not len((old_cond := await Cond.get(id=old_cid).prefetch_related('ads')).ads):
115
+ # await old_cond.delete()
116
+ # logging.warning(f"Cond#{old_cid} deleted!")
117
+ return (
118
+ ad_db
119
+ or force
120
+ and await self.ad_load(
121
+ ad, cid, ps, coinex=coinex, curex=curex, rname=rname, pms_from_cond=pms_from_cond
122
+ )
123
+ ), False
124
+ # если эта объява в таким ид уже есть в бд, но с другим условием (или без), а текущего условия еще нет в бд
125
+ if ad_db:
126
+ await ad_db.fetch_related("cond__ads", "maker")
127
+ if not ad_db.cond_id or (
128
+ # у измененного условия этой объявы есть другие объявы?
129
+ (rest_ads := set(ad_db.cond.ads) - {ad_db})
130
+ and
131
+ # другие объявы этого условия принадлежат другим юзерам
132
+ {ra.maker_id for ra in rest_ads} - {ad_db.maker_id}
133
+ ):
134
+ # создадим новое условие и присвоим его только текущей объяве
135
+ cid = await self.cond_new(cleaned, {int(ad.userId)})
136
+ ad_db.cond_id = cid
137
+ await ad_db.save()
138
+
139
+ return ad_db, True
140
+ # а если других объяв со старым условием этой обявы нет, либо они все этого же юзера
141
+ # обновляем условие (в тч во всех ЕГО объявах)
142
+ ad_db.cond.last_ver = ad_db.cond.raw_txt
143
+ ad_db.cond.raw_txt = cleaned
144
+ await ad_db.cond.save()
145
+ await self.cond_upd(ad_db.cond, {ad_db.maker.exid})
146
+ # и подправим коэфициенты похожести нового текста
147
+ await self.fix_rel_sims(ad_db.cond_id, cleaned)
148
+ return ad_db, False
149
+
150
+ cid = await self.cond_new(cleaned, {int(ad.userId)})
151
+ return await self.ad_load(
152
+ ad, cid, ps, coinex=coinex, curex=curex, rname=rname, pms_from_cond=pms_from_cond
153
+ ), True
154
+
155
+ async def cond_new(self, txt: str, uids: set[int]) -> int:
156
+ new_cond, _ = await models.Cond.update_or_create(raw_txt=txt)
157
+ # и максимально похожую связь для нового условия (если есть >= 60%)
158
+ await self.cond_upd(new_cond, uids)
159
+ return new_cond.id
160
+
161
+ async def cond_upd(self, cond: models.Cond, uids: set[int]):
162
+ self.all_conds[cond.id] = cond.raw_txt, uids
163
+ # и максимально похожую связь для нового условия (если есть >= 60%)
164
+ old_cid, sim = await self.cond_get_max_sim(cond.id, cond.raw_txt, uids)
165
+ await self.actual_sim(cond.id, old_cid, sim)
166
+
167
+ def find_in_tree(self, cid: int, old_cid: int) -> bool:
168
+ if p := self.cond_sims.get(old_cid):
169
+ if p == cid:
170
+ return True
171
+ return self.find_in_tree(cid, p)
172
+ return False
173
+
174
+ async def cond_get_max_sim(self, cid: int, txt: str, uids: set[int]) -> tuple[int | None, int | None]:
175
+ # находим все старые тексты похожие на 90% и более
176
+ if len(txt) < 15:
177
+ return None, None
178
+ sims: dict[int, int] = {}
179
+ for old_cid, (old_txt, old_uids) in self.all_conds.items():
180
+ if len(old_txt) < 15 or uids == old_uids:
181
+ continue
182
+ elif not self.can_add_sim(cid, old_cid):
183
+ continue
184
+ if sim := get_sim(txt, old_txt):
185
+ sims[old_cid] = sim
186
+ # если есть, берем самый похожий из них
187
+ if sims:
188
+ old_cid, sim = max(sims.items(), key=lambda x: x[1])
189
+ await sleep(0.3)
190
+ return old_cid, sim
191
+ return None, None
192
+
193
+ def can_add_sim(self, cid: int, old_cid: int) -> bool:
194
+ if cid == old_cid:
195
+ return False
196
+ elif self.cond_sims.get(cid) == old_cid:
197
+ return False
198
+ elif self.find_in_tree(cid, old_cid):
199
+ return False
200
+ elif self.cond_sims.get(old_cid) == cid:
201
+ return False
202
+ elif cid in self.rcond_sims.get(old_cid, {}):
203
+ return False
204
+ elif old_cid in self.rcond_sims.get(cid, {}):
205
+ return False
206
+ return True
207
+
208
+ async def fix_rel_sims(self, cid: int, new_txt: str):
209
+ for rel_sim in await models.CondSim.filter(cond_rel_id=cid).prefetch_related("cond"):
210
+ if sim := get_sim(new_txt, rel_sim.cond.raw_txt):
211
+ rel_sim.similarity = sim
212
+ await rel_sim.save()
213
+ else:
214
+ await rel_sim.delete()
215
+
216
+ async def actual_cond(self):
217
+ for curr, old in await models.CondSim.all().values_list("cond_id", "cond_rel_id"):
218
+ self.cond_sims[curr] = old
219
+ self.rcond_sims[old] |= {curr}
220
+ for cid, (txt, uids) in self.all_conds.items():
221
+ old_cid, sim = await self.cond_get_max_sim(cid, txt, uids)
222
+ await self.actual_sim(cid, old_cid, sim)
223
+ # хз бля чо это ваще
224
+ # for ad_db in await models.Ad.filter(direction__pairex__ex=self.ex).prefetch_related("cond", "maker"):
225
+ # ad = Ad(id=str(ad_db.exid), userId=str(ad_db.maker.exid), remark=ad_db.cond.raw_txt)
226
+ # await self.cond_upsert(ad, force=True)
227
+
228
+ async def actual_sim(self, cid: int, old_cid: int, sim: int):
229
+ if not sim:
230
+ return
231
+ if old_sim := await models.CondSim.get_or_none(cond_id=cid):
232
+ if old_sim.cond_rel_id != old_cid:
233
+ if sim > old_sim.similarity:
234
+ logging.warning(f"R {cid}: {old_sim.similarity}->{sim} ({old_sim.cond_rel_id}->{old_cid})")
235
+ await old_sim.update_from_dict({"similarity": sim, "old_rel_id": old_cid}).save()
236
+ self._cond_sim_upd(cid, old_cid)
237
+ elif sim != old_sim.similarity:
238
+ logging.info(f"{cid}: {old_sim.similarity}->{sim}")
239
+ await old_sim.update_from_dict({"similarity": sim}).save()
240
+ else:
241
+ await models.CondSim.create(cond_id=cid, cond_rel_id=old_cid, similarity=sim)
242
+ self._cond_sim_upd(cid, old_cid)
243
+
244
+ def _cond_sim_upd(self, cid: int, old_cid: int):
245
+ if old_old_cid := self.cond_sims.get(cid): # если старый cid уже был в дереве:
246
+ self.rcond_sims[old_old_cid].remove(cid) # удаляем из обратного
247
+ self.cond_sims[cid] = old_cid # а в прямом он автоматом переопределится, даже если и был
248
+ self.rcond_sims[old_cid] |= {cid} # ну и в обратное добавим новый
249
+
250
+ def build_tree(self):
251
+ set(self.cond_sims.keys()) | set(self.cond_sims.values())
252
+ tree = defaultdict(dict)
253
+ # Группируем родителей по детям
254
+ for child, par in self.cond_sims.items():
255
+ tree[par] |= {child: {}} # todo: make from self.rcond_sim
256
+
257
+ # Строим дерево снизу вверх
258
+ def subtree(node):
259
+ if not node:
260
+ return node
261
+ for key in node:
262
+ subnode = tree.pop(key, {})
263
+ d = subtree(subnode)
264
+ node[key] |= d # actual tree rebuilding here!
265
+ return node # todo: refact?
266
+
267
+ # Находим корни / без родителей
268
+ roots = set(self.cond_sims.values()) - set(self.cond_sims.keys())
269
+ for root in roots:
270
+ _ = subtree(tree[root])
271
+
272
+ self.tree = tree
273
+
274
+
275
+ def get_sim(s1, s2) -> int:
276
+ sim = int((SequenceMatcher(None, s1, s2).ratio() - 0.6) * 10_000)
277
+ return sim if sim > 0 else 0
278
+
279
+
280
+ def clean(s) -> str:
281
+ clear = r"[^\w\s.,!?;:()\-]"
282
+ repeat = r"(.)\1{2,}"
283
+ s = re.sub(clear, "", s).lower()
284
+ s = re.sub(repeat, r"\1", s)
285
+ return s.replace("\n\n", "\n").replace(" ", " ").strip(" \n/.,!?-")
xync_client/Abc/Ex.py CHANGED
@@ -11,11 +11,12 @@ from xync_schema import models
11
11
  from xync_schema.enums import FileType
12
12
  from xync_schema.xtype import CurEx, CoinEx, BaseAd, BaseAdIn
13
13
 
14
+ from xync_client.Abc.AdLoader import AdLoader
14
15
  from xync_client.Abc.xtype import PmEx, MapOfIdsList
15
16
  from xync_client.pm_unifier import PmUnifier, PmUni
16
17
 
17
18
 
18
- class BaseExClient(HttpClient):
19
+ class BaseExClient(HttpClient, AdLoader):
19
20
  cur_map: dict[int, str] = {}
20
21
  unifier_class: type = PmUnifier
21
22
  logo_pre_url: str
@@ -72,6 +73,7 @@ class BaseExClient(HttpClient):
72
73
  pm_exids: list[str | int] = None,
73
74
  amount: int = None,
74
75
  lim: int = None,
76
+ vm_filter: bool = False,
75
77
  ) -> list[BaseAd]: # {ad.id: ad}
76
78
  ...
77
79
 
@@ -250,14 +252,14 @@ class BaseExClient(HttpClient):
250
252
  return True
251
253
 
252
254
  # Сохранение чужого объявления (с Pm-ами) в бд
253
- async def ad_pydin2db(self, ad_pydin: BaseAdIn) -> models.Ad:
254
- dct = ad_pydin.model_dump()
255
- dct["exid"] = dct.pop("id")
256
- ad_in = models.Ad.validate(dct)
257
- ad_db, _ = await models.Ad.update_or_create(**ad_in.df_unq())
258
- await ad_db.credexs.add(*getattr(ad_pydin, "credexs_", []))
259
- await ad_db.pmexs.add(*getattr(ad_pydin, "pmexs_", []))
260
- return ad_db
255
+ # async def ad_pydin2db(self, ad_pydin: BaseAdIn) -> models.Ad:
256
+ # dct = ad_pydin.model_dump()
257
+ # dct["exid"] = dct.pop("id")
258
+ # ad_in = models.Ad.validate(dct)
259
+ # ad_db, _ = await models.Ad.update_or_create(**ad_in.df_unq())
260
+ # await ad_db.credexs.add(*getattr(ad_pydin, "credexs_", []))
261
+ # await ad_db.pmexs.add(*getattr(ad_pydin, "pmexs_", []))
262
+ # return ad_db
261
263
 
262
264
  async def file_upsert(self, url: str, ss: ClientSession = None) -> models.File:
263
265
  if not (file := await models.File.get_or_none(name__startswith=url.split("?")[0])):
@@ -2,7 +2,6 @@ import asyncio
2
2
  import logging
3
3
  import re
4
4
  from asyncio import sleep, gather
5
- from collections import defaultdict
6
5
  from datetime import datetime, timedelta, timezone
7
6
  from difflib import SequenceMatcher
8
7
  from enum import IntEnum
@@ -18,7 +17,7 @@ from payeer_api import PayeerAPI
18
17
  from pyro_client.client.file import FileClient
19
18
  from tortoise import BaseDBAsyncClient
20
19
  from tortoise.exceptions import IntegrityError
21
- from tortoise.expressions import F, Q
20
+ from tortoise.expressions import Q
22
21
  from tortoise.functions import Count
23
22
  from tortoise.signals import post_save
24
23
  from urllib3.exceptions import ReadTimeoutError
@@ -28,7 +27,7 @@ from xync_bot import XyncBot
28
27
  from xync_schema import models
29
28
  from xync_schema.enums import OrderStatus
30
29
 
31
- from xync_schema.models import Actor, Cond, CondSim, PmCur, PairSide, Agent
30
+ from xync_schema.models import Actor, PmCur, Agent
32
31
 
33
32
  from xync_client.Abc.Agent import BaseAgentClient
34
33
  from xync_client.Abc.xtype import FlatDict, BaseOrderReq
@@ -85,16 +84,12 @@ class AgentClient(BaseAgentClient): # Bybit client
85
84
  "actionType": "MODIFY",
86
85
  "securityRiskToken": "",
87
86
  }
88
- all_conds: dict[int, tuple[str, set[int]]] = {}
89
- cond_sims: dict[int, int] = defaultdict(set)
90
- rcond_sims: dict[int, set[int]] = defaultdict(set) # backward
91
- tree: dict = {}
92
87
 
93
88
  def __init__(self, agent: Agent, fbot: FileClient, bbot: XyncBot, **kwargs):
94
89
  super().__init__(agent, fbot, bbot, **kwargs)
95
90
  self.api = P2P(testnet=False, api_key=agent.auth["key"], api_secret=agent.auth["sec"])
96
- self.hist: dict = None
97
- self.completed_orders: list[int] = None
91
+ self.hist: dict | None = None
92
+ self.completed_orders: list[int] | None = None
98
93
 
99
94
  """ Private METHs"""
100
95
 
@@ -118,12 +113,8 @@ class AgentClient(BaseAgentClient): # Bybit client
118
113
  else:
119
114
  return logging.exception(method1)
120
115
 
121
- async def get_payment_method(self, fiat_id: int = None) -> dict:
122
- list_methods = self.creds()
123
- if fiat_id:
124
- fiat = [m for m in list_methods if m["id"] == fiat_id][0]
125
- return fiat
126
- return list_methods[1]
116
+ def get_payment_method(self, fiat_id: int) -> CredEpyd:
117
+ return self.creds()[fiat_id]
127
118
 
128
119
  def creds(self) -> dict[int, CredEpyd]:
129
120
  data = self.api.get_user_payment_types()
@@ -206,13 +197,13 @@ class AgentClient(BaseAgentClient): # Bybit client
206
197
  # 27
207
198
  async def fiat_upd(self, fiat_id: int, detail: str, name: str = None) -> dict:
208
199
  fiat = self.get_payment_method(fiat_id)
209
- fiat["realName"] = name
210
- fiat["accountNo"] = detail
211
- result = await self._post("/fiat/otc/user/payment/new_update", fiat)
200
+ fiat.realName = name
201
+ fiat.accountNo = detail
202
+ result = await self._post("/fiat/otc/user/payment/new_update", fiat.model_dump(exclude_none=True))
212
203
  srt = result["result"]["securityRiskToken"]
213
204
  await self._check_2fa(srt)
214
- fiat["securityRiskToken"] = srt
215
- result2 = await self._post("/fiat/otc/user/payment/new_update", fiat)
205
+ fiat.securityRiskToken = srt
206
+ result2 = await self._post("/fiat/otc/user/payment/new_update", fiat.model_dump(exclude_none=True))
216
207
  return result2
217
208
 
218
209
  # 28
@@ -244,10 +235,6 @@ class AgentClient(BaseAgentClient): # Bybit client
244
235
  cnx.exid, crx.exid, is_sell, [pmex.exid for pmex in pmxs or []], amount, lim, vm_filter
245
236
  )
246
237
 
247
- def online_ads(self) -> str:
248
- online = self._get("/fiat/otc/maker/work-config/get")
249
- return online["result"]["workStatus"]
250
-
251
238
  @staticmethod
252
239
  def get_rate(list_ads: list) -> float:
253
240
  ads = [ad for ad in list_ads if set(ad["payments"]) - {"5", "51"}]
@@ -264,7 +251,7 @@ class AgentClient(BaseAgentClient): # Bybit client
264
251
  ads = self.my_ads(True)
265
252
  if not active:
266
253
  ads += self.my_ads(False)
267
- res = [await self.ad_create(ad, actor=self.actor) for ad in ads]
254
+ res = [await self.ex_client.ad_load(ad, maker=self.actor) for ad in ads]
268
255
  res = [await models.MyAd.update_or_create(ad=ad) for ad in res]
269
256
  return len(res)
270
257
 
@@ -494,12 +481,14 @@ class AgentClient(BaseAgentClient): # Bybit client
494
481
  maker_name = order.buyerRealName, order.sellerRealName
495
482
  im_maker = int(order.makerUserId == order.userId)
496
483
  taker_id = (order.userId, order.targetUserId)[im_maker]
497
- taker_person = await self.person_upsert(maker_name[::-1][ad.side], taker_id)
484
+ taker_person = await self.ex_client.person_name_update(maker_name[::-1][ad.side], taker_id)
498
485
  seller_person = (
499
- self.actor.person if order.side else await self.person_upsert(order.sellerRealName, int(order.targetUserId))
486
+ self.actor.person
487
+ if order.side
488
+ else await self.ex_client.person_name_update(order.sellerRealName, int(order.targetUserId))
500
489
  )
501
490
  taker_nick = (self.actor.name, order.targetNickName)[im_maker] # todo: check
502
- ad_db, cond_isnew = await self.cond_upsert(ad, maker_name[ad.side], force=True)
491
+ ad_db, cond_isnew = await self.ex_client.cond_load(ad, force=True, rname=maker_name[ad.side])
503
492
  if not ad_db:
504
493
  ...
505
494
  ecredex: CredEpyd = order.confirmedPayTerm
@@ -602,7 +591,7 @@ class AgentClient(BaseAgentClient): # Bybit client
602
591
  buyer_person = (
603
592
  self.actor.person
604
593
  if not order.side
605
- else await self.person_upsert(order.buyerRealName, int(order.targetUserId))
594
+ else await self.ex_client.person_name_update(order.buyerRealName, int(order.targetUserId))
606
595
  )
607
596
  ts = [t for t in tsa if floor(fa := float(order.amount)) <= float(t["creditedAmount"]) <= round(fa)]
608
597
  if len(ts) != 1:
@@ -755,7 +744,7 @@ class AgentClient(BaseAgentClient): # Bybit client
755
744
  pmexs: list[models.PmEx] = await models.PmEx.filter(pm_id__in=pm_ids, ex=self.actor.ex).prefetch_related("pm")
756
745
  k = (-1) ** int(taker_side) # on_buy=1, on_sell=-1
757
746
  sleep_sec = 3 # 1 if set(pms) & {"volet"} and coinex.coin_id == 1 else 5
758
- creds: list[models.CredEx] = await self.get_credexs_by_pms(race.road.ad.pms, curex.cur_id)
747
+ creds: list[models.CredEx] = await self.actor.get_credexs_by(pm_ids, curex.cur_id)
759
748
  _lstat, volume = None, 0
760
749
 
761
750
  while self.actor.person.user.status > 0:
@@ -814,7 +803,7 @@ class AgentClient(BaseAgentClient): # Bybit client
814
803
  await sleep(15)
815
804
  continue
816
805
  (cur_plc,) = cur_plc # может упасть если в списке > 1 наш ad
817
- [(await self.cond_upsert(ad, ps=race.road.ad.pair_side, force=True))[0] for ad in ads[:cur_plc]]
806
+ [(await self.ex_client.cond_load(ad, race.road.ad.pair_side, True))[0] for ad in ads[:cur_plc]]
818
807
  # rivals = [
819
808
  # (await models.RaceStat.update_or_create({"place": plc, "price": ad.price, "premium": ad.premium}, ad=ad))[
820
809
  # 0
@@ -955,286 +944,6 @@ class AgentClient(BaseAgentClient): # Bybit client
955
944
  bc, sc = mdl + mdl * (perc / 2), mdl - mdl * (perc / 2)
956
945
  return (bc, sc), hp, vmf, zplace
957
946
 
958
- async def parse_ads(
959
- self,
960
- coinex: models.CoinEx,
961
- curex: models.CurEx,
962
- taker_side: bool,
963
- pms: list[str] = None,
964
- ceil: float = None,
965
- volume: float = 9000,
966
- min_fiat: int = None,
967
- max_fiat: int = None,
968
- ):
969
- k = (-1) ** int(taker_side) # on_buy=1, on_sell=-1
970
-
971
- if pms:
972
- creds: dict[models.PmEx, models.CredEx] = await self.get_credexs_by_norms(pms, curex.cur_id)
973
- [str(p.exid) for p in creds.values()]
974
-
975
- if taker_side: # гонка в стакане продажи - мы покупаем монету за ФИАТ
976
- fiats = await models.Fiat.filter(
977
- cred_id__in=[cx.cred_id for cx in creds.values()], amount__not=F("target")
978
- )
979
- volume = min(volume, max(fiats, key=lambda f: f.target - f.amount).amount / ceil)
980
- else: # гонка в стакане покупки - мы продаем МОНЕТУ за фиат
981
- asset = await models.Asset.get(addr__actor=self.actor, addr__coin_id=coinex.coin_id)
982
- volume = min(volume, asset.free)
983
- volume = str(round(volume, coinex.coin.scale))
984
- ps = await PairSide.get(
985
- is_sell=taker_side,
986
- pair__coin_id=coinex.coin_id,
987
- pair__cur_id=curex.cur_id,
988
- )
989
- while self.actor.person.user.status > 0: # todo: depends on rest asset/fiat
990
- ads: list[Ad] = await self.ads(coinex, curex, taker_side, pms and list(creds.keys()))
991
-
992
- if not ads:
993
- print(coinex.exid, curex.exid, taker_side, "no ads!")
994
- await sleep(300)
995
- continue
996
-
997
- for i, ad in enumerate(ads):
998
- if (ceil - float(ad.price)) * k < 0:
999
- break
1000
- if int(ad.userId) == self.actor.exid:
1001
- logging.info(f"My ad {'-' if taker_side else '+'}{coinex.exid}/{curex.exid} on place#{i}")
1002
- continue
1003
- ad_db, isnew = await self.cond_upsert(ad, ps=ps)
1004
- if isnew:
1005
- s = f"{'-' if taker_side else '+'}{ad.price}[{ad.minAmount}-{ad.maxAmount}]{coinex.exid}/{curex.exid}"
1006
- print(s, end=" | ", flush=True)
1007
- try:
1008
- # take
1009
- ...
1010
- except FailedRequestError as e:
1011
- if ExcCode(e.status_code) == ExcCode.RareLimit:
1012
- await sleep(195)
1013
- elif ExcCode(e.status_code) == ExcCode.Timestamp:
1014
- await sleep(2)
1015
- else:
1016
- raise e
1017
- except (ReadTimeoutError, ConnectionDoesNotExistError):
1018
- logging.warning("Connection failed. Restarting..")
1019
- await sleep(3)
1020
-
1021
- async def cond_upsert(
1022
- self, ad: Ad, rname: str = None, ps: PairSide = None, force: bool = False
1023
- ) -> tuple[models.Ad, bool]:
1024
- _sim, cid = None, None
1025
- ad_db = await models.Ad.get_or_none(exid=ad.id, maker__ex=self.ex_client.ex).prefetch_related("cond")
1026
- # если точно такое условие уже есть в бд
1027
- if not (cleaned := clean(ad.remark)) or (cid := {oc[0]: ci for ci, oc in self.all_conds.items()}.get(cleaned)):
1028
- # и объява с таким ид уже есть, но у нее другое условие
1029
- if ad_db and ad_db.cond_id != cid:
1030
- # то обновляем ид ее условия
1031
- ad_db.cond_id = cid
1032
- await ad_db.save()
1033
- logging.info(f"{ad.nickName} upd cond#{ad_db.cond_id}->{cid}")
1034
- # old_cid = ad_db.cond_id # todo: solve race-condition, а пока что очищаем при каждом запуске
1035
- # if not len((old_cond := await Cond.get(id=old_cid).prefetch_related('ads')).ads):
1036
- # await old_cond.delete()
1037
- # logging.warning(f"Cond#{old_cid} deleted!")
1038
- return (ad_db or force and await self.ad_create(ad, cid, rname, ps)), False
1039
- # если эта объява в таким ид уже есть в бд, но с другим условием (или без), а текущего условия еще нет в бд
1040
- if ad_db:
1041
- await ad_db.fetch_related("cond__ads", "maker")
1042
- if not ad_db.cond_id or (
1043
- # у измененного условия этой объявы есть другие объявы?
1044
- (rest_ads := set(ad_db.cond.ads) - {ad_db})
1045
- and
1046
- # другие объявы этого условия принадлежат другим юзерам
1047
- {ra.maker_id for ra in rest_ads} - {ad_db.maker_id}
1048
- ):
1049
- # создадим новое условие и присвоим его только текущей объяве
1050
- cid = await self.cond_new(cleaned, {int(ad.userId)})
1051
- ad_db.cond_id = cid
1052
- await ad_db.save()
1053
-
1054
- return ad_db, True
1055
- # а если других объяв со старым условием этой обявы нет, либо они все этого же юзера
1056
- # обновляем условие (в тч во всех ЕГО объявах)
1057
- ad_db.cond.last_ver = ad_db.cond.raw_txt
1058
- ad_db.cond.raw_txt = cleaned
1059
- await ad_db.cond.save()
1060
- await self.cond_upd(ad_db.cond, {ad_db.maker.exid})
1061
- # и подправим коэфициенты похожести нового текста
1062
- await self.fix_rel_sims(ad_db.cond_id, cleaned)
1063
- return ad_db, False
1064
-
1065
- cid = await self.cond_new(cleaned, {int(ad.userId)})
1066
- return await self.ad_create(ad, cid, rname, ps), True
1067
-
1068
- async def cond_new(self, txt: str, uids: set[int]) -> int:
1069
- new_cond, _ = await Cond.update_or_create(raw_txt=txt)
1070
- # и максимально похожую связь для нового условия (если есть >= 60%)
1071
- await self.cond_upd(new_cond, uids)
1072
- return new_cond.id
1073
-
1074
- async def cond_upd(self, cond: Cond, uids: set[int]):
1075
- self.all_conds[cond.id] = cond.raw_txt, uids
1076
- # и максимально похожую связь для нового условия (если есть >= 60%)
1077
- old_cid, sim = await self.cond_get_max_sim(cond.id, cond.raw_txt, uids)
1078
- await self.actual_sim(cond.id, old_cid, sim)
1079
-
1080
- def find_in_tree(self, cid: int, old_cid: int) -> bool:
1081
- if p := self.cond_sims.get(old_cid):
1082
- if p == cid:
1083
- return True
1084
- return self.find_in_tree(cid, p)
1085
- return False
1086
-
1087
- async def cond_get_max_sim(self, cid: int, txt: str, uids: set[int]) -> tuple[int | None, int | None]:
1088
- # находим все старые тексты похожие на 90% и более
1089
- if len(txt) < 15:
1090
- return None, None
1091
- sims: dict[int, int] = {}
1092
- for old_cid, (old_txt, old_uids) in self.all_conds.items():
1093
- if len(old_txt) < 15 or uids == old_uids:
1094
- continue
1095
- elif not self.can_add_sim(cid, old_cid):
1096
- continue
1097
- if sim := get_sim(txt, old_txt):
1098
- sims[old_cid] = sim
1099
- # если есть, берем самый похожий из них
1100
- if sims:
1101
- old_cid, sim = max(sims.items(), key=lambda x: x[1])
1102
- await sleep(0.3)
1103
- return old_cid, sim
1104
- return None, None
1105
-
1106
- def can_add_sim(self, cid: int, old_cid: int) -> bool:
1107
- if cid == old_cid:
1108
- return False
1109
- elif self.cond_sims.get(cid) == old_cid:
1110
- return False
1111
- elif self.find_in_tree(cid, old_cid):
1112
- return False
1113
- elif self.cond_sims.get(old_cid) == cid:
1114
- return False
1115
- elif cid in self.rcond_sims.get(old_cid, {}):
1116
- return False
1117
- elif old_cid in self.rcond_sims.get(cid, {}):
1118
- return False
1119
- return True
1120
-
1121
- async def person_upsert(self, name: str, exid: int) -> models.Person:
1122
- if actor := await models.Actor.get_or_none(exid=exid, ex=self.ex_client.ex).prefetch_related("person"):
1123
- if not actor.person:
1124
- actor.person = await models.Person.create(name=name)
1125
- await actor.save()
1126
- return actor.person
1127
- return await models.Person.create(name=name)
1128
-
1129
- async def ad_create(
1130
- self, ad: Ad, cid: int = None, rname: str = None, ps: PairSide = None, actor: Actor = None
1131
- ) -> models.Ad:
1132
- act_df = {}
1133
- if int(ad.userId) != self.actor.exid:
1134
- act_df |= {"name": ad.nickName}
1135
- if rname:
1136
- act_df |= {"person": await self.person_upsert(rname, int(ad.userId))}
1137
- if not actor:
1138
- act_df |= {"person": await self.person_upsert(ad.nickName, int(ad.userId))}
1139
- actor, _ = await Actor.update_or_create(act_df, exid=ad.userId, ex=self.ex_client.ex)
1140
- ps = ps or await PairSide.get_or_none(
1141
- is_sell=ad.side,
1142
- pair__coin__ticker=ad.tokenId,
1143
- pair__cur__ticker=ad.currencyId,
1144
- ).prefetch_related("pair__cur", "pair__coin")
1145
- if not ps or not ps.pair:
1146
- ... # THB/USDC
1147
- ad_upd = models.Ad.validate(ad.model_dump(by_alias=True))
1148
- cur_scale = 10**ps.pair.cur.scale
1149
- coinex = await models.CoinEx.get(coin_id=ps.pair.coin_id, ex=self.ex_client.ex)
1150
- df_unq = ad_upd.df_unq(
1151
- maker_id=actor.id,
1152
- pair_side_id=ps.id,
1153
- amount=int(float(ad.quantity) * float(ad.price) * cur_scale),
1154
- quantity=int(float(ad.quantity) * 10**coinex.scale),
1155
- min_fiat=int(float(ad.minAmount) * cur_scale),
1156
- max_fiat=ad.maxAmount and int(float(ad.maxAmount) * cur_scale),
1157
- price=int(float(ad.price) * cur_scale),
1158
- premium=int(float(ad.premium) * cur_scale),
1159
- )
1160
- ad_db, _ = await models.Ad.update_or_create(**df_unq)
1161
- await ad_db.pms.add(*(await models.Pm.filter(pmexs__ex=self.ex_client.ex, pmexs__exid__in=ad.payments)))
1162
- return ad_db
1163
-
1164
- async def fix_rel_sims(self, cid: int, new_txt: str):
1165
- for rel_sim in await CondSim.filter(cond_rel_id=cid).prefetch_related("cond"):
1166
- if sim := get_sim(new_txt, rel_sim.cond.raw_txt):
1167
- rel_sim.similarity = sim
1168
- await rel_sim.save()
1169
- else:
1170
- await rel_sim.delete()
1171
-
1172
- async def actual_cond(self):
1173
- for curr, old in await CondSim.all().values_list("cond_id", "cond_rel_id"):
1174
- self.cond_sims[curr] = old
1175
- self.rcond_sims[old] |= {curr}
1176
- for cid, (txt, uids) in self.all_conds.items():
1177
- old_cid, sim = await self.cond_get_max_sim(cid, txt, uids)
1178
- await self.actual_sim(cid, old_cid, sim)
1179
- # хз бля чо это ваще
1180
- # for ad_db in await models.Ad.filter(direction__pairex__ex=self.ex_client.ex).prefetch_related("cond", "maker"):
1181
- # ad = Ad(id=str(ad_db.exid), userId=str(ad_db.maker.exid), remark=ad_db.cond.raw_txt)
1182
- # await self.cond_upsert(ad, force=True)
1183
-
1184
- async def actual_sim(self, cid: int, old_cid: int, sim: int):
1185
- if not sim:
1186
- return
1187
- if old_sim := await CondSim.get_or_none(cond_id=cid):
1188
- if old_sim.cond_rel_id != old_cid:
1189
- if sim > old_sim.similarity:
1190
- logging.warning(f"R {cid}: {old_sim.similarity}->{sim} ({old_sim.cond_rel_id}->{old_cid})")
1191
- await old_sim.update_from_dict({"similarity": sim, "old_rel_id": old_cid}).save()
1192
- self._cond_sim_upd(cid, old_cid)
1193
- elif sim != old_sim.similarity:
1194
- logging.info(f"{cid}: {old_sim.similarity}->{sim}")
1195
- await old_sim.update_from_dict({"similarity": sim}).save()
1196
- else:
1197
- await CondSim.create(cond_id=cid, cond_rel_id=old_cid, similarity=sim)
1198
- self._cond_sim_upd(cid, old_cid)
1199
-
1200
- def _cond_sim_upd(self, cid: int, old_cid: int):
1201
- if old_old_cid := self.cond_sims.get(cid): # если старый cid уже был в дереве:
1202
- self.rcond_sims[old_old_cid].remove(cid) # удаляем из обратного
1203
- self.cond_sims[cid] = old_cid # а в прямом он автоматом переопределится, даже если и был
1204
- self.rcond_sims[old_cid] |= {cid} # ну и в обратное добавим новый
1205
-
1206
- async def get_credexs_by_pms(self, pms: list[models.Pm], cur_id: int) -> list[models.CredEx]:
1207
- return await models.CredEx.filter(
1208
- ex_id=self.actor.ex_id,
1209
- cred__pmcur__pm_id__in=[pm.id for pm in pms],
1210
- cred__person_id=self.actor.person_id,
1211
- cred__pmcur__cur_id=cur_id,
1212
- )
1213
-
1214
- def build_tree(self):
1215
- set(self.cond_sims.keys()) | set(self.cond_sims.values())
1216
- tree = defaultdict(dict)
1217
- # Группируем родителей по детям
1218
- for child, par in self.cond_sims.items():
1219
- tree[par] |= {child: {}} # todo: make from self.rcond_sim
1220
-
1221
- # Строим дерево снизу вверх
1222
- def subtree(node):
1223
- if not node:
1224
- return node
1225
- for key in node:
1226
- subnode = tree.pop(key, {})
1227
- d = subtree(subnode)
1228
- node[key] |= d # actual tree rebuilding here!
1229
- return node # todo: refact?
1230
-
1231
- # Находим корни / без родителей
1232
- roots = set(self.cond_sims.values()) - set(self.cond_sims.keys())
1233
- for root in roots:
1234
- _ = subtree(tree[root])
1235
-
1236
- self.tree = tree
1237
-
1238
947
  async def take_ad(self, req: TakeAdReq):
1239
948
  ad: Ad = Ad.model_validate(self.api.get_ad_details(itemId=req.ad_id))
1240
949
  bor = BaseOrderReq(
@@ -1249,16 +958,73 @@ class AgentClient(BaseAgentClient): # Bybit client
1249
958
  resp: OrderResp = await self._order_request(bor)
1250
959
  return resp
1251
960
 
961
+ # async def parse_ads(
962
+ # self,
963
+ # coinex: models.CoinEx,
964
+ # curex: models.CurEx,
965
+ # taker_side: bool,
966
+ # pms: list[str] = None,
967
+ # ceil: float = None,
968
+ # volume: float = 9000,
969
+ # min_fiat: int = None,
970
+ # max_fiat: int = None,
971
+ # ):
972
+ # k = (-1) ** int(taker_side) # on_buy=1, on_sell=-1
973
+ # if pms:
974
+ # creds: dict[models.PmEx, models.CredEx] = await self.get_credexs_by_norms(pms, curex.cur_id)
975
+ # [str(p.exid) for p in creds.values()]
976
+ #
977
+ # if taker_side: # гонка в стакане продажи - мы покупаем монету за ФИАТ
978
+ # fiats = await models.Fiat.filter(
979
+ # cred_id__in=[cx.cred_id for cx in creds.values()], amount__not=F("target")
980
+ # )
981
+ # volume = min(volume, max(fiats, key=lambda f: f.target - f.amount).amount / ceil)
982
+ # else: # гонка в стакане покупки - мы продаем МОНЕТУ за фиат
983
+ # asset = await models.Asset.get(addr__actor=self.actor, addr__coin_id=coinex.coin_id)
984
+ # volume = min(volume, asset.free)
985
+ # volume = str(round(volume, coinex.coin.scale))
986
+ # ps = await PairSide.get(
987
+ # is_sell=taker_side,
988
+ # pair__coin_id=coinex.coin_id,
989
+ # pair__cur_id=curex.cur_id,
990
+ # )
991
+ # while self.actor.person.user.status > 0: # todo: depends on rest asset/fiat
992
+ # ads: list[Ad] = await self.ads(coinex, curex, taker_side, pms and list(creds.keys()))
993
+ #
994
+ # if not ads:
995
+ # print(coinex.exid, curex.exid, taker_side, "no ads!")
996
+ # await sleep(300)
997
+ # continue
998
+ #
999
+ # for i, ad in enumerate(ads):
1000
+ # if (ceil - float(ad.price)) * k < 0:
1001
+ # break
1002
+ # if int(ad.userId) == self.actor.exid:
1003
+ # logging.info(f"My ad {'-' if taker_side else '+'}{coinex.exid}/{curex.exid} on place#{i}")
1004
+ # continue
1005
+ # ad_db, isnew = await self.cond_upsert(ad, ps=ps)
1006
+ # if isnew:
1007
+ # s = f"{'-' if taker_side else '+'}{ad.price}[{ad.minAmount}-{ad.maxAmount}]{coinex.exid}/{curex.exid}"
1008
+ # print(s, end=" | ", flush=True)
1009
+ # try:
1010
+ # # take
1011
+ # ...
1012
+ # except FailedRequestError as e:
1013
+ # if ExcCode(e.status_code) == ExcCode.RareLimit:
1014
+ # await sleep(195)
1015
+ # elif ExcCode(e.status_code) == ExcCode.Timestamp:
1016
+ # await sleep(2)
1017
+ # else:
1018
+ # raise e
1019
+ # except (ReadTimeoutError, ConnectionDoesNotExistError):
1020
+ # logging.warning("Connection failed. Restarting..")
1021
+ # await sleep(3)
1022
+
1252
1023
 
1253
1024
  def ms2utc(msk_ts_str: str):
1254
1025
  return datetime.fromtimestamp(int(msk_ts_str) / 1000, timezone(timedelta(hours=3), name="MSK"))
1255
1026
 
1256
1027
 
1257
- def get_sim(s1, s2) -> int:
1258
- sim = int((SequenceMatcher(None, s1, s2).ratio() - 0.6) * 10_000)
1259
- return sim if sim > 0 else 0
1260
-
1261
-
1262
1028
  def detailed_diff(str1, str2):
1263
1029
  matcher = SequenceMatcher(None, str1, str2)
1264
1030
  result = []
@@ -1276,14 +1042,6 @@ def detailed_diff(str1, str2):
1276
1042
  return "".join(result)
1277
1043
 
1278
1044
 
1279
- def clean(s) -> str:
1280
- clear = r"[^\w\s.,!?;:()\-]"
1281
- repeat = r"(.)\1{2,}"
1282
- s = re.sub(clear, "", s).lower()
1283
- s = re.sub(repeat, r"\1", s)
1284
- return s.replace("\n\n", "\n").replace(" ", " ").strip(" \n/.,!?-")
1285
-
1286
-
1287
1045
  def step_is_need(mad, cad) -> bool:
1288
1046
  # todo: пока не решен непонятный кейс, почему то конкурент по всем параметрам слабже, но в списке ранжируется выше.
1289
1047
  # текущая версия: recentExecuteRate округляется до целого, но на бэке байбита его дробная часть больше
@@ -1353,28 +1111,6 @@ async def main():
1353
1111
  # s, _ = await models.Synonym.update_or_create(typ=SynonymType.name, txt=name)
1354
1112
  # await s.curs.add(rub.cur)
1355
1113
 
1356
- # пока порешали рейс-кондишн, очищаем сиротские условия при каждом запуске
1357
- # [await c.delete() for c in await Cond.filter(ads__isnull=True)]
1358
- cl.all_conds = {
1359
- c.id: (c.raw_txt, {a.maker.exid for a in c.ads}) for c in await Cond.all().prefetch_related("ads__maker")
1360
- }
1361
- for curr, old in await CondSim.filter().values_list("cond_id", "cond_rel_id"):
1362
- cl.cond_sims[curr] = old
1363
- cl.rcond_sims[old] |= {curr}
1364
-
1365
- cl.build_tree()
1366
- a = set()
1367
-
1368
- def check_tree(tre):
1369
- for p, c in tre.items():
1370
- a.add(p)
1371
- check_tree(c)
1372
-
1373
- for pr, ch in cl.tree.items():
1374
- check_tree(ch)
1375
- if ct := set(cl.tree.keys()) & a:
1376
- logging.exception(f"cycle cids: {ct}")
1377
-
1378
1114
  pauth = (await models.PmAgent[1]).auth
1379
1115
  papi = PayeerAPI(pauth["email"], pauth["api_id"], pauth["api_sec"])
1380
1116
  hist: dict = papi.history(count=1000)
@@ -113,7 +113,7 @@ class Ad(BaseAd):
113
113
  recommend: bool = None # for initial actualize
114
114
  recommendTag: str = None # for initial actualize
115
115
  remark: str = Field(serialization_alias="auto_msg")
116
- side: Literal[0, 1] = None # for initial actualize # 0 - покупка, 1 - продажа (для мейкера, т.е КАКАЯ объява)
116
+ side: Literal[0, 1] = None # for initial actualize # 0 - покупка, 1 - продажа (для мейкера, т.е КАКАЯ объява)
117
117
  status: Literal[10, 20, 30] # 10: online; 20: offline; 30: completed
118
118
  symbolInfo: SymbolInfo = None # for initial actualize
119
119
  tokenId: str = None # for initial actualize
@@ -52,6 +52,7 @@ class CredEpyd(CredExOut):
52
52
  paymentExt5: str
53
53
  paymentExt6: str
54
54
  paymentTemplateVersion: int
55
+ securityRiskToken: str = ""
55
56
 
56
57
 
57
58
  class MyCredEpyd(CredEpyd): # todo: заменить везде где надо CredEpyd -> MyCredEpyd
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: xync-client
3
- Version: 0.0.147
3
+ Version: 0.0.148
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
@@ -8,6 +8,9 @@ Requires-Python: >=3.11
8
8
  Requires-Dist: asynchuobi
9
9
  Requires-Dist: bs4
10
10
  Requires-Dist: bybit-p2p
11
+ Requires-Dist: google-api-python-client
12
+ Requires-Dist: google-auth-httplib2
13
+ Requires-Dist: google-auth-oauthlib
11
14
  Requires-Dist: requests-toolbelt
12
15
  Requires-Dist: msgspec
13
16
  Requires-Dist: python-binance
@@ -2,11 +2,12 @@ xync_client/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
2
  xync_client/details.py,sha256=21itVPCgAtaYRR1H9J9oYudj95gafcFjExUN6QL17OI,1330
3
3
  xync_client/loader.py,sha256=hxf8ob50DO7r_qjr2qBoO7IyjkXeHHzVQ63YjXerjoU,600
4
4
  xync_client/pm_unifier.py,sha256=T2Xh-tvcu114P2YBI6RK_XDiaIhyq6ABMrXDuXPlx7A,6541
5
+ xync_client/Abc/AdLoader.py,sha256=-9UpLgFdIKcpQ2Z6PLCin7c0JmlUQ66WMouNUTWyEHg,13362
5
6
  xync_client/Abc/Agent.py,sha256=FmKT4EefJn2oNc9icIgxDqoJoRe33FGgaNwBw5P5B4A,5727
6
7
  xync_client/Abc/Asset.py,sha256=hlgyFaU9byr2N2r8Heh-_ICx49SKuKxfRTUA4yQWmEw,454
7
8
  xync_client/Abc/Auth.py,sha256=OPQXN7_XYQZP9431ylFksd6JDusbKG8N_1g6CXTZ6yY,1495
8
9
  xync_client/Abc/BaseTest.py,sha256=vaAs5Z4HYV7k_C3zQz6JKO75s2hXtVbBI3-0Srkzv5Q,2388
9
- xync_client/Abc/Ex.py,sha256=n41-XCjoIV-KpC_lK3jO049tQKbFmE0eDU3SDlgZTws,12986
10
+ xync_client/Abc/Ex.py,sha256=6i78hduLVULyaTYlKbu7hsSrCIWVGrFB0HMll0TeSxM,13091
10
11
  xync_client/Abc/Exception.py,sha256=Sts7RpP370NBdjaH_cyXDdHtjge8zXNUGWCrKw49Zyk,482
11
12
  xync_client/Abc/HasAbotUid.py,sha256=LsTHHjMHBauCwJoqgDa9Lx4R6xsDOHfsN4jM539Bpqg,279
12
13
  xync_client/Abc/InAgent.py,sha256=XLf5czbxxEimsIIe653buoP7OsWZD6mc2w37q4TkNd0,703
@@ -36,14 +37,14 @@ xync_client/BitGet/ex.py,sha256=uEvvXuLaJv8o8BFi0bMA3XyBuTfVDWagAjLOHZl-xlE,3765
36
37
  xync_client/BitGet/etype/ad.py,sha256=fysSW47wGYjSOPUqY864z857AJz4gjN-nOkI1Jxd27U,1838
37
38
  xync_client/BitPapa/ex.py,sha256=U-RRB_RSOtErfRgxOZYWegZ_td_uZO37YKo3Jxchf_w,912
38
39
  xync_client/Bybit/InAgent.py,sha256=1Y4ZyxqAY8xbAflku48fJjjlSs4pOz2mFFJSO9iWg8w,26405
39
- xync_client/Bybit/agent.py,sha256=vThoZdscmprogrL4a1wEhUga5uKapF8lhm7R5VT_xno,64529
40
+ xync_client/Bybit/agent.py,sha256=VftYukdo0nYysxUMFMwVpKH6xmeP4LVfnvoT3SIWCFo,51978
40
41
  xync_client/Bybit/ex.py,sha256=3oARvReBoDs90FzQY31-L-q_YU-TIRbvWB7z4lwESsA,4715
41
42
  xync_client/Bybit/order.py,sha256=H4UIb8hxFGnw1hZuSbr0yZ4qeaCOIZOMc6jEst0ycBs,1713
42
43
  xync_client/Bybit/web_earn.py,sha256=qjqS10xlFc8r40IhDdPZ0LxA2dFEGbvBGXdsrUUJCMo,3019
43
44
  xync_client/Bybit/web_p2p.py,sha256=sAXzK03t6jwDnz4rrvP2IzI0KxfKa7C_5GuzH1HwLuA,11768
44
45
  xync_client/Bybit/ws.py,sha256=OQjZHo_MiAH1dlOs3c-aUZBKyqToNTmH560udh6RYDE,1431
45
- xync_client/Bybit/etype/ad.py,sha256=HJOHi9KrbLQMpwEyd4oA8436QTNRqrd2HWFF-JNZGDo,8066
46
- xync_client/Bybit/etype/cred.py,sha256=dgFExLB4e5Wf6SqfU9SOdeooHQa84DRbTGm_OJhNw_o,1354
46
+ xync_client/Bybit/etype/ad.py,sha256=pCD0I9SL4Paaegs85fHGtvZ-7Cm917AQXYel0k1MbY0,8065
47
+ xync_client/Bybit/etype/cred.py,sha256=qDQUsMqLV4XtXKYWZV4f805kLDf7AdcPYscHRA-IMBo,1386
47
48
  xync_client/Bybit/etype/order.py,sha256=668UJdCNEnvnalmbtTQ9Tw50arJtgpV_09GHu0iyaRU,8870
48
49
  xync_client/Gate/ex.py,sha256=QbhB3u7TWnvVGD-AknB2nay6KZjEXQ-1JT9UacX4sWI,3735
49
50
  xync_client/Gate/premarket.py,sha256=IW-CgkmNJePJR2j_NRfULNKTePMX35XlhldqdiO76zY,2138
@@ -96,7 +97,7 @@ xync_client/TgWallet/order.py,sha256=BOmBx5WWfJv0-_-A8DcR-Xd8utqO_VTmSqSegm0cteQ
96
97
  xync_client/TgWallet/pyd.py,sha256=Ys3E8b3RLuyQ26frWT0F0BorkNxVpxnd18tY4Gp9dik,5636
97
98
  xync_client/TgWallet/pyro.py,sha256=2K7QWdo48k4MbbgQt90gdz_HiPck69Njm4xaMjIVgoo,1440
98
99
  xync_client/TgWallet/web.py,sha256=kDcv9SKKQPe91mw1qJBpbuyKYCAmZdfdHJylHumLBVU,1608
99
- xync_client-0.0.147.dist-info/METADATA,sha256=spFbottWxSke1DGsyR1ypIgZH8LVjREO2Vfvv2HP-4g,1037
100
- xync_client-0.0.147.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
101
- xync_client-0.0.147.dist-info/top_level.txt,sha256=bmYEVIIrD3v7yFwH-X15pEfRvzhuAdfsAZ2igvNI4O8,12
102
- xync_client-0.0.147.dist-info/RECORD,,
100
+ xync_client-0.0.148.dist-info/METADATA,sha256=v2HPGq4q5LhjcUCezyrU7_37urzKs6je3wy5XSWmTKM,1149
101
+ xync_client-0.0.148.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
102
+ xync_client-0.0.148.dist-info/top_level.txt,sha256=bmYEVIIrD3v7yFwH-X15pEfRvzhuAdfsAZ2igvNI4O8,12
103
+ xync_client-0.0.148.dist-info/RECORD,,