xync-client 0.0.155__py3-none-any.whl → 0.0.162__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,10 +11,11 @@ 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
@@ -24,12 +26,11 @@ from tortoise.expressions import Q
24
26
  from tortoise.functions import Count
25
27
  from tortoise.signals import post_save
26
28
  from tortoise.timezone import now
27
- from urllib3.exceptions import ReadTimeoutError
29
+ from tortoise.transactions import in_transaction
28
30
  from x_client import df_hdrs
29
31
  from x_model import init_db
30
32
  from x_model.func import ArrayAgg
31
33
  from xync_bot import XyncBot
32
- from xync_client.Bybit.InAgent import InAgentClient
33
34
 
34
35
  from xync_client.Bybit.ex import ExClient
35
36
  from xync_schema import models
@@ -38,7 +39,7 @@ from xync_schema.enums import OrderStatus, AgentStatus
38
39
  from xync_schema.models import Actor, PmCur, Agent
39
40
 
40
41
  from xync_client.Abc.Agent import BaseAgentClient
41
- from xync_client.Abc.xtype import FlatDict, BaseOrderReq
42
+ from xync_client.Abc.xtype import FlatDict, BaseOrderReq, AdUpd, GetAds
42
43
  from xync_client.Bybit.etype.ad import AdPostRequest, AdUpdateRequest, Ad, AdStatus, MyAd
43
44
  from xync_client.Bybit.etype.cred import CredEpyd
