xync-client 0.0.147__py3-none-any.whl → 0.0.150__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.

@@ -2,10 +2,10 @@ 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
8
+ from hashlib import sha256
9
9
  from http.client import HTTPException
10
10
  from math import floor
11
11
  from typing import Literal
@@ -18,7 +18,7 @@ from payeer_api import PayeerAPI
18
18
  from pyro_client.client.file import FileClient
19
19
  from tortoise import BaseDBAsyncClient
20
20
  from tortoise.exceptions import IntegrityError
21
- from tortoise.expressions import F, Q
21
+ from tortoise.expressions import Q
22
22
  from tortoise.functions import Count
23
23
  from tortoise.signals import post_save
24
24
  from urllib3.exceptions import ReadTimeoutError
@@ -28,7 +28,7 @@ from xync_bot import XyncBot
28
28
  from xync_schema import models
29
29
  from xync_schema.enums import OrderStatus
30
30
 
31
- from xync_schema.models import Actor, Cond, CondSim, PmCur, PairSide, Agent
31
+ from xync_schema.models import Actor, PmCur, Agent
32
32
 
33
33
  from xync_client.Abc.Agent import BaseAgentClient
34
34
  from xync_client.Abc.xtype import FlatDict, BaseOrderReq
@@ -54,8 +54,11 @@ class NoMakerException(Exception):
54
54
 
55
55
 
56
56
  class AgentClient(BaseAgentClient): # Bybit client
57
- host = "api2.bybit.com"
58
- headers = {"cookie": ";"} # rewrite token for public methods
57
+ headers = {
58
+ # "accept": "application/json",
59
+ "Cookie": ";",
60
+ }
61
+ # rewrite token for public methods
59
62
  api: P2P
60
63
  last_ad_id: list[str] = []
61
64
  update_ad_body = {
@@ -85,28 +88,24 @@ class AgentClient(BaseAgentClient): # Bybit client
85
88
  "actionType": "MODIFY",
86
89
  "securityRiskToken": "",
87
90
  }
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
91
 
93
92
  def __init__(self, agent: Agent, fbot: FileClient, bbot: XyncBot, **kwargs):
94
93
  super().__init__(agent, fbot, bbot, **kwargs)
95
94
  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
95
+ self.hist: dict | None = None
96
+ self.completed_orders: list[int] | None = None
98
97
 
99
98
  """ Private METHs"""
100
99
 
101
100
  async def fiat_new(self, payment_type: int, real_name: str, account_number: str) -> FlatDict | None:
102
101
  method1 = await self._post(
103
- "/fiat/otc/user/payment/new_create",
102
+ "/x-api/fiat/otc/user/payment/new_create",
104
103
  {"paymentType": payment_type, "realName": real_name, "accountNo": account_number, "securityRiskToken": ""},
105
104
  )
106
105
  if srt := method1["result"]["securityRiskToken"]:
107
106
  await self._check_2fa(srt)
108
107
  method2 = await self._post(
109
- "/fiat/otc/user/payment/new_create",
108
+ "/x-api/fiat/otc/user/payment/new_create",
110
109
  {
111
110
  "paymentType": payment_type,
112
111
  "realName": real_name,
@@ -118,12 +117,8 @@ class AgentClient(BaseAgentClient): # Bybit client
118
117
  else:
119
118
  return logging.exception(method1)
120
119
 
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]
120
+ def get_payment_method(self, fiat_id: int) -> CredEpyd:
121
+ return self.creds()[fiat_id]
127
122
 
128
123
  def creds(self) -> dict[int, CredEpyd]:
129
124
  data = self.api.get_user_payment_types()
@@ -149,8 +144,7 @@ class AgentClient(BaseAgentClient): # Bybit client
149
144
  pmex.pm.df_cur_id
150
145
  or (pmex.pm.country_id and (await pmex.pm.country).cur_id)
151
146
  # or (ecdx.currencyBalance and await models.Cur.get_or_none(ticker=ecdx.currencyBalance[0]))
152
- or (0 < len(pmex.pm.curs) < 30 and pmex.pm.curs[-1].id)
153
- or await self.guess_cur(ecdx)
147
+ or await self.guess_cur(ecdx, len(pmex.pm.curs) > 1 and pmex.pm.curs)
154
148
  )
