xync-client 0.0.155__py3-none-any.whl → 0.0.156.dev18__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.
@@ -1,4 +1,5 @@
1
1
  import asyncio
2
+ import json
2
3
  import logging
3
4
  import re
4
5
  from asyncio import sleep, gather
@@ -10,13 +11,15 @@ from hashlib import sha256
10
11
  from http.client import HTTPException
11
12
  from math import floor
12
13
  from typing import Literal
14
+ from uuid import uuid4
13
15
 
14
16
  import pyotp
17
+ import websockets
15
18
  from aiohttp.http_exceptions import HttpProcessingError
16
- from asyncpg import ConnectionDoesNotExistError
17
19
  from bybit_p2p import P2P
18
20
  from bybit_p2p._exceptions import FailedRequestError
19
21
  from payeer_api import PayeerAPI
22
+ from pydantic import ValidationError
20
23
  from pyro_client.client.file import FileClient
21
24
  from tortoise import BaseDBAsyncClient
22
25
  from tortoise.exceptions import IntegrityError
@@ -24,12 +27,11 @@ from tortoise.expressions import Q
24
27
  from tortoise.functions import Count
25
28
  from tortoise.signals import post_save
26
29
  from tortoise.timezone import now
27
- from urllib3.exceptions import ReadTimeoutError
30
+ from tortoise.transactions import in_transaction
28
31
  from x_client import df_hdrs
29
32
  from x_model import init_db
30
33
  from x_model.func import ArrayAgg
31
34
  from xync_bot import XyncBot
32
- from xync_client.Bybit.InAgent import InAgentClient
33
35
 
34
36
  from xync_client.Bybit.ex import ExClient
35
37
  from xync_schema import models
@@ -38,7 +40,7 @@ from xync_schema.enums import OrderStatus, AgentStatus
38
40
  from xync_schema.models import Actor, PmCur, Agent
39
41
 
40
42
  from xync_client.Abc.Agent import BaseAgentClient
41
- from xync_client.Abc.xtype import FlatDict, BaseOrderReq
43
+ from xync_client.Abc.xtype import FlatDict, BaseOrderReq, AdUpd, GetAds
42
44
  from xync_client.Bybit.etype.ad import AdPostRequest, AdUpdateRequest, Ad, AdStatus, MyAd
43
45
  from xync_client.Bybit.etype.cred import CredEpyd
44
46
  from xync_client.Bybit.etype.order import (
@@ -52,15 +54,25 @@ from xync_client.Bybit.etype.order import (
52
54
  Status,
53
55
  OrderSellRequest,
54
56
  TakeAdReq,
57
+ StatusChange,
58
+ CountDown,
59
+ Receive,
60
+ Read,
61
+ SellerCancelChange,
55
62
  )
56
- from xync_client.loader import TORM, NET_TOKEN, PAY_TOKEN
63
+ from xync_client.Pms.Payeer.agent import PmAgentClient
64
+ from xync_client.loader import TORM, NET_TOKEN, PAY_TOKEN, PRX
57
65
 
58
66
 
59
67
  class NoMakerException(Exception):
60
68
  pass
61
69
 
62
70
 
63
- class AgentClient(BaseAgentClient, InAgentClient): # Bybit client
71
+ class ShareException(Exception):
72
+ pass
73
+
74
+
75
+ class AgentClient(BaseAgentClient): # Bybit client
64
76
  headers = df_hdrs | {"accept-language": "ru-RU"}
65
77
  sec_hdrs: dict[str, str]
66
78
  # rewrite token for public methods
@@ -94,8 +106,16 @@ class AgentClient(BaseAgentClient, InAgentClient): # Bybit client
94
106
  "securityRiskToken": "",
95
107
  }
96
108
 
97
- def __init__(self, agent: Agent, ex_client: ExClient, fbot: FileClient, bbot: XyncBot, **kwargs):
98
- super().__init__(agent, ex_client, fbot, bbot, **kwargs)
109
+ def __init__(
110
+ self,
111
+ agent: Agent,
112
+ ex_client: ExClient,
113
+ fbot: FileClient,
114
+ bbot: XyncBot,
115
+ pm_clients: dict[int, PmAgentClient] = None,
116
+ **kwargs,
117
+ ):
118
+ super().__init__(agent, ex_client, fbot, bbot, pm_clients, **kwargs)
99
119
  self.sec_hdrs = {
100
120
  "accept-language": "ru,en;q=0.9",
101
121
  "gdfp": agent.auth["Risktoken"],
@@ -169,17 +189,23 @@ class AgentClient(BaseAgentClient, InAgentClient): # Bybit client
169
189
  xtr += (" | " if xtr else "") + ecdx.qrcode
170
190
  elif ecdx.paymentExt1:
171
191
  xtr += (" | " if xtr else "") + ecdx.paymentExt1
172
- cred_db, _ = await models.Cred.update_or_create(
173
- {
174
- "name": ecdx.realName,
175
- "extra": xtr,
176
- },
177
- pmcur=pmcur,
178
- person_id=pers_id or self.actor.person_id,
179
- detail=ecdx.accountNo or ecdx.payMessage,
180
- )
181
- credex_in = models.CredEx.validate({"exid": ecdx.id, "cred_id": cred_db.id, "ex_id": self.actor.ex.id})
182
- credex_db, _ = await models.CredEx.update_or_create(**credex_in.df_unq())
192
+ try:
193
+ cred_db, _ = await models.Cred.update_or_create(
194
+ {
195
+ "name": ecdx.realName,
196
+ "extra": xtr,
197
+ },
198
+ pmcur=pmcur,
199
+ person_id=pers_id or self.actor.person_id,
200
+ detail=ecdx.accountNo or ecdx.payMessage,
201
+ )
202
+ if cred_db.ovr_pm_id is None and (cred_db.detail.startswith("XyncPay") or xtr.startswith("XyncPay")):
203
+ cred_db.ovr_pm_id = 0
204
+ await cred_db.save()
205
+ credex_in = models.CredEx.validate({"exid": ecdx.id, "cred_id": cred_db.id, "ex_id": self.actor.ex.id})
206
+ credex_db, _ = await models.CredEx.update_or_create(**credex_in.df_unq())
207
+ except IntegrityError as e:
208
+ raise e
183
209
  return credex_db
184
210
 
185
211
  async def guess_cur(self, ecdx: CredEpyd, curs: list[models.Cur]):
@@ -245,33 +271,6 @@ class AgentClient(BaseAgentClient, InAgentClient): # Bybit client
245
271
  res = await self._post("/x-api/fiat/otc/maker/work-config/switch", data)
246
272
  return res
247
273
 
248
- async def ads(
249
- self,
250
- cnx: models.CoinEx,
251
- crx: models.CurEx,
252
- is_sell: bool,
253
- pmexs: list[models.PmEx],
254
- amount: int = None,
255
- lim: int = 50,
256
- vm_filter: bool = False,
257
- post_pmexs: set[models.PmEx] = None,
258
- ) -> list[Ad]:
259
- if post_pmexs:
260
- pm_exids = None
261
- lim = min(1000, lim * 25)
262
- post_pmexids = {p.exid for p in post_pmexs}
263
- else:
264
- pm_exids = [px.exid for px in pmexs]
265
- post_pmexids = set()
266
- ads: list[Ad] = await self.ex_client.ads(cnx.exid, crx.exid, is_sell, pm_exids, amount, lim, vm_filter)
267
- if post_pmexs:
268
- ads = [
269
- ad
270
- for ad in ads
271
- if (set(ad.payments) & post_pmexids or [True for px in post_pmexs if px.pm.norm in ad.remark.lower()])
272
- ]
273
- return ads
274
-
275
274
  @staticmethod
276
275
  def get_rate(list_ads: list) -> float:
277
276
  ads = [ad for ad in list_ads if set(ad["payments"]) - {"5", "51"}]
@@ -292,10 +291,39 @@ class AgentClient(BaseAgentClient, InAgentClient): # Bybit client
292
291
  ad_db = await self.ex_client.ad_load(ad, maker=self.actor)
293
292
  mad_db, _ = await models.MyAd.update_or_create(ad=ad_db)
294
293
  exids = [pt.id for pt in ad.paymentTerms]
295
- credexs = await models.CredEx.filter(ex_id=self.actor.ex_id, exid__in=exids)
294
+ credexs = await models.CredEx.filter(ex_id=self.actor.ex_id, exid__in=exids).prefetch_related("cred")
296
295
  await mad_db.credexs.add(*credexs)
296
+ # share
297
+ if [cx for cx in credexs if cx.cred.ovr_pm_id == 0]:
298
+ try:
299
+ await self.ad_share(mad_db.id)
300
+ except ShareException as e:
301
+ logging.warning(e.args[0])
302
+ await sleep(1)
303
+
297
304
  return len(ads)
298
305
 
306
+ async def ad_share(self, maid: int):
307
+ myad = await models.MyAd.get(id=maid).prefetch_related("ad")
308
+ if myad.hex and myad.shared_at + timedelta(minutes=55) > now(): # check expired
309
+ # check validity
310
+ data = await self._post("/x-api/fiat/otc/item/shareItem/info", {"shareCode": myad.hex.hex()})
311
+ if data["ret_code"] == 0:
312
+ return myad.get_url()
313
+ data = await self._post("/x-api/fiat/otc/item/share", {"itemId": str(myad.ad.exid)})
314
+ if data["ret_code"] == 912300058:
315
+ raise ShareException(f"Объява {myad.id}:{myad.ad.id}:{myad.ad.exid} выключена")
316
+ if data["ret_code"] == 912300059:
317
+ raise ShareException("Торговля выключена")
318
+ if data["ret_code"] != 0: # Новая ошибка
319
+ raise ShareException(data)
320
+ url = data["result"]["shareLink"]
321
+ resp = await self.session.get(url)
322
+ hx = resp.url.query["by_web_link"].replace(models.MyAd.WEB, "")
323
+ await models.MyAd.filter(id=maid).update(hex=bytes.fromhex(hx), shared_at=now())
324
+ await myad.refresh_from_db()
325
+ return myad.get_url()
326
+
299
327
  def get_security_token_create(self):
300
328
  data = self._post("/x-api/fiat/otc/item/create", self.create_ad_body)
301
329
  if data["ret_code"] == 912120019: # Current user can not to create add as maker
@@ -342,6 +370,9 @@ class AgentClient(BaseAgentClient, InAgentClient): # Bybit client
342
370
  return self.__get_2fa(typ, rt)
343
371
  raise Exception("2fa fail")
344
372
 
373
+ def get_ad(self, aid: int) -> Ad:
374
+ return Ad(**self.api.get_ad_details(itemId=aid)["result"])
375
+
345
376
  def _post_ad(self, risk_token: str):
346
377
  self.create_ad_body.update({"securityRiskToken": risk_token})
347
378
  data = self._post("/x-api/fiat/otc/item/create", self.create_ad_body)
@@ -363,7 +394,8 @@ class AgentClient(BaseAgentClient, InAgentClient): # Bybit client
363
394
  data = self.api.post_new_ad(**ad.model_dump())
364
395
  return data["result"]["itemId"] if data["ret_code"] == 0 else data
365
396
 
366
- def ad_upd(self, upd: AdUpdateRequest):
397
+ async def _ad_upd(self, req: AdUpd):
398
+ upd = AdUpdateRequest({})
367
399
  params = upd.model_dump()
368
400
  data = self.api.update_ad(**params)
369
401
  return data["result"] if data["ret_code"] == 0 else data
@@ -487,10 +519,10 @@ class AgentClient(BaseAgentClient, InAgentClient): # Bybit client
487
519
  {"makerUserId": self.actor.exid, "page": "1", "size": "10", "appraiseType": "1"}, # "0" - bad
488
520
  )