44
45
  from xync_client.Bybit.etype.order import (
@@ -52,50 +53,41 @@ from xync_client.Bybit.etype.order import (
52
53
  Status,
53
54
  OrderSellRequest,
54
55
  TakeAdReq,
56
+ StatusChange,
57
+ CountDown,
58
+ Receive,
59
+ Read,
60
+ SellerCancelChange,
55
61
  )
56
- from xync_client.loader import TORM, NET_TOKEN, PAY_TOKEN
62
+ from xync_client.Pms.Payeer.agent import PmAgentClient
63
+ from xync_client.loader import TORM, NET_TOKEN, PAY_TOKEN, PRX
57
64
 
58
65
 
59
66
  class NoMakerException(Exception):
60
67
  pass
61
68
 
62
69
 
63
- class AgentClient(BaseAgentClient, InAgentClient): # Bybit client
70
+ class ShareException(Exception):
71
+ pass
72
+
73
+
74
+ class AgentClient(BaseAgentClient): # Bybit client
64
75
  headers = df_hdrs | {"accept-language": "ru-RU"}
65
76
  sec_hdrs: dict[str, str]
66
77
  # rewrite token for public methods
67
78
  api: P2P
68
- last_ad_id: list[str] = []
69
- update_ad_body = {
70
- "priceType": "1",
71
- "premium": "118",
72
- "quantity": "0.01",
73
- "minAmount": "500",
74
- "maxAmount": "3500000",
75
- "paymentPeriod": "30",
76
- "remark": "",
77
- "price": "398244.84",
78
- "paymentIds": ["3162931"],
79
- "tradingPreferenceSet": {
80
- "isKyc": "1",
81
- "hasCompleteRateDay30": "0",
82
- "completeRateDay30": "",
83
- "hasOrderFinishNumberDay30": "0",
84
- "orderFinishNumberDay30": "0",
85
- "isMobile": "0",
86
- "isEmail": "0",
87
- "hasUnPostAd": "0",
88
- "hasRegisterTime": "0",
89
- "registerTimeThreshold": "0",
90
- "hasNationalLimit": "0",
91
- "nationalLimit": "",
92
- },
93
- "actionType": "MODIFY",
94
- "securityRiskToken": "",
95
- }
96
-
97
- def __init__(self, agent: Agent, ex_client: ExClient, fbot: FileClient, bbot: XyncBot, **kwargs):
98
- super().__init__(agent, ex_client, fbot, bbot, **kwargs)
79
+ orders: dict[int, tuple[models.Order, OrderFull]] = {} # pending
80
+
81
+ def __init__(
82
+ self,
83
+ agent: Agent,
84
+ ex_client: ExClient,
85
+ fbot: FileClient,
86
+ bbot: XyncBot,
87
+ pm_clients: dict[int, PmAgentClient] = None,
88
+ **kwargs,
89
+ ):
90
+ super().__init__(agent, ex_client, fbot, bbot, pm_clients, **kwargs)
99
91
  self.sec_hdrs = {
100
92
  "accept-language": "ru,en;q=0.9",
101
93
  "gdfp": agent.auth["Risktoken"],
@@ -169,17 +161,23 @@ class AgentClient(BaseAgentClient, InAgentClient): # Bybit client
169
161
  xtr += (" | " if xtr else "") + ecdx.qrcode
170
162
  elif ecdx.paymentExt1:
171
163
  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())
164
+ try:
165
+ cred_db, _ = await models.Cred.update_or_create(
166
+ {
167
+ "name": ecdx.realName,
168
+ "extra": xtr,
169
+ },
170
+ pmcur=pmcur,
171
+ person_id=pers_id or self.actor.person_id,
172
+ detail=ecdx.accountNo or ecdx.payMessage,
173
+ )
174
+ if cred_db.ovr_pm_id is None and (cred_db.detail.startswith("XyncPay") or xtr.startswith("XyncPay")):
175
+ cred_db.ovr_pm_id = 0
176
+ await cred_db.save()
177
+ credex_in = models.CredEx.validate({"exid": ecdx.id, "cred_id": cred_db.id, "ex_id": self.actor.ex.id})
178
+ credex_db, _ = await models.CredEx.update_or_create(**credex_in.df_unq())
179
+ except IntegrityError as e:
180
+ raise e
183
181
  return credex_db
184
182
 
185
183
  async def guess_cur(self, ecdx: CredEpyd, curs: list[models.Cur]):
@@ -245,33 +243,6 @@ class AgentClient(BaseAgentClient, InAgentClient): # Bybit client
245
243
  res = await self._post("/x-api/fiat/otc/maker/work-config/switch", data)
246
244
  return res
247
245
 
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
246
  @staticmethod
276
247
  def get_rate(list_ads: list) -> float:
277
248
  ads = [ad for ad in list_ads if set(ad["payments"]) - {"5", "51"}]
@@ -292,10 +263,48 @@ class AgentClient(BaseAgentClient, InAgentClient): # Bybit client
292
263
  ad_db = await self.ex_client.ad_load(ad, maker=self.actor)
293
264
  mad_db, _ = await models.MyAd.update_or_create(ad=ad_db)
294
265
  exids = [pt.id for pt in ad.paymentTerms]
295
- credexs = await models.CredEx.filter(ex_id=self.actor.ex_id, exid__in=exids)
266
+ credexs = await models.CredEx.filter(ex_id=self.actor.ex_id, exid__in=exids).prefetch_related("cred")
267
+ await mad_db.credexs.clear()
296
268
  await mad_db.credexs.add(*credexs)
297
269
  return len(ads)
298
270
 
271
+ async def ads_share(self, cur_id: int = None) -> int:
272
+ mq = models.MyAd.hot_mads_query([4]).filter(ad__maker=self.actor)
273
+ if cur_id:
274
+ mq = mq.filter(ad__pair_side__pair__cur_id=cur_id)
275
+ mads: list[models.MyAd] = await mq.all()
276
+ return len([await self.ad_share(mad.id) for mad in mads])
277
+
278
+ async def ad_share(self, maid: int):
279
+ myad = await models.MyAd.get(id=maid).prefetch_related(
280
+ "ad__pair_side__pair__coin", "ad__pair_side__pair__cur", "ad__maker"
281
+ )
282
+ if myad.hex and myad.shared_at + timedelta(minutes=55) > now(): # check expired
283
+ # check validity
284
+ data = await self._post("/x-api/fiat/otc/item/shareItem/info", {"shareCode": myad.hex.hex()})
285
+ if data["ret_code"] == 0:
286
+ return myad.get_url()
287
+ data = await self._post("/x-api/fiat/otc/item/share", {"itemId": str(myad.ad.exid)})
288
+ if data["ret_code"] == 912300058:
289
+ raise ShareException(
290
+ f"Объява {myad.id}:{myad.ad.id}:{myad.ad.exid} агента {myad.ad.maker.agent_id} выключена"
291
+ )
292
+ if data["ret_code"] == 912300059:
293
+ raise ShareException(f"Торговля агента {myad.ad.maker.agent_id} выключена")
294
+ if data["ret_code"] == 10007:
295
+ raise ShareException(f"Авторизация агента {myad.ad.maker.agent_id} слетела")
296
+ if data["ret_code"] != 0: # Новая ошибка
297
+ raise ShareException(data)
298
+ url = data["result"]["shareLink"]
299
+ resp = await self.session.get(url)
300
+ side = "buy" if myad.ad.pair_side.is_sell else "sell" # inverse for taker
301
+ coin, cur = myad.ad.pair_side.pair.coin.ticker, myad.ad.pair_side.pair.cur.ticker
302
+ pref = models.MyAd.WEB.format(side=side, coin=coin, cur=cur)
303
+ hx = resp.url.query["by_web_link"].replace(pref, "")
304
+ _r = await models.MyAd.filter(id=maid).update(hex=bytes.fromhex(hx), shared_at=now())
305
+ await myad.refresh_from_db()
306
+ return myad.get_url()
307
+
299
308
  def get_security_token_create(self):
300
309
  data = self._post("/x-api/fiat/otc/item/create", self.create_ad_body)
301
310
  if data["ret_code"] == 912120019: # Current user can not to create add as maker
@@ -342,6 +351,9 @@ class AgentClient(BaseAgentClient, InAgentClient): # Bybit client
342
351
  return self.__get_2fa(typ, rt)
343
352
  raise Exception("2fa fail")
344
353
 
354
+ def get_ad(self, aid: int) -> Ad:
355
+ return Ad(**self.api.get_ad_details(itemId=aid)["result"])
356
+
345
357
  def _post_ad(self, risk_token: str):
346
358
  self.create_ad_body.update({"securityRiskToken": risk_token})
347
359
  data = self._post("/x-api/fiat/otc/item/create", self.create_ad_body)
@@ -363,7 +375,8 @@ class AgentClient(BaseAgentClient, InAgentClient): # Bybit client
363
375
  data = self.api.post_new_ad(**ad.model_dump())
364
376
  return data["result"]["itemId"] if data["ret_code"] == 0 else data
365
377
 
366
- def ad_upd(self, upd: AdUpdateRequest):
378
+ async def _ad_upd(self, req: AdUpd):
379
+ upd = AdUpdateRequest({})
367
380
  params = upd.model_dump()
368
381
  data = self.api.update_ad(**params)
369
382
  return data["result"] if data["ret_code"] == 0 else data
@@ -487,10 +500,10 @@ class AgentClient(BaseAgentClient, InAgentClient): # Bybit client
487
500
  {"makerUserId": self.actor.exid, "page": "1", "size": "10", "appraiseType": "1"}, # "0" - bad
488
501
  )
489
502
 