155
149
  if not cur_id:
156
150
  raise Exception(f"Set default cur for {pmex.name}")
@@ -169,7 +163,7 @@ class AgentClient(BaseAgentClient): # Bybit client
169
163
  credex_db, _ = await models.CredEx.update_or_create(**credex_in.df_unq())
170
164
  return credex_db
171
165
 
172
- async def guess_cur(self, ecdx: CredEpyd):
166
+ async def guess_cur(self, ecdx: CredEpyd, curs: list[models.Cur]):
173
167
  mbs = ecdx.bankName.split(", ")
174
168
  mbs += ecdx.branchName.split(" / ")
175
169
  mbs = {mb.lower(): mb for mb in mbs}
@@ -181,7 +175,7 @@ class AgentClient(BaseAgentClient): # Bybit client
181
175
  .values("pmcurs__cur_id", "names", "ccnt")
182
176
  ):
183
177
  return pms[0]["pmcurs__cur_id"]
184
- curs = {c.ticker: c.id for c in await models.Cur.all()}
178
+ curs = {c.ticker: c.id for c in curs or await models.Cur.all()}
185
179
  for cur, cid in curs.items():
186
180
  if re.search(re.compile(rf"\({cur}\)$"), ecdx.bankName):
187
181
  return cid
@@ -191,6 +185,8 @@ class AgentClient(BaseAgentClient): # Bybit client
191
185
  return cid
192
186
  if re.search(re.compile(rf"\({cur}\)$"), ecdx.payMessage):
193
187
  return cid
188
+ if re.search(re.compile(rf"\({cur}\)$"), ecdx.paymentExt1):
189
+ return cid
194
190
  return None
195
191
 
196
192
  # 25: Список реквизитов моих платежных методов
@@ -206,28 +202,28 @@ class AgentClient(BaseAgentClient): # Bybit client
206
202
  # 27
207
203
  async def fiat_upd(self, fiat_id: int, detail: str, name: str = None) -> dict:
208
204
  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)
205
+ fiat.realName = name
206
+ fiat.accountNo = detail
207
+ result = await self._post("/x-api/fiat/otc/user/payment/new_update", fiat.model_dump(exclude_none=True))
212
208
  srt = result["result"]["securityRiskToken"]
213
209
  await self._check_2fa(srt)
214
- fiat["securityRiskToken"] = srt
215
- result2 = await self._post("/fiat/otc/user/payment/new_update", fiat)
210
+ fiat.securityRiskToken = srt
211
+ result2 = await self._post("/fiat/otc/user/payment/new_update", fiat.model_dump(exclude_none=True))
216
212
  return result2
217
213
 
218
214
  # 28
219
215
  async def fiat_del(self, fiat_id: int) -> dict | str:
220
216
  data = {"id": fiat_id, "securityRiskToken": ""}
221
- method = await self._post("/fiat/otc/user/payment/new_delete", data)
217
+ method = await self._post("/x-api/fiat/otc/user/payment/new_delete", data)
222
218
  srt = method["result"]["securityRiskToken"]
223
219
  await self._check_2fa(srt)
224
220
  data["securityRiskToken"] = srt
225
- delete = await self._post("/fiat/otc/user/payment/new_delete", data)
221
+ delete = await self._post("/x-api/fiat/otc/user/payment/new_delete", data)
226
222
  return delete
227
223
 
228
224
  async def switch_ads(self, new_status: AdStatus) -> dict:
229
225
  data = {"workStatus": new_status.name} # todo: переделать на апи, там status 0 -> 1
230
- res = await self._post("/fiat/otc/maker/work-config/switch", data)
226
+ res = await self._post("/x-api/fiat/otc/maker/work-config/switch", data)
231
227
  return res
232
228
 
233
229
  async def ads(
@@ -244,10 +240,6 @@ class AgentClient(BaseAgentClient): # Bybit client
244
240
  cnx.exid, crx.exid, is_sell, [pmex.exid for pmex in pmxs or []], amount, lim, vm_filter
245
241
  )
246
242
 
247
- def online_ads(self) -> str:
248
- online = self._get("/fiat/otc/maker/work-config/get")
249
- return online["result"]["workStatus"]
250
-
251
243
  @staticmethod
252
244
  def get_rate(list_ads: list) -> float:
253
245
  ads = [ad for ad in list_ads if set(ad["payments"]) - {"5", "51"}]
@@ -264,27 +256,42 @@ class AgentClient(BaseAgentClient): # Bybit client
264
256
  ads = self.my_ads(True)
265
257
  if not active:
266
258
  ads += self.my_ads(False)
267
- res = [await self.ad_create(ad, actor=self.actor) for ad in ads]
259
+ res = [await self.ex_client.ad_load(ad, maker=self.actor) for ad in ads]
268
260
  res = [await models.MyAd.update_or_create(ad=ad) for ad in res]
269
261
  return len(res)
270
262
 
271
263
  def get_security_token_create(self):
272
- data = self._post("/fiat/otc/item/create", self.create_ad_body)
264
+ data = self._post("/x-api/fiat/otc/item/create", self.create_ad_body)
273
265
  if data["ret_code"] == 912120019: # Current user can not to create add as maker
274
266
  raise NoMakerException(data)
275
267
  security_risk_token = data["result"]["securityRiskToken"]
276
268
  return security_risk_token
277
269
 
278
270
  async def _check_2fa(self, risk_token) -> int:
279
- cres = await self._post("/user/public/risk/components", {"risk_token": risk_token})
280
- if cres["ret_msg"] != "success":
271
+ res = await self._post(
272
+ "/x-api/user/public/risk/components",
273
+ {"risk_token": risk_token},
274
+ hdrs={
275
+ # "Accept-Language": "ru,en;q=0.9",
276
+ },
277
+ )
278
+ if res["ret_msg"] != "success":
281
279
  raise HTTPException("get")