489
521
 
490
- async def get_orders_active(
522
+ async def get_pending_orders(
491
523
  self, side: int = None, status: int = None, begin_time: int = None, end_time: int = None, token_id: str = None
492
524
  ):
493
- return await self._post(
525
+ res = await self._post(
494
526
  "/x-api/fiat/otc/order/pending/simplifyList",
495
527
  {
496
528
  "status": status,
@@ -499,9 +531,12 @@ class AgentClient(BaseAgentClient, InAgentClient): # Bybit client
499
531
  "endTime": end_time,
500
532
  "side": side, # 1 - продажа, 0 - покупка
501
533
  "page": 1,
502
- "size": 10,
534
+ "size": 20,
503
535
  },
504
536
  )
537
+ if res["ret_code"] == 0:
538
+ return {o.id: OrderItem(**o) for o in res["result"]["items"]}
539
+ return res["ret_code"]
505
540
 
506
541
  def get_orders_done(self, begin_time: int, end_time: int, status: int, side: int, token_id: str):
507
542
  return self._post(
@@ -517,27 +552,27 @@ class AgentClient(BaseAgentClient, InAgentClient): # Bybit client
517
552
  },
518
553
  )
519
554
 
520
- async def create_order(self, order: OrderFull) -> models.Order:
521
- # ad = Ad(**self.api.get_ad_details(itemId=order.itemId)["result"])
522
- await sleep(1)
555
+ async def create_order_db(self, order: OrderFull) -> models.Order:
523
556
  curex = await models.CurEx.get_or_none(ex=self.ex_client.ex, exid=order.currencyId).prefetch_related("cur")
524
557
  cur_scale = (curex.scale if curex.scale is not None else curex.cur.scale) if curex else 2
525
558
  coinex = await models.CoinEx.get(ex=self.ex_client.ex, exid=order.tokenId).prefetch_related("coin")
526
559
  coin_scale = coinex.scale if coinex.scale is not None else coinex.cur.scale
527
- maker_name = order.sellerRealName, order.buyerRealName
528
- im_maker = int(order.makerUserId == order.userId)
560
+ sb_names = order.sellerRealName, order.buyerRealName
561
+ im_maker = int(int(order.makerUserId) == self.actor.exid)
529
562
  taker_id = (order.userId, order.targetUserId)[im_maker]
530
- taker_person = await self.ex_client.person_name_update(maker_name[::-1][order.side], taker_id)
563
+ taker_name = sb_names[order.side] # todo: double check
564
+ taker_person = await self.ex_client.person_name_update(taker_name, taker_id)
531
565
  seller_person = (
532
566
  self.actor.person
533
567
  if order.side
534
568
  else await self.ex_client.person_name_update(order.sellerRealName, int(order.targetUserId))
535
569
  )
536
570
  taker_nick = (self.actor.name, order.targetNickName)[im_maker] # todo: check
537
- # ad_db, cond_isnew = await self.ex_client.cond_load(ad, force=True, rname=maker_name[order.side])
538
571
  ad_db = await models.Ad.get(exid=order.itemId)
539
572
  if not ad_db:
540
- ...
573
+ ad = self.get_ad(order.itemId)
574
+ # ad_db, cond_isnew = await self.ex_client.cond_load(ad, force=True, rname=maker_name[order.side])
575
+ ad_db = await self.ex_client.ad_load(ad, maker=self.actor)
541
576
  ecredex: CredEpyd = order.confirmedPayTerm
542
577
 
543
578
  if ecredex.paymentType == 0 and im_maker and order.side:
@@ -695,7 +730,7 @@ class AgentClient(BaseAgentClient, InAgentClient): # Bybit client
695
730
  continue
696
731
  fo = self.api.get_order_details(orderId=o.id)
697
732
  order = OrderFull.model_validate(fo["result"])
698
- order_db = await self.create_order(order)
733
+ order_db = await self.create_order_db(order)
699
734
  await sleep(1)
700
735
  dmsgs = self.api.get_chat_messages(orderId=oid, size=200)["result"]["result"][::-1]
701
736
  msgs = [Message.model_validate(m) for m in dmsgs if m["msgType"] in (1, 2, 7, 8)]
@@ -720,54 +755,6 @@ class AgentClient(BaseAgentClient, InAgentClient): # Bybit client
720
755
  # for t in papi.history():
721
756
  # os = self.api.get_orders(page=1, size=30)
722
757
 
723
- async def mad_upd(self, mad: Ad, attrs: dict, cxids: list[str]):
724
- if not [setattr(mad, k, v) for k, v in attrs.items() if getattr(mad, k) != v]:
725
- print(end="v" if mad.side else "^", flush=True)
726
- return await sleep(5)
727
- req = AdUpdateRequest.model_validate({**mad.model_dump(), "paymentIds": cxids})
728
- try:
729
- return self.ad_upd(req)
730
- except FailedRequestError as e:
731
- if ExcCode(e.status_code) == ExcCode.FixPriceLimit:
732
- if limits := re.search(
733
- r"The fixed price set is lower than ([0-9]+\.?[0-9]{0,2}) or higher than ([0-9]+\.?[0-9]{0,2})",
734
- e.message,
735
- ):
736
- return await self.mad_upd(mad, {"price": limits.group(1 if mad.side else 2)}, cxids)
737
- elif ExcCode(e.status_code) == ExcCode.RareLimit:
738
- await sleep(180)
739
- else:
740
- raise e
741
- except (ReadTimeoutError, ConnectionDoesNotExistError):
742
- logging.warning("Connection failed. Restarting..")
743
- print("-" if mad.side else "+", end=req.price, flush=True)
744
- await sleep(60)
745
-
746
- def overprice_filter(self, ads: list[Ad], ceil: float, k: Literal[-1, 1]):
747
- # вырезаем ads с ценами выше потолка
748
- if ads and (ceil - float(ads[0].price)) * k > 0:
749
- if int(ads[0].userId) != self.actor.exid:
750
- ads.pop(0)
751
- self.overprice_filter(ads, ceil, k)
752
-
753
- def get_cad(self, ads: list[Ad], ceil: float, k: Literal[-1, 1], target_place: int, cur_plc: int) -> Ad:
754
- if not ads:
755
- return None
756
- # чью цену будем обгонять, предыдущей или слещующей объявы?
757
- # cad: Ad = ads[place] if cur_plc > place else ads[cur_plc]
758
- # переделал пока на жесткую установку целевого места, даже если текущее выше:
759
- if len(ads) <= target_place:
760
- logging.error(f"target place {target_place} not found in ads {len(ads)}-lenght list")
761
- target_place = len(ads) - 1
762
- cad: Ad = ads[target_place]
763
- # а цена обгоняемой объявы не выше нашего потолка?
764
- if (float(cad.price) - ceil) * k <= 0:
765
- # тогда берем следующую
766
- ads.pop(target_place)
767
- cad = self.get_cad(ads, ceil, k, target_place, cur_plc)
768
- # todo: добавить фильтр по лимитам min-max
769
- return cad
770
-
771
758
  # @staticmethod
772
759
  # def premium_up(mad: Ad, cad: Ad, k: Literal[-1, 1]):
773
760
  # mpc, mpm, cpc, cpm = Decimal(mad.price), Decimal(mad.premium), Decimal(cad.price), Decimal(cad.premium)
@@ -777,235 +764,6 @@ class AgentClient(BaseAgentClient, InAgentClient): # Bybit client
777
764
  # if round(cpc * new_premium / cpm, 2) == m
778
765
  # mad.premium = new_premium.to_eng_string()
779
766
 
780
- async def start_race(self):
781
- races = await models.Race.filter(started=True, road__ad__maker_id=self.actor.id).prefetch_related(
782
- "road__ad__pair_side__pair__cur", "road__credexs__cred", "road__ad__pms__pmexs__pm"
783
- )
784
- tasks = [create_task(self.racing(race), name=f"Rc{race.id}") for race in races]
785
- return await gather(*tasks)
786
-
787
- async def racing(self, race: models.Race):
788
- coinex: models.CoinEx = await models.CoinEx.get(
789
- coin_id=race.road.ad.pair_side.pair.coin_id, ex=self.actor.ex
790
- ).prefetch_related("coin")
791
- curex: models.CurEx = await models.CurEx.get(
792
- cur_id=race.road.ad.pair_side.pair.cur_id, ex=self.actor.ex
793
- ).prefetch_related("cur")
794
- taker_side: bool = not race.road.ad.pair_side.is_sell
795
- creds = [c.cred for c in race.road.credexs]
796
- pmexs: list[models.PmEx] = [pmex for pm in race.road.ad.pms for pmex in pm.pmexs if pmex.ex_id == 4]
797
- post_pm_ids = {c.cred.ovr_pm_id for c in race.road.credexs if c.cred.ovr_pm_id}
798
- post_pmexs = set(await models.PmEx.filter(pm_id__in=post_pm_ids, ex=self.actor.ex).prefetch_related("pm"))
799
-
800
- k = (-1) ** int(taker_side) # on_buy=1, on_sell=-1
801
- sleep_sec = 3 # 1 if set(pms) & {"volet"} and coinex.coin_id == 1 else 5
802
- _lstat, volume = None, 0
803
-
804
- while self.actor.person.user.status > 0:
805
- # обновляем все обновления по текущей гонке из бд
806
- await race.refresh_from_db()
807
- if not race.started:
808
- await sleep(5)
809
- continue
810
- # если гонка дольше Х минут не обновлялась, обновляем ее (и ее пары) потолок
811
- expiration = datetime.now(timezone.utc) - timedelta(minutes=15)
812
- amt = race.filter_amount * 10**-curex.cur.scale if race.filter_amount else None
813
- if race.updated_at < expiration:
814
- ceils, hp, vmf, zplace = await self.get_ceils(coinex, curex, pmexs, 0.003, False, 0, amt, post_pmexs)
815
- race.ceil = int(ceils[int(taker_side)] * 10**curex.scale)
816
- await race.save()
817
- # upd pair race
818
- if prace := await models.Race.annotate(pms_count=Count("road__ad__pms")).get_or_none(
819
- road__ad__pair_side__pair_id=race.road.ad.pair_side.pair_id,
820
- road__ad__pair_side__is_sell=taker_side,
821
- road__ad__maker=self.actor,
822
- updated_at__lt=expiration,
823
- road__credexs__id__in=[c.id for c in race.road.credexs],
824
- pms_count=len(pmexs),
825
- ):
826
- prace.ceil = int(ceils[int(not taker_side)] * 10**curex.scale)
827
- await prace.save()
828
-
829
- last_vol = volume
830
- if taker_side: # гонка в стакане продажи - мы покупаем монету за ФИАТ
831
- fiat = max(await models.Fiat.filter(cred_id__in=[c.id for c in creds]), key=lambda x: x.amount)
832
- volume = (fiat.amount * 10**-curex.cur.scale) / (race.road.ad.price * 10**-curex.scale)
833
- else: # гонка в стакане покупки - мы продаем МОНЕТУ за фиат
834
- asset = await models.Asset.get(addr__actor=self.actor, addr__coin_id=coinex.coin_id)
835
- volume = asset.free * 10**-coinex.scale
836
- volume = str(round(volume, coinex.scale))
837
- try:
838
- ads: list[Ad] = await self.ads(coinex, curex, taker_side, pmexs, amt, 50, race.vm_filter, post_pmexs)
839
- except Exception:
840
- await sleep(1)
841
- ads: list[Ad] = await self.ads(coinex, curex, taker_side, pmexs, amt, 50, race.vm_filter, post_pmexs)
842
-
843
- self.overprice_filter(ads, race.ceil * 10**-curex.scale, k) # обрезаем сверху все ads дороже нашего потолка
844
-
845
- if not ads:
846
- print(coinex.exid, curex.exid, taker_side, "no ads!")
847
- await sleep(15)
848
- continue
849
- # определяем наше текущее место в уже обрезанном списке ads
850
- if not (cur_plc := [i for i, ad in enumerate(ads) if int(ad.userId) == self.actor.exid]):
851
- logging.warning(f"No racing in {pmexs[0].name} {'-' if taker_side else '+'}{coinex.exid}/{curex.exid}")
852
- await sleep(15)
853
- continue
854
- (cur_plc,) = cur_plc # может упасть если в списке > 1 наш ad
855
- [(await self.ex_client.cond_load(ad, race.road.ad.pair_side, True))[0] for ad in ads[:cur_plc]]
856
- # rivals = [
857
- # (await models.RaceStat.update_or_create({"place": plc, "price": ad.price, "premium": ad.premium}, ad=ad))[
858
- # 0
859
- # ]
860
- # for plc, ad in enumerate(rads)
861
- # ]
862
- mad: Ad = ads.pop(cur_plc)
863
- # if (
864
- # not (lstat := lstat or await race.stats.order_by("-created_at").first())
865
- # or lstat.place != cur_plc
866
- # or lstat.price != float(mad.price)
867
- # or set(rivals) != set(await lstat.rivals)
868
- # ):
869
- # lstat = await models.RaceStat.create(race=race, place=cur_plc, price=mad.price, premium=mad.premium)
870
- # await lstat.rivals.add(*rivals)
871
- if not ads:
872
- await sleep(60)
873
- continue
874
- if not (cad := self.get_cad(ads, race.ceil * 10**-curex.scale, k, race.target_place, cur_plc)):
875
- continue
876
- new_price = round(float(cad.price) - k * step(mad, cad, curex.scale), curex.scale)
877
- if (
878
- float(mad.price) == new_price and volume == last_vol
879
- ): # Если место уже нужное или нужная цена и так уже стоит
880
- print(
881
- f"{'v' if taker_side else '^'}{mad.price}",
882
- end=f"[{race.ceil * 10**-curex.scale}+{cur_plc}] ",
883
- flush=True,
884
- )
885
- await sleep(sleep_sec)
886
- continue
887
- if cad.priceType: # Если цена конкурента плавающая, то повышаем себе не цену, а %
888
- new_premium = (float(mad.premium) or float(cad.premium)) - k * step(mad, cad, 2)
889
- # if float(mad.premium) == new_premium: # Если нужный % и так уже стоит
890
- # if mad.priceType and cur_plc != race.target_place:
891
- # new_premium -= k * step(mad, cad, 2)
892
- # elif volume == last_vol:
893
- # print(end="v" if taker_side else "^", flush=True)
894
- # await sleep(sleep_sec)
895
- # continue
896
- mad.premium = str(round(new_premium, 2))
897
- mad.priceType = cad.priceType
898
- mad.quantity = volume
899
- mad.maxAmount = str(2_000_000 if curex.cur_id == 1 else 40_000)
900
- req = AdUpdateRequest.model_validate(
901
- {
902
- **mad.model_dump(),
903
- "price": str(round(new_price, curex.scale)),
904
- "paymentIds": [str(cx.exid) for cx in race.road.credexs],
905
- }
906
- )
907
- try:
908
- print(
909
- f"c{race.ceil * 10**-curex.scale}+{cur_plc} {coinex.coin.ticker}{'-' if taker_side else '+'}{req.price}{curex.cur.ticker}"
910
- f"{[pm.norm for pm in race.road.ad.pms]}{f'({req.premium}%)' if req.premium != '0' else ''} "
911
- f"t{race.target_place} ;",
912
- flush=True,
913
- )
914
- _res = self.ad_upd(req)
915
- except FailedRequestError as e:
916
- if ExcCode(e.status_code) == ExcCode.FixPriceLimit:
917
- if limits := re.search(
918
- r"The fixed price set is lower than ([0-9]+\.?[0-9]{0,2}) or higher than ([0-9]+\.?[0-9]{0,2})",
919
- e.message,
920
- ):
921
- req.price = limits.group(1 if taker_side else 2)
922
- if req.price != mad.price:
923
- _res = self.ad_upd(req)
924
- else:
925
- raise e
926
- elif ExcCode(e.status_code) == ExcCode.InsufficientBalance:
927
- asset = await models.Asset.get(addr__actor=self.actor, addr__coin_id=coinex.coin_id)
928
- req.quantity = str(round(asset.free * 10**-coinex.scale, coinex.scale))
929
- _res = self.ad_upd(req)
930
- elif ExcCode(e.status_code) == ExcCode.RareLimit:
931
- if not (
932
- sads := [
933
- ma
934
- for ma in self.my_ads(False)
935
- if (
936
- ma.currencyId == curex.exid
937
- and ma.tokenId == coinex.exid
938
- and taker_side != ma.side
939
- and set(ma.payments) == set([pe.exid for pe in pmexs])
940
- )
941
- ]
942
- ):
943
- logging.error(f"Need reserve Ad {'sell' if taker_side else 'buy'} {coinex.exid}/{curex.exid}")
944
- await sleep(90)
945
- continue
946
- self.ad_del(ad_id=int(mad.id))
947
- req.id = sads[0].id
948
- req.actionType = "ACTIVE"
949
- self.api.update_ad(**req.model_dump())
950
- logging.warning(f"Ad#{mad.id} recreated")
951
- elif ExcCode(e.status_code) == ExcCode.Timestamp:
952
- await sleep(3)
953
- else:
954
- raise e
955
- except (ReadTimeoutError, ConnectionDoesNotExistError):
956
- logging.warning("Connection failed. Restarting..")
957
- await sleep(6)
958
-
959
- async def get_books(
960
- self,
961
- coinex: models.CoinEx,
962
- curex: models.CurEx,
963
- pmexs: list[models.PmEx],
964
- amount: int,
965
- post_pmexs: list[models.PmEx] = None,
966
- ) -> tuple[list[Ad], list[Ad]]:
967
- buy: list[Ad] = await self.ads(coinex, curex, False, pmexs, amount, 40, False, post_pmexs)
968
- sell: list[Ad] = await self.ads(coinex, curex, True, pmexs, amount, 30, False, post_pmexs)
969
- return buy, sell
970
-
971
- async def get_spread(
972
- self, bb: list[Ad], sb: list[Ad], perc: float, vmf: bool = None, place: int = 0, exact: bool = False
973
- ) -> tuple[tuple[float, float], float, bool, int]:
974
- if len(bb) <= place or len(sb) <= place:
975
- ...
976
- buy_price, sell_price = float(bb[place].price), float(sb[place].price)
977
- half_spread = (buy_price - sell_price) / (buy_price + sell_price)
978
- # if half_spread * 2 < perc: # todo: aA???
979
- # if not exact:
980
- # if vmf is None: # сначала фильтруем только VA
981
- # return await self.get_spread(bb, sb, perc, True, place)
982
- # # если даже по VA не хватает спреда - увеличиваем место
983
- # return await self.get_spread(bb, sb, perc, vmf, place + 1)
984
-
985
- return (buy_price, sell_price), half_spread, vmf, place
986
-
987
- async def get_ceils(
988
- self,
989
- coinex: models.CoinEx,
990
- curex: models.CurEx,
991
- pmexs: list[models.PmEx],
992
- min_prof=0.02,
993
- vmf: bool = False,
994
- place: int = 0,
995
- amount: int = None,
996
- post_pmexs: set[models.PmEx] = None,
997
- ) -> tuple[tuple[float, float], float, bool, int]: # todo: refact to Pairex
998
- bb, sb = await self.get_books(coinex, curex, pmexs, amount, post_pmexs)
999
- if vmf:
1000
- # ориентируемся на цены объявлений только проверенных мерчантов
1001
- bb = [b for b in bb if "VA" in b.authTag]
1002
- sb = [s for s in sb if "VA" in s.authTag]
1003
- perc = list(post_pmexs or pmexs)[0].pm.fee * 0.0001 + min_prof
1004
- (bf, sf), hp, vmf, zplace = await self.get_spread(bb, sb, perc, vmf, place)
1005
- mdl = (bf + sf) / 2
1006
- bc, sc = mdl + mdl * (perc / 2), mdl - mdl * (perc / 2)
1007
- return (bc, sc), hp, vmf, zplace
1008
-
1009
767
  async def take_ad(self, req: TakeAdReq):
1010
768
  if req.price and req.is_sell and req.cur_:
1011
769
  ... # todo call the get_ad_details() only if lack of data
@@ -1046,83 +804,90 @@ class AgentClient(BaseAgentClient, InAgentClient): # Bybit client
1046
804
  return resp
1047
805
 
1048
806
  async def watch_payeer(self, mcs: dict[int, "AgentClient"]):
1049
- coinex: models.CoinEx = await models.CoinEx.get(coin_id=1, ex=self.actor.ex).prefetch_related("coin")
1050
- curex: models.CurEx = await models.CurEx.get(cur_id=1, ex=self.actor.ex).prefetch_related("cur")
807
+ await models.CoinEx.get(coin_id=1, ex=self.actor.ex).prefetch_related("coin")
808
+ await models.CurEx.get(cur_id=1, ex=self.actor.ex).prefetch_related("cur")
1051
809
  post_pmexs = set(await models.PmEx.filter(pm_id=366, ex=self.actor.ex).prefetch_related("pm"))
1052
810
  i = 0
1053
811
  while True:
1054
812
  try:
1055
- ss = await self.ads(coinex, curex, True, None, None, 1000, False, post_pmexs)
1056
- ss = [s for s in ss if float(s.price) > 90.42 or int(s.userId) in mcs.keys()]
1057
- if ss:
1058
- ad: Ad = ss[0]
813
+ breq = GetAds(coin_id=1, cur_id=1, is_sell=False, limit=50)
814
+ bs = await self.ex_client.ads(breq, post_pmexs=post_pmexs)
815
+ bs = [b for b in bs if float(b.price) < 100 or int(b.userId) in mcs.keys()]
816
+ if bs:
817
+ ad: Ad = bs[0]
1059
818
  await self.bbot.send(
1060
819
  193017646,
1061
820
  f"price: {ad.price}\nnick: {ad.nickName}\nprice: {ad.price}"
1062
821
  f"\nqty: {ad.quantity} [{ad.minAmount}-{ad.maxAmount}]",
1063
822
  )
1064
- am = min(float(ad.maxAmount), max(1000 + i, float(ad.minAmount)))
823
+ am = min(float(ad.maxAmount), max(8000 + i, float(ad.minAmount)))
1065
824
  req = TakeAdReq(
1066
825
  ad_id=ad.id,
1067
826
  amount=am,
1068
827
  pm_id=14,
1069
- is_sell=True,
828
+ is_sell=False,
1070
829
  coin_id=1,
1071
830
  cur_id=1,
1072
831
  )
1073
832
  ord_resp: OrderResp = await self.take_ad(req)
1074
833
  # order: OrderFull = OrderFull(**self.api.get_order_details(orderId=ord_resp.orderId)["result"])
1075
834
  order: OrderFull = await self.get_order_info(ord_resp.orderId)
1076
- odb = await self.create_order(order)
1077
- # t = await models.Transfer(order=odb, amount=odb.amount, updated_at=now())
1078
- # await t.fetch_related("order__cred__pmcur__cur")
1079
- # res = await self.pm_clients[366].check_in(t)
835
+ odb = await self.create_order_db(order)
836
+ t = await models.Transfer(order=odb, amount=odb.amount, updated_at=now())
837
+ await t.fetch_related("order__cred__pmcur__cur")
838
+ # res = await self.pm_clients[366].send(t)
839
+ await sleep(2)
840
+ self.api.mark_as_paid(
841
+ orderId=str(odb.exid),
842
+ paymentType=str(order.paymentTermList[0].paymentType), # pmex.exid
843
+ paymentId=order.paymentTermList[0].id, # credex.exid
844
+ )
845
+ await sleep(3)
1080
846
  if int(ad.userId) in mcs:
1081
- mcs[int(ad.userId)].api.mark_as_paid(
1082
- orderId=str(odb.exid),
1083
- paymentType=ad.payments[0], # pmex.exid
1084
- paymentId=order.paymentTermList[0].id, # credex.exid
1085
- )
1086
- self.api.release_assets(orderId=order.id)
1087
- ...
847
+ mcs[int(ad.userId)].api.release_assets(orderId=order.id)
1088
848
 
1089
- bs = await self.ads(coinex, curex, False, None, None, 1000, False, post_pmexs)
1090
- bs = [b for b in bs if float(b.price) < 89.56 or int(b.userId) in mcs.keys()]
1091
- if bs:
1092
- ad: Ad = bs[0]
849
+ await sleep(5)
850
+
851
+ sreq = GetAds(coin_id=1, cur_id=1, is_sell=True, limit=50, kwargs={"post_pmexs": post_pmexs})
852
+ ss = await self.ex_client.ads(sreq, post_pmexs=post_pmexs)
853
+ ss = [s for s in ss if float(s.price) > 92 or int(s.userId) in mcs.keys()]
854
+ if ss:
855
+ ad: Ad = ss[0]
1093
856
  await self.bbot.send(
1094
857
  193017646,
1095
858
  f"price: {ad.price}\nnick: {ad.nickName}\nprice: {ad.price}"
1096
859
  f"\nqty: {ad.quantity} [{ad.minAmount}-{ad.maxAmount}]",
1097
860
  )
1098
- am = min(float(ad.maxAmount), max(600 + i, float(ad.minAmount)))
861
+ am = min(float(ad.maxAmount), max(10000 + i, float(ad.minAmount)))
1099
862
  req = TakeAdReq(
1100
863
  ad_id=ad.id,
1101
864
  amount=am,
1102
865
  pm_id=14,
1103
- is_sell=False,
866
+ is_sell=True,
1104
867
  coin_id=1,
1105
868
  cur_id=1,
1106
869
  )
1107
870
  ord_resp: OrderResp = await self.take_ad(req)
1108
871
  # order: OrderFull = OrderFull(**self.api.get_order_details(orderId=ord_resp.orderId)["result"])
1109
872
  order: OrderFull = await self.get_order_info(ord_resp.orderId)
1110
- odb = await self.create_order(order)
1111
- t = await models.Transfer(order=odb, amount=odb.amount, updated_at=now())
1112
- await t.fetch_related("order__cred__pmcur__cur")
1113
- # res = await self.pm_clients[366].send(t)
1114
- self.api.mark_as_paid(
1115
- orderId=str(odb.exid),
1116
- paymentType=ad.payments[0], # pmex.exid
1117
- paymentId=order.paymentTermList[0].id, # credex.exid
1118
- )
873
+ odb = await self.create_order_db(order)
874
+ # t = await models.Transfer(order=odb, amount=odb.amount, updated_at=now())
875
+ # await t.fetch_related("order__cred__pmcur__cur")
876
+ # res = await self.pm_clients[366].check_in(t)
877
+ await sleep(2)
1119
878
  if int(ad.userId) in mcs:
1120
- mcs[int(ad.userId)].api.release_assets(orderId=order.id)
1121
- await sleep(1)
1122
- ...
879
+ mcs[int(ad.userId)].api.mark_as_paid(
880
+ orderId=str(odb.exid),
881
+ paymentType=str(order.paymentTermList[0].paymentType), # pmex.exid
882
+ paymentId=order.paymentTermList[0].id, # credex.exid
883
+ )
884
+ await sleep(3)
885
+ self.api.release_assets(orderId=order.id)
886
+ await sleep(5)
887
+
1123
888
  except Exception as e:
1124
889
  logging.exception(e)
1125
- await sleep(90)
890
+ await sleep(30)
1126
891
  except HttpProcessingError as e:
1127
892
  logging.error(e)
1128
893
  print(end=".", flush=True)
@@ -1136,12 +901,389 @@ class AgentClient(BaseAgentClient, InAgentClient): # Bybit client
1136
901
  req = TakeAdReq(ad_id="1856989782009487360", amount=am, pm_id=366)
1137
902
  ord_resp: OrderResp = await self.take_ad(req)
1138
903
  order: OrderFull = OrderFull(**self.api.get_order_details(orderId=ord_resp.orderId)["result"])
1139
- odb = await self.create_order(order)
904
+ odb = await self.create_order_db(order)
1140
905
  t = await models.Transfer(order=odb, amount=odb.amount, updated_at=now())
1141
906
  await t.fetch_related("order__cred__pmcur__cur")
1142
907
  await self.pm_clients[366].send(t)
1143
908
  ...
1144
909
 
910
+ pm_clients: dict[int, PmAgentClient]
911
+
912
+ async def start_listen(self):
913
+ t = await self.ott()
914
+ ts = int(float(t["time_now"]) * 1000)
915
+ await self.ws_prv(self.agent.auth["cookies"]["deviceId"], t["result"], ts)
916
+
917
+ # 3N: [T] - Уведомление об одобрении запроса на сделку
918
+ async def request_accepted_notify(self) -> int: ... # id
919
+
920
+ async def ws_prv(self, did: str, tok: str, ts: int):
921
+ u = f"wss://ws2.bybit.com/private?appid=bybit&os=web&deviceid={did}&timestamp={ts}"
922
+ async with websockets.connect(u) as websocket:
923
+ auth_msg = json.dumps({"req_id": did, "op": "login", "args": [tok]})
924
+ await websocket.send(auth_msg)
925
+
926
+ sub_msg = json.dumps({"op": "subscribe", "args": ["FIAT_OTC_TOPIC", "FIAT_OTC_ONLINE_TOPIC"]})
927
+ await websocket.send(sub_msg)
928
+ sub_msg = json.dumps({"op": "input", "args": ["FIAT_OTC_TOPIC", '{"topic":"SUPER_DEAL"}']})
929
+ await websocket.send(sub_msg)
930
+ sub_msg = json.dumps({"op": "input", "args": ["FIAT_OTC_TOPIC", '{"topic":"OTC_ORDER_STATUS"}']})
931
+ await websocket.send(sub_msg)
932
+ sub_msg = json.dumps({"op": "input", "args": ["FIAT_OTC_TOPIC", '{"topic":"WEB_THREE_SELL"}']})
933
+ await websocket.send(sub_msg)
934
+ sub_msg = json.dumps({"op": "input", "args": ["FIAT_OTC_TOPIC", '{"topic":"APPEALED_CHANGE"}']})
935
+ await websocket.send(sub_msg)
936
+
937
+ sub_msg = json.dumps({"op": "subscribe", "args": ["fiat.cashier.order"]})
938
+ await websocket.send(sub_msg)
939
+ sub_msg = json.dumps({"op": "subscribe", "args": ["fiat.cashier.order-eftd-complete-privilege-event"]})
940
+ await websocket.send(sub_msg)
941
+ sub_msg = json.dumps({"op": "subscribe", "args": ["fiat.cashier.order-savings-product-event"]})
942
+ await websocket.send(sub_msg)
943
+ sub_msg = json.dumps({"op": "subscribe", "args": ["fiat.deal-core.order-savings-complete-event"]})
944
+ await websocket.send(sub_msg)
945
+
946
+ sub_msg = json.dumps({"op": "subscribe", "args": ["FIAT_OTC_TOPIC", "FIAT_OTC_ONLINE_TOPIC"]})
947
+ await websocket.send(sub_msg)
948
+ while resp := await websocket.recv():
949
+ if data := json.loads(resp):
950
+ upd, order_db = None, None
951
+ logging.info(f" {now().strftime('%H:%M:%S')} upd: {data.get('topic')}:{data.get('type')}")
952
+ match data.get("topic"):
953
+ case "OTC_ORDER_STATUS":
954
+ match data["type"]:
955
+ case "STATUS_CHANGE":
956
+ try:
957
+ upd = StatusChange.model_validate(data["data"])
958
+ except ValidationError as e:
959
+ logging.error(e)
960
+ logging.error(data["data"])
961
+ order = self.api.get_order_details(orderId=upd.id)
962
+ order = OrderFull.model_validate(order["result"])
963
+ order_db = await models.Order.get_or_none(
964
+ exid=order.id, ad__exid=order.itemId
965
+ ) or await self.create_order_db(order)
966
+ match upd.status:
967
+ case Status.created:
968
+ logging.info(f"Order {order.id} created at {order.createDate}")
969
+ # сразу уменьшаем доступный остаток монеты/валюты
970
+ await self.money_upd(order_db)
971
+ if upd.side: # я покупатель - ждем мою оплату
972
+ _dest = order.paymentTermList[0].accountNo
973
+ if not re.match(r"^([PpРр])\d{7,10}\b", _dest):
974
+ continue
975
+ await order_db.fetch_related("ad__pair_side__pair", "cred__pmcur__cur")
976
+ await self.send_payment(order_db)
977
+ case Status.wait_for_buyer:
978
+ if upd.side == 0: # ждем когда покупатель оплатит
979
+ if not (pmacdx := await self.get_pma_by_cdex(order)):
980
+ continue
981
+ pma, cdx = pmacdx
982
+ am, tid = await pma.check_in(
983
+ float(order.amount),
984
+ cdx.cred.pmcur.cur.ticker,
985
+ # todo: почему в московском час.поясе?
986
+ datetime.fromtimestamp(float(order.transferDate) / 1000),
987
+ )
988
+ if not tid:
989
+ logging.info(
990
+ f"Order {order.id} created at {order.createDate}, not paid yet"
991
+ )
992
+ continue
993
+ try:
994
+ t, is_new = await models.Transfer.update_or_create(
995
+ dict(
996
+ amount=int(float(order.amount) * 100),
997
+ order=order_db,
998
+ ),
999
+ pmid=tid,
1000
+ )
1001
+ except IntegrityError as e:
1002
+ logging.error(tid)
1003
+ logging.error(order)
1004
+ logging.exception(e)
1005
+
1006
+ if not is_new: # если по этому платежу уже отпущен другая продажа
1007
+ continue
1008
+
1009
+ # если висят незавершенные продажи с такой же суммой
1010
+ pos = (await self.get_pending_orders(1))["result"]
1011
+ pos = [
1012
+ o
1013
+ for o in pos.get("items", [])
1014
+ if (
1015
+ o["amount"] == order.amount
1016
+ and o["id"] != upd.id
1017
+ and int(order.createDate)
1018
+ < int(o["createDate"]) + 15 * 60 * 1000
1019
+ # get full_order from o, and cred or pm from full_order:
1020
+ and self.api.get_order_details(orderId=o["id"])["result"][
1021
+ "paymentTermList"
1022
+ ][0]["accountNo"]
1023
+ == order.paymentTermList[0].accountNo
1024
+ )
1025
+ ]
1026
+ curex = await models.CurEx.get(
1027
+ cur__ticker=order.currencyId, ex=self.ex_client.ex
1028
+ )
1029
+ pos_db = await models.Order.filter(
1030
+ exid__not=order.id,
1031
+ cred_id=order_db.cred_id,
1032
+ amount=int(float(order.amount) * 10**curex.scale),
1033
+ status__not_in=[OrderStatus.completed, OrderStatus.canceled],
1034
+ created_at__gt=now() - timedelta(minutes=15),
1035
+ )
1036
+ if pos or pos_db:
1037
+ await self.ex_client.bot.send(
1038
+ f"[Duplicate amount!]"
1039
+ f"(https://www.bybit.com/ru-RU/p2p/orderList/{order.id})",
1040
+ self.actor.person.user.username_id,
1041
+ )
1042
+ logging.warning("Duplicate amount!")
1043
+ continue
1044
+
1045
+ # !!! ОТПРАВЛЯЕМ ДЕНЬГИ !!!
1046
+ self.api.release_assets(orderId=upd.id)
1047
+ logging.info(
1048
+ f"Order {order.id} created, paid before #{tid}:{am} at {order.createDate}, and RELEASED at {now()}"
1049
+ )
1050
+ elif upd.side == 1: # я покупатель - ждем мою оплату
1051
+ continue # logging.warning(f"Order {order.id} PAID at {now()}: {int_am}")
1052
+ else:
1053
+ ...
1054
+ # todo: check is always canceling
1055
+ # await order_db.update_from_dict({"status": OrderStatus.canceled}).save()
1056
+ # logging.info(f"Order {order.id} canceled at {datetime.now()}")
1057
+
1058
+ case Status.wait_for_seller:
1059
+ if order_db.status == OrderStatus.paid:
1060
+ continue
1061
+ await order_db.update_from_dict(
1062
+ {
1063
+ "status": OrderStatus.paid,
1064
+ "payed_at": datetime.fromtimestamp(
1065
+ float(order.transferDate) / 1000
1066
+ ),
1067
+ }
1068
+ ).save()
1069
+ logging.info(f"Order {order.id} payed at {order_db.payed_at}")
1070
+
1071
+ case Status.appealed:
1072
+ # todo: appealed by WHO? щас наугад стоит by_seller
1073
+ await order_db.update_from_dict(
1074
+ {
1075
+ "status": OrderStatus.appealed_by_seller,
1076
+ "appealed_at": datetime.fromtimestamp(
1077
+ float(order.updateDate) / 1000
1078
+ ),
1079
+ }
1080
+ ).save()
1081
+ logging.info(f"Order {order.id} appealed at {order_db.appealed_at}")
1082
+
1083
+ case Status.canceled:
1084
+ await order_db.update_from_dict({"status": OrderStatus.canceled}).save()
1085
+ logging.info(f"Order {order.id} canceled at {datetime.now()}")
1086
+ await self.money_upd(order_db)
1087
+
1088
+ case Status.completed:
1089
+ await order_db.update_from_dict(
1090
+ {
1091
+ "status": OrderStatus.completed,
1092
+ "confirmed_at": datetime.fromtimestamp(
1093
+ float(order.updateDate) / 1000
1094
+ ),
1095
+ }
1096
+ ).save()
1097
+ await self.money_upd(order_db)
1098
+
1099
+ case _:
1100
+ logging.warning(f"Order {order.id} UNKNOWN STATUS {datetime.now()}")
1101
+ case "COUNT_DOWN":
1102
+ upd = CountDown.model_validate(data["data"])
1103
+ case _:
1104
+ self.listen(data)
1105
+ case "OTC_USER_CHAT_MSG":
1106
+ match data["type"]:
1107
+ case "RECEIVE":
1108
+ upd = Receive.model_validate(data["data"])
1109
+ if order_db := await models.Order.get_or_none(
1110
+ exid=upd.orderId, ad__maker__ex=self.actor.ex
1111
+ ).prefetch_related("ad__pair_side__pair", "cred__pmcur__cur"):
1112
+ im_taker = order_db.taker_id == self.actor.id
1113
+ im_buyer = order_db.ad.pair_side.is_sell == im_taker
1114
+ if order_db.ad.auto_msg != upd.message and upd.roleType == "user":
1115
+ msg, _ = await models.Msg.update_or_create(
1116
+ {
1117
+ "to_maker": upd.userId == self.actor.exid and im_taker,
1118
+ "sent_at": datetime.fromtimestamp(float(upd.createDate) / 1000),
1119
+ },
1120
+ txt=upd.message,
1121
+ order=order_db,
1122
+ )
1123
+ if not upd.message:
1124
+ ...
1125
+ if im_buyer and (g := re.match(r"^[PpРр]\d{7,10}\b", upd.message)):
1126
+ if not order_db.cred.detail.startswith(dest := g.group()):
1127
+ order_db.cred.detail = dest
1128
+ await order_db.save()
1129
+ await self.send_payment(order_db)
1130
+ case "READ":
1131
+ upd = Read.model_validate(data["data"])
1132
+ # if upd.status not in (StatusWs.created, StatusWs.canceled, 10, StatusWs.completed):
1133
+ if upd.orderStatus in (
1134
+ Status.wait_for_buyer,
1135
+ ): # todo: тут приходит ордер.статус=10, хотя покупатель еще не нажал оплачено
1136
+ order = self.api.get_order_details(orderId=upd.orderId)["result"]
1137
+ order = OrderFull.model_validate(order)
1138
+
1139
+ case "CLEAR":
1140
+ continue
1141
+ case _:
1142
+ self.listen(data)
1143
+ case "OTC_USER_CHAT_MSG_V2":
1144
+ # match data["type"]:
1145
+ # case "RECEIVE":
1146
+ # upd = Receive.model_validate(data["data"])
1147
+ # case "READ":
1148
+ # upd = Read.model_validate(data["data"])
1149
+ # case "CLEAR":
1150
+ # pass
1151
+ # case _:
1152
+ # self.listen(data)
1153
+ continue
1154
+ case "SELLER_CANCEL_CHANGE":
1155
+ upd = SellerCancelChange.model_validate(data["data"])
1156
+ case None:
1157
+ if not data.get("success"):
1158
+ logging.error(data, "NOT SUCCESS!")
1159
+ else:
1160
+ continue # success login, subscribes, input
1161
+ case _:
1162
+ logging.warning(data, "UNKNOWN TOPIC")
1163
+ ...
1164
+ if not upd:
1165
+ logging.warning(data, "NOT PROCESSED UPDATE")
1166
+
1167
+ async def money_upd(self, odb: models.Order):
1168
+ # обновляем остаток монеты
1169
+ await odb.fetch_related("ad__pair_side__pair", "ad__my_ad__credexs__cred__fiat", "cred__pmcur", "transfer")
1170
+ ass = await models.Asset.get(addr__coin_id=odb.ad.pair_side.pair.coin_id, addr__actor=self.actor)
1171
+ # обновляем остаток валюты
1172
+ im_maker = odb.ad.maker_id == self.actor.id
1173
+ im_seller = odb.ad.pair_side.is_sell == im_maker
1174
+ if im_maker:
1175
+ if _fiats := [cx.cred.fiat for cx in odb.ad.my_ad.credexs if cx.cred.fiat]:
1176
+ fiat = _fiats[0]
1177
+ await fiat.fetch_related("cred__pmcur__pm")
1178
+ else:
1179
+ raise ValueError(odb, "No Fiat")
1180
+ elif im_seller: # im taker
1181
+ fltr = dict(cred__person_id=self.actor.person_id)
1182
+ fltr |= (
1183
+ {"cred__ovr_pm_id": odb.cred.ovr_pm_id, "cred__pmcur__cur_id": odb.cred.pmcur.cur_id}
1184
+ if odb.cred.ovr_pm_id
1185
+ else {"cred__pmcur_id": odb.cred.pmcur_id}
1186
+ )
1187
+ if not (fiat := await models.Fiat.get_or_none(**fltr).prefetch_related("cred__pmcur__pm")):
1188
+ raise ValueError(odb, "No Fiat")
1189
+ fee = round(odb.amount * (fiat.cred.pmcur.pm.fee or 0) * 0.0001)
1190
+ # k = int(im_seller) * 2 - 1 # im_seller: 1, im_buyer: -1
1191
+ if odb.status == OrderStatus.created:
1192
+ if im_seller:
1193
+ ass.free -= odb.quantity
1194
+ ass.freeze += odb.quantity
1195
+ else: # я покупатель
1196
+ fiat.amount -= odb.amount + fee
1197
+ elif odb.status == OrderStatus.completed:
1198
+ if im_seller:
1199
+ fiat.amount += odb.amount
1200
+ else: # я покупатель
1201
+ ass.free += odb.quantity
1202
+ elif odb.status == OrderStatus.canceled:
1203
+ if im_seller:
1204
+ ass.free += odb.quantity
1205
+ ass.freeze -= odb.quantity
1206
+ else: # я покупатель
1207
+ fiat.amount += odb.amount + fee
1208
+ else:
1209
+ logging.exception(odb.id, f"STATUS: {odb.status.name}")
1210
+ await ass.save(update_fields=["free", "freeze"])
1211
+ await fiat.save(update_fields=["amount"])
1212
+ logging.info(f"Order #{odb.id} {odb.status.name}. Fiat: {fiat.amount}, Asset: {ass.free}")
1213
+
1214
+ async def send_payment(self, order_db: models.Order):
1215
+ if order_db.status != OrderStatus.created:
1216
+ return
1217
+ fmt_am = round(order_db.amount * 10**-2, 2)
1218
+ pma, cur = await self.get_pma_by_pmex(order_db)
1219
+ async with in_transaction():
1220
+ # отмечаем ордер на бирже "оплачен"
1221
+ pmex = await models.PmEx.get(pm_id=order_db.cred.pmcur.pm_id, ex=self.actor.ex)
1222
+ credex = await models.CredEx.get(cred=order_db.cred, ex=self.actor.ex)
1223
+ self.api.mark_as_paid(
1224
+ orderId=str(order_db.exid),
1225
+ paymentType=pmex.exid, # pmex.exid
1226
+ paymentId=str(credex.exid), # credex.exid
1227
+ )
1228
+ # проверяем не отправляли ли мы уже перевод по этому ордеру
1229
+ if t := await models.Transfer.get_or_none(order=order_db, amount=order_db.amount):
1230
+ await pma.bot.send(
1231
+ f"Order# {order_db.exid}: Double send {fmt_am}{cur} to {order_db.cred.detail} #{t.pmid}!",
1232
+ self.actor.person.user.username_id,
1233
+ )
1234
+ raise Exception(
1235
+ f"Order# {order_db.exid}: Double send {fmt_am}{cur} to {order_db.cred.detail} #{t.pmid}!"
1236
+ )
1237
+
1238
+ # ставим в бд статус "оплачен"
1239
+ order_db.status = OrderStatus.paid
1240
+ order_db.payed_at = datetime.now(timezone.utc)
1241
+ await order_db.save()
1242
+ # создаем перевод в бд
1243
+ t = models.Transfer(order=order_db, amount=order_db.amount, updated_at=now())
1244
+ # отправляем деньги
1245
+ tid, img = await pma.send(t)
1246
+ t.pmid = tid
1247
+ await t.save()
1248
+ await self.send_receipt(str(order_db.exid), tid) # отправляем продавцу чек
1249
+ logging.info(f"Order {order_db.exid} PAID at {datetime.now()}: {fmt_am}!")
1250
+
1251
+ async def send_receipt(self, oexid: str, tid: int) -> tuple[PmAgentClient | None, models.CredEx] | None:
1252
+ try:
1253
+ if res := self.api.upload_chat_file(upload_file=f"tmp/{tid}.png").get("result"):
1254
+ await sleep(0.5)
1255
+ self.api.send_chat_message(orderId=oexid, contentType="pic", message=res["url"], msgUuid=uuid4().hex)
1256
+ except Exception as e:
1257
+ logging.error(e)
1258
+ await sleep(0.5)
1259
+ self.api.send_chat_message(orderId=oexid, contentType="str", message=f"#{tid}", msgUuid=uuid4().hex)
1260
+
1261
+ async def get_pma_by_cdex(self, order: OrderFull) -> tuple[PmAgentClient | None, models.CredEx] | None:
1262
+ cdxs = await models.CredEx.filter(
1263
+ ex=self.ex_client.ex,
1264
+ exid__in=[ptl.id for ptl in order.paymentTermList],
1265
+ cred__person=self.actor.person,
1266
+ ).prefetch_related("cred__pmcur__cur")
1267
+ pmas = [pma for cdx in cdxs if (pma := self.pm_clients.get(cdx.cred.pmcur.pm_id))]
1268
+ if not len(pmas):
1269
+ # raise ValueError(order.paymentTermList, f"No pm_agents for {order.paymentTermList[0].paymentType}")
1270
+ return None
1271
+ elif len(pmas) > 1:
1272
+ logging.error(order.paymentTermList, f">1 pm_agents for {cdxs[0].cred.pmcur.pm_id}")
1273
+ else:
1274
+ return pmas[0], cdxs[0]
1275
+
1276
+ async def get_pma_by_pmex(self, order_db: models.Order) -> tuple[PmAgentClient, str]:
1277
+ pma = self.pm_clients.get(order_db.cred.pmcur.pm_id)
1278
+ if pma:
1279
+ return pma, order_db.cred.pmcur.cur.ticker
1280
+ logging.error(f"No pm_agents for {order_db.cred.pmcur.pm_id}")
1281
+
1282
+ @staticmethod
1283
+ def listen(data: dict | None):
1284
+ # print(data)
1285
+ ...
1286
+
1145
1287
 
1146
1288
  def ms2utc(msk_ts_str: str):
1147
1289
  return datetime.fromtimestamp(int(msk_ts_str) / 1000, timezone(timedelta(hours=3), name="MSK"))
@@ -1164,23 +1306,6 @@ def detailed_diff(str1, str2):
1164
1306
  return "".join(result)
1165
1307
 
1166
1308
 
1167
- def step_is_need(mad, cad) -> bool:
1168
- # todo: пока не решен непонятный кейс, почему то конкурент по всем параметрам слабже, но в списке ранжируется выше.
1169
- # текущая версия: recentExecuteRate округляется до целого, но на бэке байбита его дробная часть больше
1170
- return (
1171
- bool(set(cad.authTag) & {"VA2", "BA"})
1172
- or cad.recentExecuteRate > mad.recentExecuteRate
1173
- or (
1174
- cad.recentExecuteRate
1175
- == mad.recentExecuteRate # and cad.finishNum > mad.finishNum # пока прибавляем для равных
1176
- )
1177
- )
1178
-
1179
-
1180
- def step(mad, cad, scale: int = 2) -> float:
1181
- return float(int(step_is_need(mad, cad)) * 10**-scale).__round__(scale)
1182
-
1183
-
1184
1309
  class ExcCode(IntEnum):
1185
1310
  FixPriceLimit = 912120022
1186
1311
  RareLimit = 912120050
@@ -1209,7 +1334,7 @@ async def main():
1209
1334
  cn = await init_db(TORM)
1210
1335
 
1211
1336
  agent = (
1212
- await models.Agent.filter(actor__ex_id=4, auth__isnull=False, status__gt=AgentStatus.off, id=8)
1337
+ await models.Agent.filter(actor__ex_id=4, auth__isnull=False, status__gt=AgentStatus.off, id=2)
1213
1338
  .prefetch_related(
1214
1339
  "actor__ex",
1215
1340
  "actor__person__user__gmail",
@@ -1225,7 +1350,10 @@ async def main():
1225
1350
  ex = await models.Ex.get(name="Bybit")
1226
1351
  ecl: ExClient = ex.client(filebot)
1227
1352
  abot = XyncBot(PAY_TOKEN, cn)
1228
- cl: AgentClient = agent.client(ecl, filebot, abot)
1353
+ # pmas = await models.PmAgent.filter(active=True, user_id=1).prefetch_related("pm", "user__gmail")
1354
+ # pm_clients = {pma.pm_id: pma.client(abot) for pma in pmas}
1355
+ prx = PRX and "http://" + PRX
1356
+ cl: AgentClient = agent.client(ecl, filebot, abot, proxy=prx)
1229
1357
 
1230
1358
  # req = TakeAdReq(ad_id=1955696985964089344, amount=504, pm_id=128)
1231
1359
  # await cl.take_ad(req)
@@ -1239,8 +1367,12 @@ async def main():
1239
1367
  # await cl.set_creds()
1240
1368
  # await cl.export_my_ads()
1241
1369
 
1370
+ my_ad = await models.MyAd[5]
1371
+ await cl.ad_share(my_ad.id)
1372
+ await cl.start_listen()
1373
+
1242
1374
  ms = await models.Agent.filter(
1243
- actor__ex_id=4, auth__isnull=False, status__gt=AgentStatus.off, actor__person__user__id__in=[2]
1375
+ actor__ex_id=4, auth__isnull=False, status__gt=AgentStatus.off, actor__person__user__id__in=[3]
1244
1376
  ).prefetch_related(
1245
1377
  "actor__ex",
1246
1378
  "actor__person__user__gmail",
@@ -1251,7 +1383,7 @@ async def main():
1251
1383
  mcs = {m.actor.exid: m.client(ecl, filebot, abot) for m in ms}
1252
1384
 
1253
1385
  await gather(
1254
- create_task(cl.start(True)),
1386
+ # create_task(cl.start()),
1255
1387
  create_task(cl.watch_payeer(mcs)),
1256
1388
  )
1257
1389
  # ensure_future(cl.start(True))