490
- async def get_orders_active(
503
+ async def get_pending_orders(
491
504
  self, side: int = None, status: int = None, begin_time: int = None, end_time: int = None, token_id: str = None
492
505
  ):
493
- return await self._post(
506
+ res = await self._post(
494
507
  "/x-api/fiat/otc/order/pending/simplifyList",
495
508
  {
496
509
  "status": status,
@@ -499,9 +512,12 @@ class AgentClient(BaseAgentClient, InAgentClient): # Bybit client
499
512
  "endTime": end_time,
500
513
  "side": side, # 1 - продажа, 0 - покупка
501
514
  "page": 1,
502
- "size": 10,
515
+ "size": 20,
503
516
  },
504
517
  )
518
+ if res["ret_code"] == 0:
519
+ return {int(o["id"]): OrderItem(**o) for o in res["result"]["items"]}
520
+ return res["ret_code"]
505
521
 
506
522
  def get_orders_done(self, begin_time: int, end_time: int, status: int, side: int, token_id: str):
507
523
  return self._post(
@@ -517,27 +533,27 @@ class AgentClient(BaseAgentClient, InAgentClient): # Bybit client
517
533
  },
518
534
  )
519
535
 
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)
536
+ async def create_order_db(self, order: OrderFull) -> models.Order:
523
537
  curex = await models.CurEx.get_or_none(ex=self.ex_client.ex, exid=order.currencyId).prefetch_related("cur")
524
538
  cur_scale = (curex.scale if curex.scale is not None else curex.cur.scale) if curex else 2
525
539
  coinex = await models.CoinEx.get(ex=self.ex_client.ex, exid=order.tokenId).prefetch_related("coin")
526
540
  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)
541
+ sb_names = order.sellerRealName, order.buyerRealName
542
+ im_maker = int(int(order.makerUserId) == self.actor.exid)
529
543
  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)
544
+ taker_name = sb_names[order.side] # todo: double check
545
+ taker_person = await self.ex_client.person_name_update(taker_name, taker_id)
531
546
  seller_person = (
532
547
  self.actor.person
533
548
  if order.side
534
549
  else await self.ex_client.person_name_update(order.sellerRealName, int(order.targetUserId))
535
550
  )
536
551
  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
552
  ad_db = await models.Ad.get(exid=order.itemId)
539
553
  if not ad_db:
540
- ...
554
+ ad = self.get_ad(order.itemId)
555
+ # ad_db, cond_isnew = await self.ex_client.cond_load(ad, force=True, rname=maker_name[order.side])
556
+ ad_db = await self.ex_client.ad_load(ad, maker=self.actor)
541
557
  ecredex: CredEpyd = order.confirmedPayTerm
542
558
 
543
559
  if ecredex.paymentType == 0 and im_maker and order.side:
@@ -693,9 +709,8 @@ class AgentClient(BaseAgentClient, InAgentClient): # Bybit client
693
709
  for oid, o in ords.items():
694
710
  if o.status != Status.completed.value or oid in self.completed_orders:
695
711
  continue
696
- fo = self.api.get_order_details(orderId=o.id)
697
- order = OrderFull.model_validate(fo["result"])
698
- order_db = await self.create_order(order)
712
+ order = await self.get_order_full(o.id)
713
+ order_db = await self.create_order_db(order)
699
714
  await sleep(1)
700
715
  dmsgs = self.api.get_chat_messages(orderId=oid, size=200)["result"]["result"][::-1]
701
716
  msgs = [Message.model_validate(m) for m in dmsgs if m["msgType"] in (1, 2, 7, 8)]
@@ -720,54 +735,6 @@ class AgentClient(BaseAgentClient, InAgentClient): # Bybit client
720
735
  # for t in papi.history():
721
736
  # os = self.api.get_orders(page=1, size=30)
722
737
 
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
738
  # @staticmethod
772
739
  # def premium_up(mad: Ad, cad: Ad, k: Literal[-1, 1]):
773
740
  # mpc, mpm, cpc, cpm = Decimal(mad.price), Decimal(mad.premium), Decimal(cad.price), Decimal(cad.premium)
@@ -777,235 +744,6 @@ class AgentClient(BaseAgentClient, InAgentClient): # Bybit client
777
744
  # if round(cpc * new_premium / cpm, 2) == m
778
745
  # mad.premium = new_premium.to_eng_string()
779
746
 
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
747
  async def take_ad(self, req: TakeAdReq):
1010
748
  if req.price and req.is_sell and req.cur_:
1011
749
  ... # todo call the get_ad_details() only if lack of data
@@ -1046,83 +784,90 @@ class AgentClient(BaseAgentClient, InAgentClient): # Bybit client
1046
784
  return resp
1047
785
 
1048
786
  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")
787
+ await models.CoinEx.get(coin_id=1, ex=self.actor.ex).prefetch_related("coin")
788
+ await models.CurEx.get(cur_id=1, ex=self.actor.ex).prefetch_related("cur")
1051
789
  post_pmexs = set(await models.PmEx.filter(pm_id=366, ex=self.actor.ex).prefetch_related("pm"))
1052
790
  i = 0
1053
791
  while True:
1054
792
  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]
793
+ breq = GetAds(coin_id=1, cur_id=1, is_sell=False, limit=50)
794
+ bs = await self.ex_client.ads(breq, post_pmexs=post_pmexs)
795
+ bs = [b for b in bs if float(b.price) < 100 or int(b.userId) in mcs.keys()]
796
+ if bs:
797
+ ad: Ad = bs[0]
1059
798
  await self.bbot.send(
1060
799
  193017646,
1061
800
  f"price: {ad.price}\nnick: {ad.nickName}\nprice: {ad.price}"
1062
801
  f"\nqty: {ad.quantity} [{ad.minAmount}-{ad.maxAmount}]",
1063
802
  )