282
- cres = cres["result"]["component_list"]
280
+ cres = sorted(res["result"]["component_list"], key=lambda c: c["component_id"], reverse=True)
281
+ # cres = [{"component_id": "payment_password_verify"}]
282
+ vdata = {
283
+ "risk_token": risk_token,
284
+ "component_list": {c["component_id"]: await self.__get_2fa(c["component_id"], risk_token) for c in cres},
285
+ }
283
286
  res = await self._post(
284
- "/user/public/risk/verify",
285
- {
286
- "risk_token": risk_token,
287
- "component_list": {c["component_id"]: self.__get_2fa(c["component_id"]) for c in cres},
287
+ "/x-api/user/public/risk/verify",
288
+ vdata,
289
+ hdrs={
290
+ "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36",
291
+ "accept-language": "ru,en;q=0.9",
292
+ # "accept-language": "ru-RU,ru;q=0.9,en-US;q=0.8,en;q=0.7",
293
+ "accept-encoding": "gzip, deflate, br, zstd",
294
+ "accept": "application/json",
288
295
  },
289
296
  )
290
297
  if res["ret_msg"] != "success":
@@ -293,25 +300,32 @@ class AgentClient(BaseAgentClient): # Bybit client
293
300
  await self._check_2fa(risk_token)
294
301
  return res["ret_code"]
295
302
 
296
- async def __get_2fa(self, typ: Literal["google2fa", "email_verify", "payment_password_verify"], rt: str = None):
297
- if typ == "google2fa":
298
- bybit_secret = self.agent.auth["2fa"]
299
- totp = pyotp.TOTP(bybit_secret)
300
- return totp.now()
301
- elif typ == "email_verify":
302
- res = await self._post("/user/public/risk/send/code", {"risk_token": rt, "component_id": "email_verify"})
303
- if res["ret_msg"] != "success":
304
- return self.gmail.bybit_code()
305
- elif cool_down := int(res["result"]["cool_down"]):
306
- await sleep(cool_down)
303
+ async def __get_2fa(
304
+ self, typ: Literal["google2fa", "email_verify", "payment_password_verify", "phone_verify"], rt: str = None
305
+ ):
306
+ res = {"ret_msg": "success"}
307
+ if typ != "google2fa":
308
+ # "accept-language": "ru-RU,ru;q=0.9,en-US;q=0.8,en;q=0.7",
309
+ # "accept": "application/json"
310
+ res = await self._post("/x-api/user/public/risk/send/code", {"risk_token": rt, "component_id": typ})
311
+ if res["ret_msg"] == "success":
312
+ if typ == "google2fa":
313
+ bybit_secret = self.agent.auth["2fa"]
314
+ totp = pyotp.TOTP(bybit_secret)
315
+ return totp.now()
316
+ elif typ == "email_verify":
307
317
  return self.gmail.bybit_code()
308
- elif typ == "payment_password_verify":
309
- return self.agent.auth["pass"]
318
+ elif typ == "payment_password_verify":
319
+ hp = sha256(self.agent.auth["pass"].encode()).hexdigest()
320
+ return hp
321
+ elif cool_down := int(res["result"]["cool_down"]):
322
+ await sleep(cool_down)
323
+ return self.__get_2fa(typ, rt)
310
324
  raise Exception("2fa fail")
311
325
 
312
326
  def _post_ad(self, risk_token: str):
313
327
  self.create_ad_body.update({"securityRiskToken": risk_token})
314
- data = self._post("/fiat/otc/item/create", self.create_ad_body)
328
+ data = self._post("/x-api/fiat/otc/item/create", self.create_ad_body)
315
329
  return data
316
330
 
317
331
  # создание объявлений
@@ -337,7 +351,7 @@ class AgentClient(BaseAgentClient): # Bybit client
337
351
 
338
352
  def get_security_token_update(self) -> str:
339
353
  self.update_ad_body["id"] = self.last_ad_id
340
- data = self._post("/fiat/otc/item/update", self.update_ad_body)
354
+ data = self._post("/x-api/fiat/otc/item/update", self.update_ad_body)
341
355
  security_risk_token = data["result"]["securityRiskToken"]
342
356
  return security_risk_token
343
357
 
@@ -354,7 +368,7 @@ class AgentClient(BaseAgentClient): # Bybit client
354
368
 
355
369
  def update_ad(self, risk_token: str):
356
370
  self.update_ad_body.update({"securityRiskToken": risk_token})
357
- data = self._post("/fiat/otc/item/update", self.update_ad_body)
371
+ data = self._post("/x-api/fiat/otc/item/update", self.update_ad_body)
358
372
  return data
359
373
 
360
374
  def ad_del(self, ad_id: int):
@@ -362,7 +376,7 @@ class AgentClient(BaseAgentClient): # Bybit client
362
376
  return data
363
377
 
364
378
  async def __preorder_request(self, ad_id: int) -> PreOrderResp:
365
- res = await self._post("/fiat/otc/item/simple", data={"item_id": str(ad_id)})
379
+ res = await self._post("/x-api/fiat/otc/item/simple", json={"item_id": str(ad_id)})
366
380
  if res["ret_code"] == 0:
367
381
  res = res["result"]
368
382
  return PreOrderResp.model_validate(res)
@@ -373,20 +387,18 @@ class AgentClient(BaseAgentClient): # Bybit client
373
387
  tokenId=br.coin_exid,
374
388
  currencyId=br.cur_exid,
375
389
  side="1" if br.is_sell else "0",
376
- amount=str(br.fiat_amount),
390
+ amount=f"{br.fiat_amount:.2f}".rstrip("0").rstrip("."),
377
391
  curPrice=por.curPrice,
378
392
  quantity=str(round(br.fiat_amount / float(por.price), br.coin_scale)),
379
393
  flag="amount",
380
- # paymentType="51",
381
- # paymentId="20399134",
382
394
  # online="0"
383
395
  )
384
396
  if br.is_sell:
385
397
  credex = await models.CredEx.get(
386
398
  cred__person_id=self.actor.person_id,
387
- pmcur__pm__pmexs__exid=por.payments[0],
388
- pmcur__pm__pmexs__ex_id=self.ex_client.ex.id,
389
- pmcur__cur_id=br.cur_exid,
399
+ cred__pmcur__pm__pmexs__exid=por.payments[0],
400
+ cred__pmcur__pm__pmexs__ex_id=self.ex_client.ex.id,
401
+ cred__pmcur__cur__ticker=br.cur_exid,
390
402
  )
391
403
  req = OrderSellRequest(**req.model_dump(), paymentType=por.payments[0], paymentId=str(credex.exid))
392
404
  return req
@@ -398,9 +410,12 @@ class AgentClient(BaseAgentClient): # Bybit client
398
410
  return await self.__order_create(req, bor)
399
411
 
400
412
  async def __order_create(self, req: OrderRequest | OrderSellRequest, bor: BaseOrderReq) -> OrderResp:
401
- res: dict = await self._post("/fiat/otc/order/create", data=req.model_dump())
413
+ res: dict = await self._post("/x-api/fiat/otc/order/create", json=req.model_dump())
402
414
  if res["ret_code"] == 0:
403
415
  resp = OrderResp.model_validate(res["result"])
416
+ elif res["ret_code"] == 10001:
417
+ logging.error(req.model_dump(), "POST", self.session._base_url)
418
+ raise HTTPException()
404
419
  elif res["ret_code"] == 912120030 or res["ret_msg"] == "The price has changed, please try again later.":
405
420
  resp = await self._order_request(bor)
406
421
  if not resp.orderId and resp.needSecurityRisk:
@@ -414,15 +429,15 @@ class AgentClient(BaseAgentClient): # Bybit client
414
429
 
415
430
  async def cancel_order(self, order_id: str) -> bool:
416
431
  cr = CancelOrderReq(orderId=order_id)
417
- res = await self._post("/fiat/otc/order/cancel", cr.model_dump())
432
+ res = await self._post("/x-api/fiat/otc/order/cancel", cr.model_dump())
418
433
  return res["ret_code"] == 0
419
434
 
420
435
  def get_order_info(self, order_id: str) -> dict:
421
- data = self._post("/fiat/otc/order/info", json={"orderId": order_id})
436
+ data = self._post("/x-api/fiat/otc/order/info", json={"orderId": order_id})
422
437
  return data["result"]
423
438
 
424
439
  def get_chat_msg(self, order_id):