1064
- am = min(float(ad.maxAmount), max(1000 + i, float(ad.minAmount)))
803
+ am = min(float(ad.maxAmount), max(8000 + i, float(ad.minAmount)))
1065
804
  req = TakeAdReq(
1066
805
  ad_id=ad.id,
1067
806
  amount=am,
1068
807
  pm_id=14,
1069
- is_sell=True,
808
+ is_sell=False,
1070
809
  coin_id=1,
1071
810
  cur_id=1,
1072
811
  )
1073
812
  ord_resp: OrderResp = await self.take_ad(req)
1074
813
  # order: OrderFull = OrderFull(**self.api.get_order_details(orderId=ord_resp.orderId)["result"])
1075
814
  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)
815
+ odb = await self.create_order_db(order)
816
+ t = await models.Transfer(order=odb, amount=odb.amount, updated_at=now())
817
+ await t.fetch_related("order__cred__pmcur__cur")
818
+ # res = await self.pm_clients[366].send(t)
819
+ await sleep(2)
820
+ self.api.mark_as_paid(
821
+ orderId=str(odb.exid),
822
+ paymentType=str(order.paymentTermList[0].paymentType), # pmex.exid
823
+ paymentId=order.paymentTermList[0].id, # credex.exid
824
+ )
825
+ await sleep(3)
1080
826
  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
- ...
827
+ mcs[int(ad.userId)].api.release_assets(orderId=order.id)
1088
828
 
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]
829
+ await sleep(5)
830
+
831
+ sreq = GetAds(coin_id=1, cur_id=1, is_sell=True, limit=50, kwargs={"post_pmexs": post_pmexs})
832
+ ss = await self.ex_client.ads(sreq, post_pmexs=post_pmexs)
833
+ ss = [s for s in ss if float(s.price) > 92 or int(s.userId) in mcs.keys()]
834
+ if ss:
835
+ ad: Ad = ss[0]
1093
836
  await self.bbot.send(
1094
837
  193017646,
1095
838
  f"price: {ad.price}\nnick: {ad.nickName}\nprice: {ad.price}"
1096
839
  f"\nqty: {ad.quantity} [{ad.minAmount}-{ad.maxAmount}]",
1097
840
  )
1098
- am = min(float(ad.maxAmount), max(600 + i, float(ad.minAmount)))
841
+ am = min(float(ad.maxAmount), max(10000 + i, float(ad.minAmount)))
1099
842
  req = TakeAdReq(
1100
843
  ad_id=ad.id,
1101
844
  amount=am,
1102
845
  pm_id=14,
1103
- is_sell=False,
846
+ is_sell=True,
1104
847
  coin_id=1,
1105
848
  cur_id=1,
1106
849
  )
1107
850
  ord_resp: OrderResp = await self.take_ad(req)
1108
851
  # order: OrderFull = OrderFull(**self.api.get_order_details(orderId=ord_resp.orderId)["result"])
1109
852
  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
- )
853
+ odb = await self.create_order_db(order)
854
+ # t = await models.Transfer(order=odb, amount=odb.amount, updated_at=now())
855
+ # await t.fetch_related("order__cred__pmcur__cur")
856
+ # res = await self.pm_clients[366].check_in(t)
857
+ await sleep(2)
1119
858
  if int(ad.userId) in mcs:
1120
- mcs[int(ad.userId)].api.release_assets(orderId=order.id)
1121
- await sleep(1)
1122
- ...
859
+ mcs[int(ad.userId)].api.mark_as_paid(
860
+ orderId=str(odb.exid),
861
+ paymentType=str(order.paymentTermList[0].paymentType), # pmex.exid
862
+ paymentId=order.paymentTermList[0].id, # credex.exid
863
+ )
864
+ await sleep(3)
865
+ self.api.release_assets(orderId=order.id)
866
+ await sleep(5)
867
+
1123
868
  except Exception as e:
1124
869
  logging.exception(e)
1125
- await sleep(90)
870
+ await sleep(30)
1126
871
  except HttpProcessingError as e:
1127
872
  logging.error(e)
1128
873
  print(end=".", flush=True)
@@ -1135,13 +880,379 @@ class AgentClient(BaseAgentClient, InAgentClient): # Bybit client
1135
880
  am = 500 + i
1136
881
  req = TakeAdReq(ad_id="1856989782009487360", amount=am, pm_id=366)
1137
882
  ord_resp: OrderResp = await self.take_ad(req)
1138
- order: OrderFull = OrderFull(**self.api.get_order_details(orderId=ord_resp.orderId)["result"])
1139
- odb = await self.create_order(order)
883
+ order: OrderFull = await self.get_order_full(int(ord_resp.orderId))
884
+ odb = await self.create_order_db(order)
1140
885
  t = await models.Transfer(order=odb, amount=odb.amount, updated_at=now())
1141
886
  await t.fetch_related("order__cred__pmcur__cur")
1142
887
  await self.pm_clients[366].send(t)
1143
888
  ...
1144
889
 