425
- data = self._post("/fiat/otc/order/message/listpage", json={"orderId": order_id, "size": 100})
440
+ data = self._post("/x-api/fiat/otc/order/message/listpage", json={"orderId": order_id, "size": 100})
426
441
  msgs = [
427
442
  {"text": msg["message"], "type": msg["contentType"], "role": msg["roleType"], "user_id": msg["userId"]}
428
443
  for msg in data["result"]["result"]
@@ -431,14 +446,14 @@ class AgentClient(BaseAgentClient): # Bybit client
431
446
  return msgs
432
447
 
433
448
  def block_user(self, user_id: str):
434
- return self._post("/fiat/p2p/user/add_block_user", {"blockedUserId": user_id})
449
+ return self._post("/x-api/fiat/p2p/user/add_block_user", {"blockedUserId": user_id})
435
450
 
436
451
  def unblock_user(self, user_id: str):
437
- return self._post("/fiat/p2p/user/delete_block_user", {"blockedUserId": user_id})
452
+ return self._post("/x-api/fiat/p2p/user/delete_block_user", {"blockedUserId": user_id})
438
453
 
439
454
  def user_review_post(self, order_id: str):
440
455
  return self._post(
441
- "/fiat/otc/order/appraise/modify",
456
+ "/x-api/fiat/otc/order/appraise/modify",
442
457
  {
443
458
  "orderId": order_id,
444
459
  "anonymous": "0",
@@ -458,7 +473,7 @@ class AgentClient(BaseAgentClient): # Bybit client
458
473
  self, side: int = None, status: int = None, begin_time: int = None, end_time: int = None, token_id: str = None
459
474
  ):
460
475
  return await self._post(
461
- "/fiat/otc/order/pending/simplifyList",
476
+ "/x-api/fiat/otc/order/pending/simplifyList",
462
477
  {
463
478
  "status": status,
464
479
  "tokenId": token_id,
@@ -472,7 +487,7 @@ class AgentClient(BaseAgentClient): # Bybit client
472
487
 
473
488
  def get_orders_done(self, begin_time: int, end_time: int, status: int, side: int, token_id: str):
474
489
  return self._post(
475
- "/fiat/otc/order/simplifyList",
490
+ "/x-api/fiat/otc/order/simplifyList",
476
491
  {
477
492
  "status": status, # 50 - завершено
478
493
  "tokenId": token_id,
@@ -494,12 +509,14 @@ class AgentClient(BaseAgentClient): # Bybit client
494
509
  maker_name = order.buyerRealName, order.sellerRealName
495
510
  im_maker = int(order.makerUserId == order.userId)
496
511
  taker_id = (order.userId, order.targetUserId)[im_maker]
497
- taker_person = await self.person_upsert(maker_name[::-1][ad.side], taker_id)
512
+ taker_person = await self.ex_client.person_name_update(maker_name[::-1][ad.side], taker_id)
498
513
  seller_person = (
499
- self.actor.person if order.side else await self.person_upsert(order.sellerRealName, int(order.targetUserId))
514
+ self.actor.person
515
+ if order.side
516
+ else await self.ex_client.person_name_update(order.sellerRealName, int(order.targetUserId))
500
517
  )
501
518
  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)
519
+ ad_db, cond_isnew = await self.ex_client.cond_load(ad, force=True, rname=maker_name[ad.side])
503
520
  if not ad_db:
504
521
  ...
505
522
  ecredex: CredEpyd = order.confirmedPayTerm
@@ -602,7 +619,7 @@ class AgentClient(BaseAgentClient): # Bybit client
602
619
  buyer_person = (
603
620
  self.actor.person
604
621
  if not order.side
605
- else await self.person_upsert(order.buyerRealName, int(order.targetUserId))
622
+ else await self.ex_client.person_name_update(order.buyerRealName, int(order.targetUserId))
606
623
  )
607
624
  ts = [t for t in tsa if floor(fa := float(order.amount)) <= float(t["creditedAmount"]) <= round(fa)]
608
625
  if len(ts) != 1:
@@ -755,7 +772,7 @@ class AgentClient(BaseAgentClient): # Bybit client
755
772
  pmexs: list[models.PmEx] = await models.PmEx.filter(pm_id__in=pm_ids, ex=self.actor.ex).prefetch_related("pm")
756
773
  k = (-1) ** int(taker_side) # on_buy=1, on_sell=-1
757
774
  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)
775
+ creds: list[models.CredEx] = await self.actor.get_credexs_by(pm_ids, curex.cur_id)
759
776
  _lstat, volume = None, 0
760
777
 
761
778
  while self.actor.person.user.status > 0:
@@ -814,7 +831,7 @@ class AgentClient(BaseAgentClient): # Bybit client
814
831
  await sleep(15)
815
832
  continue
816
833
  (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]]
834
+ [(await self.ex_client.cond_load(ad, race.road.ad.pair_side, True))[0] for ad in ads[:cur_plc]]
818
835
  # rivals = [
819
836
  # (await models.RaceStat.update_or_create({"place": plc, "price": ad.price, "premium": ad.premium}, ad=ad))[
820
837
  # 0
@@ -955,310 +972,89 @@ class AgentClient(BaseAgentClient): # Bybit client
955
972
  bc, sc = mdl + mdl * (perc / 2), mdl - mdl * (perc / 2)
956
973
  return (bc, sc), hp, vmf, zplace
957
974
 
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
975
+ async def _take_ad(self, req: TakeAdReq):
976
+ res = self.api.get_ad_details(itemId=req.ad_id)["result"]
977
+ ad: Ad = Ad.model_validate(res)
970
978
 
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
- async def take_ad(self, req: TakeAdReq):
1239
- ad: Ad = Ad.model_validate(self.api.get_ad_details(itemId=req.ad_id))
1240
979
  bor = BaseOrderReq(
1241
980
  ad_id=str(ad.id),
1242
981
  fiat_amount=req.amount,
1243
- is_sell=bool(ad.side),
982
+ is_sell=not bool(ad.side),
1244
983
  cur_exid=ad.currencyId,
1245
984
  coin_exid=ad.tokenId,
1246
- coin_scale=ad.token.scale,
985
+ coin_scale=ad.symbolInfo.token.scale,
1247
986
  pm_id=req.pm_id,
1248
987
  )
1249
988
  resp: OrderResp = await self._order_request(bor)
1250
989
  return resp
1251
990
 
991
+ # async def parse_ads(
992
+ # self,
993
+ # coinex: models.CoinEx,
994
+ # curex: models.CurEx,
995
+ # taker_side: bool,
996
+ # pms: list[str] = None,
997
+ # ceil: float = None,
998
+ # volume: float = 9000,
999
+ # min_fiat: int = None,
1000
+ # max_fiat: int = None,
1001
+ # ):
1002
+ # k = (-1) ** int(taker_side) # on_buy=1, on_sell=-1
1003
+ # if pms:
1004
+ # creds: dict[models.PmEx, models.CredEx] = await self.get_credexs_by_norms(pms, curex.cur_id)
1005
+ # [str(p.exid) for p in creds.values()]
1006
+ #
1007
+ # if taker_side: # гонка в стакане продажи - мы покупаем монету за ФИАТ
1008
+ # fiats = await models.Fiat.filter(
1009
+ # cred_id__in=[cx.cred_id for cx in creds.values()], amount__not=F("target")
1010
+ # )
1011
+ # volume = min(volume, max(fiats, key=lambda f: f.target - f.amount).amount / ceil)
1012
+ # else: # гонка в стакане покупки - мы продаем МОНЕТУ за фиат
1013
+ # asset = await models.Asset.get(addr__actor=self.actor, addr__coin_id=coinex.coin_id)
1014
+ # volume = min(volume, asset.free)
1015
+ # volume = str(round(volume, coinex.coin.scale))
1016
+ # ps = await PairSide.get(
1017
+ # is_sell=taker_side,
1018
+ # pair__coin_id=coinex.coin_id,
1019
+ # pair__cur_id=curex.cur_id,
1020
+ # )
1021
+ # while self.actor.person.user.status > 0: # todo: depends on rest asset/fiat
1022
+ # ads: list[Ad] = await self.ads(coinex, curex, taker_side, pms and list(creds.keys()))
1023
+ #
1024
+ # if not ads:
1025
+ # print(coinex.exid, curex.exid, taker_side, "no ads!")
1026
+ # await sleep(300)
1027
+ # continue
1028
+ #
1029
+ # for i, ad in enumerate(ads):
1030
+ # if (ceil - float(ad.price)) * k < 0:
1031
+ # break
1032
+ # if int(ad.userId) == self.actor.exid:
1033
+ # logging.info(f"My ad {'-' if taker_side else '+'}{coinex.exid}/{curex.exid} on place#{i}")
1034
+ # continue
1035
+ # ad_db, isnew = await self.cond_upsert(ad, ps=ps)
1036
+ # if isnew:
1037
+ # s = f"{'-' if taker_side else '+'}{ad.price}[{ad.minAmount}-{ad.maxAmount}]{coinex.exid}/{curex.exid}"
1038
+ # print(s, end=" | ", flush=True)
1039
+ # try:
1040
+ # # take
1041
+ # ...
1042
+ # except FailedRequestError as e:
1043
+ # if ExcCode(e.status_code) == ExcCode.RareLimit:
1044
+ # await sleep(195)
1045
+ # elif ExcCode(e.status_code) == ExcCode.Timestamp:
1046
+ # await sleep(2)
1047
+ # else:
1048
+ # raise e
1049
+ # except (ReadTimeoutError, ConnectionDoesNotExistError):
1050
+ # logging.warning("Connection failed. Restarting..")
1051
+ # await sleep(3)
1052
+
1252
1053
 
1253
1054
  def ms2utc(msk_ts_str: str):
1254
1055
  return datetime.fromtimestamp(int(msk_ts_str) / 1000, timezone(timedelta(hours=3), name="MSK"))
1255
1056
 
1256
1057
 
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
1058
  def detailed_diff(str1, str2):
1263
1059
  matcher = SequenceMatcher(None, str1, str2)
1264
1060
  result = []
@@ -1276,14 +1072,6 @@ def detailed_diff(str1, str2):
1276
1072
  return "".join(result)
1277
1073
 
1278
1074
 
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
1075
  def step_is_need(mad, cad) -> bool:
1288
1076
  # todo: пока не решен непонятный кейс, почему то конкурент по всем параметрам слабже, но в списке ранжируется выше.
1289
1077
  # текущая версия: recentExecuteRate округляется до целого, но на бэке байбита его дробная часть больше
@@ -1353,28 +1141,6 @@ async def main():
1353
1141
  # s, _ = await models.Synonym.update_or_create(typ=SynonymType.name, txt=name)
1354
1142
  # await s.curs.add(rub.cur)
1355
1143
 
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
1144
  pauth = (await models.PmAgent[1]).auth
1379
1145
  papi = PayeerAPI(pauth["email"], pauth["api_id"], pauth["api_sec"])
1380
1146
  hist: dict = papi.history(count=1000)