890
+ async def load_pending_orders(self):
891
+ po: dict[int, OrderItem] = await self.get_pending_orders()
892
+ if isinstance(po, int): # если код ошибки вместо результата
893
+ raise ValueError(po)
894
+ self.orders = {
895
+ o.exid: (o, await self.get_order_full(o.exid)) for o in await models.Order.filter(exid__in=po.keys())
896
+ }
897
+ for oid in po.keys() - self.orders.keys():
898
+ await self.load_order(oid)
899
+
900
+ async def _start_listen(self):
901
+ t = await self.ott()
902
+ ts = int(float(t["time_now"]) * 1000)
903
+ did = self.agent.auth["cookies"]["deviceId"]
904
+ u = f"wss://ws2.bybit.com/private?appid=bybit&os=web&deviceid={did}&timestamp={ts}"
905
+ async with websockets.connect(u) as websocket:
906
+ auth_msg = json.dumps({"req_id": did, "op": "login", "args": [t["result"]]})
907
+ await websocket.send(auth_msg)
908
+
909
+ sub_msg = json.dumps({"op": "subscribe", "args": ["FIAT_OTC_TOPIC", "FIAT_OTC_ONLINE_TOPIC"]})
910
+ await websocket.send(sub_msg)
911
+ sub_msg = json.dumps({"op": "input", "args": ["FIAT_OTC_TOPIC", '{"topic":"SUPER_DEAL"}']})
912
+ await websocket.send(sub_msg)
913
+ sub_msg = json.dumps({"op": "input", "args": ["FIAT_OTC_TOPIC", '{"topic":"OTC_ORDER_STATUS"}']})
914
+ await websocket.send(sub_msg)
915
+ sub_msg = json.dumps({"op": "input", "args": ["FIAT_OTC_TOPIC", '{"topic":"WEB_THREE_SELL"}']})
916
+ await websocket.send(sub_msg)
917
+ sub_msg = json.dumps({"op": "input", "args": ["FIAT_OTC_TOPIC", '{"topic":"APPEALED_CHANGE"}']})
918
+ await websocket.send(sub_msg)
919
+
920
+ sub_msg = json.dumps({"op": "subscribe", "args": ["fiat.cashier.order"]})
921
+ await websocket.send(sub_msg)
922
+ sub_msg = json.dumps({"op": "subscribe", "args": ["fiat.cashier.order-eftd-complete-privilege-event"]})
923
+ await websocket.send(sub_msg)
924
+ sub_msg = json.dumps({"op": "subscribe", "args": ["fiat.cashier.order-savings-product-event"]})
925
+ await websocket.send(sub_msg)
926
+ sub_msg = json.dumps({"op": "subscribe", "args": ["fiat.deal-core.order-savings-complete-event"]})
927
+ await websocket.send(sub_msg)
928
+
929
+ sub_msg = json.dumps({"op": "subscribe", "args": ["FIAT_OTC_TOPIC", "FIAT_OTC_ONLINE_TOPIC"]})
930
+ await websocket.send(sub_msg)
931
+ while resp := await websocket.recv():
932
+ if data := json.loads(resp):
933
+ logging.info(f" {now().strftime('%H:%M:%S')} upd: {data.get('topic')}:{data.get('type')}")
934
+ await self.proc(data)
935
+
936
+ async def proc(self, data: dict):
937
+ match data.get("topic"):
938
+ case "OTC_ORDER_STATUS":
939
+ match data["type"]:
940
+ case "STATUS_CHANGE":
941
+ upd = StatusChange.model_validate(data["data"])
942
+ order_db, order = await self.load_order(upd.id)
943
+ match upd.status:
944
+ case Status.ws_new:
945
+ logging.info(f"Order {upd.id} CREATED at {upd.createDate}")
946
+ # await self.got_new_order(order_db, order)
947
+
948
+ # # сразу уменьшаем доступный остаток монеты/валюты
949
+ # await self.money_upd(order_db)
950
+ # if upd.side: # я покупатель - ждем мою оплату
951
+ # _dest = order.paymentTermList[0].accountNo
952
+ # if not re.match(r"^([PpРр])\d{7,10}\b", _dest):
953
+ # return
954
+ # await order_db.fetch_related("ad__pair_side__pair", "cred__pmcur__cur")
955
+ # await self.send_payment(order_db)
956
+ case Status.created:
957
+ if upd.side == 0: # я продавец, ждем когда покупатель оплатит
958
+ # check_payment() # again
959
+ ...
960
+ # if not (pmacdx := await self.get_pma_by_cdex(order)):
961
+ # return
962
+ # pma, cdx = pmacdx
963
+ # am, tid = await pma.check_in(
964
+ # float(order.amount),
965
+ # cdx.cred.pmcur.cur.ticker,
966
+ # # todo: почему в московском час.поясе?
967
+ # datetime.fromtimestamp(float(order.transferDate) / 1000),
968
+ # )
969
+ # if not tid:
970
+ # logging.info(f"Order {order.id} created at {order.createDate}, not paid yet")
971
+ # return
972
+ # try:
973
+ # t, is_new = await models.Transfer.update_or_create(
974
+ # dict(
975
+ # amount=int(float(order.amount) * 100),
976
+ # order=order_db,
977
+ # ),
978
+ # pmid=tid,
979
+ # )
980
+ # except IntegrityError as e:
981
+ # logging.error(tid)
982
+ # logging.error(order)
983
+ # logging.exception(e)
984
+ #
985
+ # if not is_new: # если по этому платежу уже отпущен другая продажа
986
+ # return
987
+ #
988
+ # # если висят незавершенные продажи с такой же суммой
989
+ # pos = (await self.get_orders_active(1))["result"]
990
+ # pos = [
991
+ # o
992
+ # for o in pos.get("items", [])
993
+ # if (
994
+ # o["amount"] == order.amount
995
+ # and o["id"] != upd.id
996
+ # and int(order.createDate) < int(o["createDate"]) + 15 * 60 * 1000
997
+ # # get full_order from o, and cred or pm from full_order:
998
+ # and self.api.get_order_details(orderId=o["id"])["result"][
999
+ # "paymentTermList"
1000
+ # ][0]["accountNo"]
1001
+ # == order.paymentTermList[0].accountNo
1002
+ # )
1003
+ # ]
1004
+ # curex = await models.CurEx.get(cur__ticker=order.currencyId, ex=self.ex_client.ex)
1005
+ # pos_db = await models.Order.filter(
1006
+ # exid__not=order.id,
1007
+ # cred_id=order_db.cred_id,
1008
+ # amount=int(float(order.amount) * 10**curex.scale),
1009
+ # status__not_in=[OrderStatus.completed, OrderStatus.canceled],
1010
+ # created_at__gt=now() - timedelta(minutes=15),
1011
+ # )
1012
+ # if pos or pos_db:
1013
+ # await self.ex_client.bot.send(
1014
+ # f"[Duplicate amount!]"
1015
+ # f"(https://www.bybit.com/ru-RU/p2p/orderList/{order.id})",
1016
+ # self.actor.person.user.username_id,
1017
+ # )
1018
+ # logging.warning("Duplicate amount!")
1019
+ # return
1020
+ #
1021
+ # # !!! ОТПРАВЛЯЕМ ДЕНЬГИ !!!
1022
+ # self.api.release_assets(orderId=upd.id)
1023
+ # logging.info(
1024
+ # f"Order {order.id} created, paid before #{tid}:{am} at {order.createDate}, and RELEASED at {now()}"
1025
+ # )
1026
+ elif upd.side == 1: # я покупатель - ждем мою оплату
1027
+ # pay()
1028
+ logging.warning(f"Order {upd.id} CREATED2 at {now()}")
1029
+
1030
+ case Status.paid:
1031
+ if order_db.status == OrderStatus.paid:
1032
+ return
1033
+ await order_db.update_from_dict(
1034
+ {
1035
+ "status": OrderStatus.paid,
1036
+ "payed_at": datetime.fromtimestamp(float(order.transferDate) / 1000),
1037
+ }
1038
+ ).save()
1039
+ logging.info(f"Order {order.id} payed at {order_db.payed_at}")
1040
+
1041
+ case Status.appealed_by_seller: # just any appealed
1042
+ # todo: appealed by WHO? щас наугад стоит by_seller
1043
+ await order_db.update_from_dict(
1044
+ {
1045
+ "status": OrderStatus.appealed_by_seller,
1046
+ "appealed_at": datetime.fromtimestamp(float(order.updateDate) / 1000),
1047
+ }
1048
+ ).save()
1049
+ logging.info(f"Order {order.id} appealed at {order_db.appealed_at}")
1050
+
1051
+ case Status.canceled:
1052
+ await order_db.update_from_dict({"status": OrderStatus.canceled}).save()
1053
+ logging.info(f"Order {order.id} canceled at {datetime.now()}")
1054
+ # await self.money_upd(order_db)
1055
+
1056
+ case Status.completed:
1057
+ await order_db.refresh_from_db()
1058
+ if order_db.status != OrderStatus.completed:
1059
+ await order_db.update_from_dict(
1060
+ {
1061
+ "status": OrderStatus.completed,
1062
+ "confirmed_at": datetime.fromtimestamp(float(order.updateDate) / 1000),
1063
+ }
1064
+ ).save(update_fields=["status", "confirmed_at"])
1065
+ # await self.money_upd(order_db)
1066
+
1067
+ case _:
1068
+ logging.warning(f"Order {order.id} UNKNOWN STATUS {datetime.now()}")
1069
+ case "COUNT_DOWN":
1070
+ upd = CountDown.model_validate(data["data"])
1071
+
1072
+ case "OTC_USER_CHAT_MSG":
1073
+ match data["type"]:
1074
+ case "RECEIVE":
1075
+ upd = Receive.model_validate(data["data"])
1076
+ order_db, order = await self.load_order(upd.orderId)
1077
+ # got_msg()
1078
+ ...
1079
+ # im_taker = order_db.taker_id == self.actor.id
1080
+ # im_buyer = order_db.ad.pair_side.is_sell == im_taker
1081
+ # if order_db.ad.auto_msg != upd.message and upd.roleType == "user":
1082
+ # msg, _ = await models.Msg.update_or_create(
1083
+ # {
1084
+ # "to_maker": upd.userId == self.actor.exid and im_taker,
1085
+ # "sent_at": datetime.fromtimestamp(float(upd.createDate) / 1000),
1086
+ # },
1087
+ # txt=upd.message,
1088
+ # order=order_db,
1089
+ # )
1090
+ # if not upd.message:
1091
+ # ...
1092
+ # if im_buyer and (g := re.match(r"^[PpРр]\d{7,10}\b", upd.message)):
1093
+ # if not order_db.cred.detail.startswith(dest := g.group()):
1094
+ # order_db.cred.detail = dest
1095
+ # await order_db.save()
1096
+ # await self.send_payment(order_db)
1097
+ case "READ":
1098
+ # msg_read()
1099
+ upd = Read.model_validate(data["data"])
1100
+
1101
+ case "CLEAR":
1102
+ return
1103
+ case "OTC_USER_CHAT_MSG_V2":
1104
+ # msg dup
1105
+ ...
1106
+ # match data["type"]:
1107
+ # case "RECEIVE":
1108
+ # upd = Receive.model_validate(data["data"])
1109
+ # case "READ":
1110
+ # upd = Read.model_validate(data["data"])
1111
+ # case "CLEAR":
1112
+ # pass
1113
+ # case _:
1114
+ # self.listen(data)
1115
+ case "SELLER_CANCEL_CHANGE":
1116
+ upd = SellerCancelChange.model_validate(data["data"])
1117
+ case None:
1118
+ if not data.get("success"):
1119
+ logging.error(data, "NOT SUCCESS!")
1120
+ else:
1121
+ return # success login, subscribes, input
1122
+ case _:
1123
+ logging.warning(data, "UNKNOWN TOPIC")
1124
+
1125
+ async def get_order_full(self, oid: int) -> OrderFull:
1126
+ order = self.api.get_order_details(orderId=oid)
1127
+ return OrderFull.model_validate(order["result"])
1128
+
1129
+ async def load_order(self, oid: int) -> tuple[models.Order, OrderFull]:
1130
+ if not self.orders.get(oid):
1131
+ order = await self.get_order_full(oid)
1132
+ if not (
1133
+ order_db := await models.Order.get_or_none(exid=oid, ad__maker__ex=self.actor.ex).prefetch_related(
1134
+ "ad__pair_side__pair", "cred__pmcur__cur"
1135
+ )
1136
+ ):
1137
+ order_db = await self.create_order_db(order)
1138
+ self.orders[oid] = order_db, order
1139
+ return self.orders[oid]
1140
+
1141
+ async def money_upd(self, odb: models.Order):
1142
+ # обновляем остаток монеты
1143
+ await odb.fetch_related("ad__pair_side__pair", "ad__my_ad__credexs__cred__fiat", "cred__pmcur", "transfer")
1144
+ ass = await models.Asset.get(addr__coin_id=odb.ad.pair_side.pair.coin_id, addr__actor=self.actor)
1145
+ # обновляем остаток валюты
1146
+ im_maker = odb.ad.maker_id == self.actor.id
1147
+ im_seller = odb.ad.pair_side.is_sell == im_maker
1148
+ if im_maker:
1149
+ if _fiats := [cx.cred.fiat for cx in odb.ad.my_ad.credexs if cx.cred.fiat]:
1150
+ fiat = _fiats[0]
1151
+ await fiat.fetch_related("cred__pmcur__pm")
1152
+ else:
1153
+ raise ValueError(odb, "No Fiat")
1154
+ elif im_seller: # im taker
1155
+ fltr = dict(cred__person_id=self.actor.person_id)
1156
+ fltr |= (
1157
+ {"cred__ovr_pm_id": odb.cred.ovr_pm_id, "cred__pmcur__cur_id": odb.cred.pmcur.cur_id}
1158
+ if odb.cred.ovr_pm_id
1159
+ else {"cred__pmcur_id": odb.cred.pmcur_id}
1160
+ )
1161
+ if not (fiat := await models.Fiat.get_or_none(**fltr).prefetch_related("cred__pmcur__pm")):
1162
+ raise ValueError(odb, "No Fiat")
1163
+ fee = round(odb.amount * (fiat.cred.pmcur.pm.fee or 0) * 0.0001)
1164
+ # k = int(im_seller) * 2 - 1 # im_seller: 1, im_buyer: -1
1165
+ if odb.status == OrderStatus.created:
1166
+ if im_seller:
1167
+ ass.free -= odb.quantity
1168
+ ass.freeze += odb.quantity
1169
+ else: # я покупатель
1170
+ fiat.amount -= odb.amount + fee
1171
+ elif odb.status == OrderStatus.completed:
1172
+ if im_seller:
1173
+ fiat.amount += odb.amount
1174
+ else: # я покупатель
1175
+ ass.free += odb.quantity
1176
+ elif odb.status == OrderStatus.canceled:
1177
+ if im_seller:
1178
+ ass.free += odb.quantity
1179
+ ass.freeze -= odb.quantity
1180
+ else: # я покупатель
1181
+ fiat.amount += odb.amount + fee
1182
+ else:
1183
+ logging.exception(odb.id, f"STATUS: {odb.status.name}")
1184
+ await ass.save(update_fields=["free", "freeze"])
1185
+ await fiat.save(update_fields=["amount"])
1186
+ logging.info(f"Order #{odb.id} {odb.status.name}. Fiat: {fiat.amount}, Asset: {ass.free}")
1187
+
1188
+ async def send_payment(self, order_db: models.Order):
1189
+ if order_db.status != OrderStatus.created:
1190
+ return
1191
+ fmt_am = round(order_db.amount * 10**-2, 2)
1192
+ pma, cur = await self.get_pma_by_pmex(order_db)
1193
+ async with in_transaction():
1194
+ # отмечаем ордер на бирже "оплачен"
1195
+ pmex = await models.PmEx.get(pm_id=order_db.cred.pmcur.pm_id, ex=self.actor.ex)
1196
+ credex = await models.CredEx.get(cred=order_db.cred, ex=self.actor.ex)
1197
+ self.api.mark_as_paid(
1198
+ orderId=str(order_db.exid),
1199
+ paymentType=pmex.exid, # pmex.exid
1200
+ paymentId=str(credex.exid), # credex.exid
1201
+ )
1202
+ # проверяем не отправляли ли мы уже перевод по этому ордеру
1203
+ if t := await models.Transfer.get_or_none(order=order_db, amount=order_db.amount):
1204
+ await pma.bot.send(
1205
+ f"Order# {order_db.exid}: Double send {fmt_am}{cur} to {order_db.cred.detail} #{t.pmid}!",
1206
+ self.actor.person.user.username_id,
1207
+ )
1208
+ raise Exception(
1209
+ f"Order# {order_db.exid}: Double send {fmt_am}{cur} to {order_db.cred.detail} #{t.pmid}!"
1210
+ )
1211
+
1212
+ # ставим в бд статус "оплачен"
1213
+ order_db.status = OrderStatus.paid
1214
+ order_db.payed_at = datetime.now(timezone.utc)
1215
+ await order_db.save()
1216
+ # создаем перевод в бд
1217
+ t = models.Transfer(order=order_db, amount=order_db.amount, updated_at=now())
1218
+ # отправляем деньги
1219
+ tid, img = await pma.send(t)
1220
+ t.pmid = tid
1221
+ await t.save()
1222
+ await self.send_receipt(str(order_db.exid), tid) # отправляем продавцу чек
1223
+ logging.info(f"Order {order_db.exid} PAID at {datetime.now()}: {fmt_am}!")
1224
+
1225
+ async def send_receipt(self, oexid: str, tid: int) -> tuple[PmAgentClient | None, models.CredEx] | None:
1226
+ try:
1227
+ if res := self.api.upload_chat_file(upload_file=f"tmp/{tid}.png").get("result"):
1228
+ await sleep(0.5)
1229
+ self.api.send_chat_message(orderId=oexid, contentType="pic", message=res["url"], msgUuid=uuid4().hex)
1230
+ except Exception as e:
1231
+ logging.error(e)
1232
+ await sleep(0.5)
1233
+ self.api.send_chat_message(orderId=oexid, contentType="str", message=f"#{tid}", msgUuid=uuid4().hex)
1234
+
1235
+ async def get_pma_by_cdex(self, order: OrderFull) -> tuple[PmAgentClient | None, models.CredEx] | None:
1236
+ cdxs = await models.CredEx.filter(
1237
+ ex=self.ex_client.ex,
1238
+ exid__in=[ptl.id for ptl in order.paymentTermList],
1239
+ cred__person=self.actor.person,
1240
+ ).prefetch_related("cred__pmcur__cur")
1241
+ pmas = [pma for cdx in cdxs if (pma := self.pm_clients.get(cdx.cred.pmcur.pm_id))]
1242
+ if not len(pmas):
1243
+ # raise ValueError(order.paymentTermList, f"No pm_agents for {order.paymentTermList[0].paymentType}")
1244
+ return None
1245
+ elif len(pmas) > 1:
1246
+ logging.error(order.paymentTermList, f">1 pm_agents for {cdxs[0].cred.pmcur.pm_id}")
1247
+ else:
1248
+ return pmas[0], cdxs[0]
1249
+
1250
+ async def get_pma_by_pmex(self, order_db: models.Order) -> tuple[PmAgentClient, str]:
1251
+ pma = self.pm_clients.get(order_db.cred.pmcur.pm_id)
1252
+ if pma:
1253
+ return pma, order_db.cred.pmcur.cur.ticker
1254
+ logging.error(f"No pm_agents for {order_db.cred.pmcur.pm_id}")
1255
+
1145
1256
 
1146
1257
  def ms2utc(msk_ts_str: str):
1147
1258
  return datetime.fromtimestamp(int(msk_ts_str) / 1000, timezone(timedelta(hours=3), name="MSK"))
@@ -1164,23 +1275,6 @@ def detailed_diff(str1, str2):
1164
1275
  return "".join(result)
1165
1276
 
1166
1277
 
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
1278
  class ExcCode(IntEnum):
1185
1279
  FixPriceLimit = 912120022
1186
1280
  RareLimit = 912120050
@@ -1209,7 +1303,7 @@ async def main():
1209
1303
  cn = await init_db(TORM)
1210
1304
 
1211
1305
  agent = (
1212
- await models.Agent.filter(actor__ex_id=4, auth__isnull=False, status__gt=AgentStatus.off, id=8)
1306
+ await models.Agent.filter(actor__ex_id=4, auth__isnull=False, status__gt=AgentStatus.off, id=5)
1213
1307
  .prefetch_related(
1214
1308
  "actor__ex",
1215
1309
  "actor__person__user__gmail",
@@ -1225,7 +1319,10 @@ async def main():
1225
1319
  ex = await models.Ex.get(name="Bybit")
1226
1320
  ecl: ExClient = ex.client(filebot)
1227
1321
  abot = XyncBot(PAY_TOKEN, cn)
1228
- cl: AgentClient = agent.client(ecl, filebot, abot)
1322
+ # pmas = await models.PmAgent.filter(active=True, user_id=1).prefetch_related("pm", "user__gmail")
1323
+ # pm_clients = {pma.pm_id: pma.client(abot) for pma in pmas}
1324
+ prx = PRX and "http://" + PRX
1325
+ cl: AgentClient = agent.client(ecl, filebot, abot, proxy=prx)
1229
1326
 
1230
1327
  # req = TakeAdReq(ad_id=1955696985964089344, amount=504, pm_id=128)
1231
1328
  # await cl.take_ad(req)
@@ -1236,11 +1333,14 @@ async def main():
1236
1333
  # await cl.ex_client.set_pairs()
1237
1334
  # await cl.ex_client.set_pms()
1238
1335
 
1239
- # await cl.set_creds()
1240
- # await cl.export_my_ads()
1336
+ await cl.set_creds()
1337
+ await cl.export_my_ads()
1338
+
1339
+ my_ad = await models.MyAd[5]
1340
+ await cl.ad_share(my_ad.id)
1241
1341
 
1242
1342
  ms = await models.Agent.filter(
1243
- actor__ex_id=4, auth__isnull=False, status__gt=AgentStatus.off, actor__person__user__id__in=[2]
1343
+ actor__ex_id=4, auth__isnull=False, status__gt=AgentStatus.off, actor__person__user__id__in=[3]
1244
1344
  ).prefetch_related(
1245
1345
  "actor__ex",
1246
1346
  "actor__person__user__gmail",
@@ -1251,7 +1351,7 @@ async def main():
1251
1351
  mcs = {m.actor.exid: m.client(ecl, filebot, abot) for m in ms}
1252
1352
 
1253
1353
  await gather(
1254
- create_task(cl.start(True)),
1354
+ # create_task(cl.start()),
1255
1355
  create_task(cl.watch_payeer(mcs)),
1256
1356
  )
1257
1357
  # ensure_future(cl.start(True))