xync-client 0.0.141__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.
Files changed (40) hide show
  1. xync_client/Abc/AdLoader.py +5 -0
  2. xync_client/Abc/Agent.py +354 -8
  3. xync_client/Abc/Ex.py +432 -25
  4. xync_client/Abc/HasAbotUid.py +10 -0
  5. xync_client/Abc/InAgent.py +0 -11
  6. xync_client/Abc/PmAgent.py +34 -26
  7. xync_client/Abc/xtype.py +57 -3
  8. xync_client/Bybit/InAgent.py +233 -409
  9. xync_client/Bybit/agent.py +844 -777
  10. xync_client/Bybit/etype/__init__.py +0 -0
  11. xync_client/Bybit/etype/ad.py +54 -86
  12. xync_client/Bybit/etype/cred.py +29 -9
  13. xync_client/Bybit/etype/order.py +75 -103
  14. xync_client/Bybit/ex.py +35 -48
  15. xync_client/Gmail/__init__.py +119 -98
  16. xync_client/Htx/agent.py +213 -40
  17. xync_client/Htx/etype/ad.py +40 -16
  18. xync_client/Htx/etype/order.py +194 -0
  19. xync_client/Htx/ex.py +17 -19
  20. xync_client/Mexc/agent.py +268 -0
  21. xync_client/Mexc/api.py +1255 -0
  22. xync_client/Mexc/etype/ad.py +52 -1
  23. xync_client/Mexc/etype/order.py +354 -0
  24. xync_client/Mexc/ex.py +34 -22
  25. xync_client/Okx/1.py +14 -0
  26. xync_client/Okx/agent.py +39 -0
  27. xync_client/Okx/ex.py +8 -8
  28. xync_client/Pms/Payeer/agent.py +396 -0
  29. xync_client/Pms/Payeer/login.py +1 -59
  30. xync_client/Pms/Payeer/trade.py +58 -0
  31. xync_client/Pms/Volet/__init__.py +82 -63
  32. xync_client/Pms/Volet/api.py +5 -4
  33. xync_client/loader.py +2 -0
  34. xync_client/pm_unifier.py +1 -1
  35. {xync_client-0.0.141.dist-info → xync_client-0.0.156.dev18.dist-info}/METADATA +5 -1
  36. {xync_client-0.0.141.dist-info → xync_client-0.0.156.dev18.dist-info}/RECORD +38 -29
  37. xync_client/Pms/Payeer/__init__.py +0 -253
  38. xync_client/Pms/Payeer/api.py +0 -25
  39. {xync_client-0.0.141.dist-info → xync_client-0.0.156.dev18.dist-info}/WHEEL +0 -0
  40. {xync_client-0.0.141.dist-info → xync_client-0.0.156.dev18.dist-info}/top_level.txt +0 -0
@@ -1,37 +1,47 @@
1
1
  import asyncio
2
+ import json
2
3
  import logging
3
4
  import re
4
5
  from asyncio import sleep, gather
5
- from collections import defaultdict
6
+ from asyncio.tasks import create_task
6
7
  from datetime import datetime, timedelta, timezone
7
8
  from difflib import SequenceMatcher
8
9
  from enum import IntEnum
10
+ from hashlib import sha256
9
11
  from http.client import HTTPException
10
12
  from math import floor
11
13
  from typing import Literal
14
+ from uuid import uuid4
12
15
 
13
16
  import pyotp
14
- from asyncpg import ConnectionDoesNotExistError
17
+ import websockets
18
+ from aiohttp.http_exceptions import HttpProcessingError
15
19
  from bybit_p2p import P2P
16
20
  from bybit_p2p._exceptions import FailedRequestError
17
21
  from payeer_api import PayeerAPI
22
+ from pydantic import ValidationError
18
23
  from pyro_client.client.file import FileClient
19
24
  from tortoise import BaseDBAsyncClient
20
25
  from tortoise.exceptions import IntegrityError
21
- from tortoise.expressions import F, Q
26
+ from tortoise.expressions import Q
22
27
  from tortoise.functions import Count
23
28
  from tortoise.signals import post_save
24
- from urllib3.exceptions import ReadTimeoutError
29
+ from tortoise.timezone import now
30
+ from tortoise.transactions import in_transaction
31
+ from x_client import df_hdrs
25
32
  from x_model import init_db
26
33
  from x_model.func import ArrayAgg
34
+ from xync_bot import XyncBot
35
+
36
+ from xync_client.Bybit.ex import ExClient
27
37
  from xync_schema import models
28
- from xync_schema.enums import OrderStatus
38
+ from xync_schema.enums import OrderStatus, AgentStatus
29
39
 
30
- from xync_schema.models import Actor, Cond, CondSim, PmCur, PairSide
40
+ from xync_schema.models import Actor, PmCur, Agent
31
41
 
32
42
  from xync_client.Abc.Agent import BaseAgentClient
33
- from xync_client.Abc.xtype import BaseOrderReq, FlatDict
34
- from xync_client.Bybit.etype.ad import AdPostRequest, AdUpdateRequest, Ad, AdStatus
43
+ from xync_client.Abc.xtype import FlatDict, BaseOrderReq, AdUpd, GetAds
44
+ from xync_client.Bybit.etype.ad import AdPostRequest, AdUpdateRequest, Ad, AdStatus, MyAd
35
45
  from xync_client.Bybit.etype.cred import CredEpyd
36
46
  from xync_client.Bybit.etype.order import (
37
47
  OrderRequest,
@@ -42,17 +52,30 @@ from xync_client.Bybit.etype.order import (
42
52
  OrderFull,
43
53
  Message,
44
54
  Status,
55
+ OrderSellRequest,
56
+ TakeAdReq,
57
+ StatusChange,
58
+ CountDown,
59
+ Receive,
60
+ Read,
61
+ SellerCancelChange,
45
62
  )
46
- from xync_client.loader import TORM, NET_TOKEN
63
+ from xync_client.Pms.Payeer.agent import PmAgentClient
64
+ from xync_client.loader import TORM, NET_TOKEN, PAY_TOKEN, PRX
47
65
 
48
66
 
49
67
  class NoMakerException(Exception):
50
68
  pass
51
69
 
52
70
 
71
+ class ShareException(Exception):
72
+ pass
73
+
74
+
53
75
  class AgentClient(BaseAgentClient): # Bybit client
54
- host = "api2.bybit.com"
55
- headers = {"cookie": ";"} # rewrite token for public methods
76
+ headers = df_hdrs | {"accept-language": "ru-RU"}
77
+ sec_hdrs: dict[str, str]
78
+ # rewrite token for public methods
56
79
  api: P2P
57
80
  last_ad_id: list[str] = []
58
81
  update_ad_body = {
@@ -82,28 +105,37 @@ class AgentClient(BaseAgentClient): # Bybit client
82
105
  "actionType": "MODIFY",
83
106
  "securityRiskToken": "",
84
107
  }
85
- all_conds: dict[int, tuple[str, set[int]]] = {}
86
- cond_sims: dict[int, int] = defaultdict(set)
87
- rcond_sims: dict[int, set[int]] = defaultdict(set) # backward
88
- tree: dict = {}
89
108
 
90
- def __init__(self, actor: Actor, bot: FileClient, **kwargs):
91
- super().__init__(actor, bot, **kwargs)
92
- self.api = P2P(testnet=False, api_key=actor.agent.auth["key"], api_secret=actor.agent.auth["sec"])
93
- self.hist: dict = None
94
- self.completed_orders: list[int] = None
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)
119
+ self.sec_hdrs = {
120
+ "accept-language": "ru,en;q=0.9",
121
+ "gdfp": agent.auth["Risktoken"],
122
+ "tx-id": agent.auth["Risktoken"],
123
+ }
124
+ self.api = P2P(testnet=False, api_key=agent.auth["key"], api_secret=agent.auth["sec"])
125
+ self.hist: dict | None = None
126
+ self.completed_orders: list[int] | None = None
95
127
 
96
128
  """ Private METHs"""
97
129
 
98
130
  async def fiat_new(self, payment_type: int, real_name: str, account_number: str) -> FlatDict | None:
99
131
  method1 = await self._post(
100
- "/fiat/otc/user/payment/new_create",
132
+ "/x-api/fiat/otc/user/payment/new_create",
101
133
  {"paymentType": payment_type, "realName": real_name, "accountNo": account_number, "securityRiskToken": ""},
102
134
  )
103
135
  if srt := method1["result"]["securityRiskToken"]:
104
136
  await self._check_2fa(srt)
105
137
  method2 = await self._post(
106
- "/fiat/otc/user/payment/new_create",
138
+ "/x-api/fiat/otc/user/payment/new_create",
107
139
  {
108
140
  "paymentType": payment_type,
109
141
  "realName": real_name,
@@ -115,12 +147,8 @@ class AgentClient(BaseAgentClient): # Bybit client
115
147
  else:
116
148
  return logging.exception(method1)
117
149
 
118
- async def get_payment_method(self, fiat_id: int = None) -> dict:
119
- list_methods = self.creds()
120
- if fiat_id:
121
- fiat = [m for m in list_methods if m["id"] == fiat_id][0]
122
- return fiat
123
- return list_methods[1]
150
+ def get_payment_method(self, fiat_id: int) -> CredEpyd:
151
+ return self.creds()[fiat_id]
124
152
 
125
153
  def creds(self) -> dict[int, CredEpyd]:
126
154
  data = self.api.get_user_payment_types()
@@ -144,29 +172,43 @@ class AgentClient(BaseAgentClient): # Bybit client
144
172
  elif not cur_id: # is new Cred
145
173
  cur_id = (
146
174
  pmex.pm.df_cur_id
175
+ or await self.guess_cur(ecdx, len(pmex.pm.curs) > 1 and pmex.pm.curs)
147
176
  or (pmex.pm.country_id and (await pmex.pm.country).cur_id)
148
- # or (ecdx.currencyBalance and await models.Cur.get_or_none(ticker=ecdx.currencyBalance[0]))
149
- or (0 < len(pmex.pm.curs) < 30 and pmex.pm.curs[-1].id)
150
- or await self.guess_cur(ecdx)
177
+ # or (ecdx.currencyBalance and await models.Cur.get_or_none(ticker=ecdx.currencyBalance[0])) # это че еще за хуйня?
151
178
  )
152
179
  if not cur_id:
153
180
  raise Exception(f"Set default cur for {pmex.name}")
154
181
  if not (pmcur := await models.PmCur.get_or_none(cur_id=cur_id, pm_id=pmex.pm_id)):
155
- raise HTTPException(f"No PmCur with cur#{ecdx.currencyBalance} and pm#{ecdx.paymentType}", 404)
156
- dct = {
157
- "pmcur_id": pmcur.id,
158
- "name": ecdx.realName,
159
- "person_id": pers_id or self.actor.person_id,
160
- "detail": ecdx.accountNo,
161
- "extra": ecdx.branchName or ecdx.bankName or ecdx.qrcode or ecdx.payMessage or ecdx.paymentExt1,
162
- } # todo: WTD with multicur pms?
163
- cred_in = models.Cred.validate(dct, False)
164
- cred_db, _ = await models.Cred.update_or_create(**cred_in.df_unq())
165
- credex_in = models.CredEx.validate({"exid": ecdx.id, "cred_id": cred_db.id, "ex_id": self.actor.ex.id})
166
- credex_db, _ = await models.CredEx.update_or_create(**credex_in.df_unq())
182
+ raise HTTPException(f"No PmCur with cur#{cur_id} and pm#{ecdx.paymentType}", 404)
183
+ xtr = ecdx.branchName
184
+ if ecdx.bankName:
185
+ xtr += (" | " if xtr else "") + ecdx.bankName
186
+ elif ecdx.payMessage:
187
+ xtr += (" | " if xtr else "") + ecdx.payMessage
188
+ elif ecdx.qrcode:
189
+ xtr += (" | " if xtr else "") + ecdx.qrcode
190
+ elif ecdx.paymentExt1:
191
+ xtr += (" | " if xtr else "") + ecdx.paymentExt1
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
167
209
  return credex_db
168
210
 
169
- async def guess_cur(self, ecdx: CredEpyd):
211
+ async def guess_cur(self, ecdx: CredEpyd, curs: list[models.Cur]):
170
212
  mbs = ecdx.bankName.split(", ")
171
213
  mbs += ecdx.branchName.split(" / ")
172
214
  mbs = {mb.lower(): mb for mb in mbs}
@@ -178,7 +220,7 @@ class AgentClient(BaseAgentClient): # Bybit client
178
220
  .values("pmcurs__cur_id", "names", "ccnt")
179
221
  ):
180
222
  return pms[0]["pmcurs__cur_id"]
181
- curs = {c.ticker: c.id for c in await models.Cur.all()}
223
+ curs = {c.ticker: c.id for c in curs or await models.Cur.all()}
182
224
  for cur, cid in curs.items():
183
225
  if re.search(re.compile(rf"\({cur}\)$"), ecdx.bankName):
184
226
  return cid
@@ -188,6 +230,8 @@ class AgentClient(BaseAgentClient): # Bybit client
188
230
  return cid
189
231
  if re.search(re.compile(rf"\({cur}\)$"), ecdx.payMessage):
190
232
  return cid
233
+ if re.search(re.compile(rf"\({cur}\)$"), ecdx.paymentExt1):
234
+ return cid
191
235
  return None
192
236
 
193
237
  # 25: Список реквизитов моих платежных методов
@@ -197,62 +241,44 @@ class AgentClient(BaseAgentClient): # Bybit client
197
241
  return credexs
198
242
 
199
243
  async def ott(self):
200
- t = await self._post("/user/private/ott")
244
+ t = await self._post("/x-api/user/private/ott")
201
245
  return t
202
246
 
203
247
  # 27
204
248
  async def fiat_upd(self, fiat_id: int, detail: str, name: str = None) -> dict:
205
249
  fiat = self.get_payment_method(fiat_id)
206
- fiat["realName"] = name
207
- fiat["accountNo"] = detail
208
- result = await self._post("/fiat/otc/user/payment/new_update", fiat)
250
+ fiat.realName = name
251
+ fiat.accountNo = detail
252
+ result = await self._post("/x-api/fiat/otc/user/payment/new_update", fiat.model_dump(exclude_none=True))
209
253
  srt = result["result"]["securityRiskToken"]
210
254
  await self._check_2fa(srt)
211
- fiat["securityRiskToken"] = srt
212
- result2 = await self._post("/fiat/otc/user/payment/new_update", fiat)
255
+ fiat.securityRiskToken = srt
256
+ result2 = await self._post("/fiat/otc/user/payment/new_update", fiat.model_dump(exclude_none=True))
213
257
  return result2
214
258
 
215
259
  # 28
216
260
  async def fiat_del(self, fiat_id: int) -> dict | str:
217
261
  data = {"id": fiat_id, "securityRiskToken": ""}
218
- method = await self._post("/fiat/otc/user/payment/new_delete", data)
262
+ method = await self._post("/x-api/fiat/otc/user/payment/new_delete", data)
219
263
  srt = method["result"]["securityRiskToken"]
220
264
  await self._check_2fa(srt)
221
265
  data["securityRiskToken"] = srt
222
- delete = await self._post("/fiat/otc/user/payment/new_delete", data)
266
+ delete = await self._post("/x-api/fiat/otc/user/payment/new_delete", data)
223
267
  return delete
224
268
 
225
269
  async def switch_ads(self, new_status: AdStatus) -> dict:
226
270
  data = {"workStatus": new_status.name} # todo: переделать на апи, там status 0 -> 1
227
- res = await self._post("/fiat/otc/maker/work-config/switch", data)
271
+ res = await self._post("/x-api/fiat/otc/maker/work-config/switch", data)
228
272
  return res
229
273
 
230
- async def ads(
231
- self,
232
- cnx: models.CoinEx,
233
- crx: models.CurEx,
234
- is_sell: bool,
235
- pmxs: list[models.PmEx],
236
- amount: int = None,
237
- lim: int = 50,
238
- vm_filter: bool = False,
239
- ) -> list[Ad]:
240
- return await self.ex_client.ads(
241
- cnx.exid, crx.exid, is_sell, [pmex.exid for pmex in pmxs or []], amount, lim, vm_filter
242
- )
243
-
244
- def online_ads(self) -> str:
245
- online = self._get("/fiat/otc/maker/work-config/get")
246
- return online["result"]["workStatus"]
247
-
248
274
  @staticmethod
249
275
  def get_rate(list_ads: list) -> float:
250
276
  ads = [ad for ad in list_ads if set(ad["payments"]) - {"5", "51"}]
251
277
  return float(ads[0]["price"])
252
278
 
253
- def my_ads(self, active: bool = True, page: int = 1) -> list[Ad]:
279
+ def my_ads(self, active: bool = True, page: int = 1) -> list[MyAd]:
254
280
  resp = self.api.get_ads_list(size="30", page=str(page), status=AdStatus.active if active else AdStatus.sold_out)
255
- ads = [Ad.model_validate(ad) for ad in resp["result"]["items"]]
281
+ ads = [MyAd.model_validate(ad) for ad in resp["result"]["items"]]
256
282
  if resp["result"]["count"] > 30 * page:
257
283
  ads.extend(self.my_ads(active, page + 1))
258
284
  return ads
@@ -261,35 +287,95 @@ class AgentClient(BaseAgentClient): # Bybit client
261
287
  ads = self.my_ads(True)
262
288
  if not active:
263
289
  ads += self.my_ads(False)
264
- res = [await self.ad_create(ad, actor=self.actor) for ad in ads]
265
- res = [await models.MyAd.update_or_create(ad=ad) for ad in res]
266
- return len(res)
290
+ for ad in ads:
291
+ ad_db = await self.ex_client.ad_load(ad, maker=self.actor)
292
+ mad_db, _ = await models.MyAd.update_or_create(ad=ad_db)
293
+ exids = [pt.id for pt in ad.paymentTerms]
294
+ credexs = await models.CredEx.filter(ex_id=self.actor.ex_id, exid__in=exids).prefetch_related("cred")
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
+
304
+ return len(ads)
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()
267
326
 
268
327
  def get_security_token_create(self):
269
- data = self._post("/fiat/otc/item/create", self.create_ad_body)
328
+ data = self._post("/x-api/fiat/otc/item/create", self.create_ad_body)
270
329
  if data["ret_code"] == 912120019: # Current user can not to create add as maker
271
330
  raise NoMakerException(data)
272
331
  security_risk_token = data["result"]["securityRiskToken"]
273
332
  return security_risk_token
274
333
 
275
- def _check_2fa(self, risk_token):
276
- # 2fa code
277
- bybit_secret = self.agent.auth["2fa"]
278
- totp = pyotp.TOTP(bybit_secret)
279
- totp_code = totp.now()
280
-
281
- res = self._post(
282
- "/user/public/risk/verify", {"risk_token": risk_token, "component_list": {"google2fa": totp_code}}
283
- )
334
+ async def _check_2fa(self, risk_token) -> int:
335
+ data = {"risk_token": risk_token}
336
+ res = await self._post("/x-api/user/public/risk/components", data, hdrs=self.sec_hdrs)
284
337
  if res["ret_msg"] != "success":
285
- print("Wrong 2fa, wait 5 secs and retry..")
286
- sleep(5)
287
- self._check_2fa(risk_token)
288
- return res
338
+ raise HTTPException("get")
339
+ cres = sorted(res["result"]["component_list"], key=lambda c: c["component_id"], reverse=True)
340
+ vdata = {
341
+ "risk_token": risk_token,
342
+ "component_list": {c["component_id"]: await self.__get_2fa(c["component_id"], risk_token) for c in cres},
343
+ }
344
+ res = await self._post("/x-api/user/public/risk/verify", vdata, hdrs=self.sec_hdrs)
345
+ if er_code := res["ret_code"] or res["result"]["ret_code"]: # если код не 0, значит ошибка
346
+ logging.error("Wrong 2fa, wait 5 secs and retry..")
347
+ await sleep(5)
348
+ return await self._check_2fa(risk_token)
349
+ return er_code
350
+
351
+ async def __get_2fa(
352
+ self, typ: Literal["google2fa", "email_verify", "payment_password_verify", "phone_verify"], rt: str = None
353
+ ):
354
+ res = {"ret_msg": "success"}
355
+ if typ != "google2fa":
356
+ data = {"risk_token": rt, "component_id": typ}
357
+ res = await self._post("/x-api/user/public/risk/send/code", data, hdrs=self.sec_hdrs)
358
+ if res["ret_msg"] == "success":
359
+ if typ == "google2fa":
360
+ bybit_secret = self.agent.auth["2fa"]
361
+ totp = pyotp.TOTP(bybit_secret)
362
+ return totp.now()
363
+ elif typ == "email_verify":
364
+ return self.gmail.bybit_code()
365
+ elif typ == "payment_password_verify":
366
+ hp = sha256(self.agent.auth["pass"].encode()).hexdigest()
367
+ return hp
368
+ elif cool_down := int(res["result"]["cool_down"]):
369
+ await sleep(cool_down)
370
+ return self.__get_2fa(typ, rt)
371
+ raise Exception("2fa fail")
372
+
373
+ def get_ad(self, aid: int) -> Ad:
374
+ return Ad(**self.api.get_ad_details(itemId=aid)["result"])
289
375
 
290
376
  def _post_ad(self, risk_token: str):
291
377
  self.create_ad_body.update({"securityRiskToken": risk_token})
292
- data = self._post("/fiat/otc/item/create", self.create_ad_body)
378
+ data = self._post("/x-api/fiat/otc/item/create", self.create_ad_body)
293
379
  return data
294
380
 
295
381
  # создание объявлений
@@ -308,14 +394,15 @@ class AgentClient(BaseAgentClient): # Bybit client
308
394
  data = self.api.post_new_ad(**ad.model_dump())
309
395
  return data["result"]["itemId"] if data["ret_code"] == 0 else data
310
396
 
311
- def ad_upd(self, upd: AdUpdateRequest):
397
+ async def _ad_upd(self, req: AdUpd):
398
+ upd = AdUpdateRequest({})
312
399
  params = upd.model_dump()
313
400
  data = self.api.update_ad(**params)
314
401
  return data["result"] if data["ret_code"] == 0 else data
315
402
 
316
403
  def get_security_token_update(self) -> str:
317
404
  self.update_ad_body["id"] = self.last_ad_id
318
- data = self._post("/fiat/otc/item/update", self.update_ad_body)
405
+ data = self._post("/x-api/fiat/otc/item/update", self.update_ad_body)
319
406
  security_risk_token = data["result"]["securityRiskToken"]
320
407
  return security_risk_token
321
408
 
@@ -332,46 +419,75 @@ class AgentClient(BaseAgentClient): # Bybit client
332
419
 
333
420
  def update_ad(self, risk_token: str):
334
421
  self.update_ad_body.update({"securityRiskToken": risk_token})
335
- data = self._post("/fiat/otc/item/update", self.update_ad_body)
422
+ data = self._post("/x-api/fiat/otc/item/update", self.update_ad_body)
336
423
  return data
337
424
 
338
425
  def ad_del(self, ad_id: int):
339
426
  data = self.api.remove_ad(itemId=ad_id)
340
427
  return data
341
428
 
342
- async def order_request(self, br: BaseOrderReq) -> OrderResp:
343
- res0 = await self._post("/fiat/otc/item/simple", data={"item_id": str(br.ad_id)})
344
- if res0["ret_code"] == 0:
345
- res0 = res0["result"]
346
- res0 = PreOrderResp.model_validate(res0)
429
+ async def __preorder_request(self, ad_id: int) -> PreOrderResp:
430
+ res = await self._post("/x-api/fiat/otc/item/simple", json={"item_id": str(ad_id)})
431
+ if res["ret_code"] == 0:
432
+ res = res["result"]
433
+ return PreOrderResp.model_validate(res)
434
+
435
+ async def _order_request(self, bor: BaseOrderReq) -> OrderResp:
436
+ por: PreOrderResp = await self.__preorder_request(bor.ad_id)
347
437
  req = OrderRequest(
348
- itemId=br.ad_id,
349
- tokenId=br.coin_exid,
350
- currencyId=br.cur_exid,
351
- side=str(OrderRequest.Side(int(br.is_sell))),
352
- amount=str(br.fiat_amount or br.asset_amount * float(res0.price)),
353
- curPrice=res0.curPrice,
354
- quantity=str(br.asset_amount or round(br.fiat_amount / float(res0.price), br.coin_scale)),
355
- flag="amount" if br.amount_is_fiat else "quantity",
438
+ itemId=por.id,
439
+ tokenId=bor.coin_exid,
440
+ currencyId=bor.cur_exid,
441
+ side="1" if bor.is_sell else "0",
442
+ amount=f"{bor.fiat_amount:.2f}".rstrip("0").rstrip("."),
443
+ curPrice=por.curPrice,
444
+ quantity=str(round(bor.fiat_amount / float(por.price), bor.coin_scale)),
445
+ flag="amount",
446
+ # online="0"
356
447
  )
448
+ if bor.is_sell:
449
+ credex = await models.CredEx.get(
450
+ cred__person_id=self.actor.person_id,
451
+ cred__pmcur__pm__pmexs__exid=[pp for pp in por.payments if pp == bor.pmex_exid][0], # bor.pmex_exid
452
+ cred__pmcur__pm__pmexs__ex_id=self.ex_client.ex.id,
453
+ cred__pmcur__cur__ticker=bor.cur_exid,
454
+ )
455
+ req = OrderSellRequest(**req.model_dump(), paymentType=bor.pmex_exid, paymentId=str(credex.exid))
357
456
  # вот непосредственно сам запрос на ордер
358
- res = await self._post("/fiat/otc/order/create", data=req.model_dump())
457
+ return await self.__order_create(req, bor)
458
+
459
+ async def __order_create(self, req: OrderRequest | OrderSellRequest, bor: BaseOrderReq) -> OrderResp:
460
+ hdrs = {"Risktoken": self.sec_hdrs["gdfp"]}
461
+ res: dict = await self._post("/x-api/fiat/otc/order/create", json=req.model_dump(), hdrs=hdrs)
359
462
  if res["ret_code"] == 0:
360
- return OrderResp.model_validate(res["result"])
463
+ resp = OrderResp.model_validate(res["result"])
464
+ elif res["ret_code"] == 10001:
465
+ logging.error(req.model_dump(), "POST", self.session._base_url)
466
+ raise HTTPException()
361
467
  elif res["ret_code"] == 912120030 or res["ret_msg"] == "The price has changed, please try again later.":
362
- return await self.order_request(br)
468
+ resp = await self._order_request(bor)
469
+ else:
470
+ logging.exception(res)
471
+ if not resp.orderId and resp.needSecurityRisk:
472
+ if rc := await self._check_2fa(resp.securityRiskToken):
473
+ await self.bbot.send(self.actor.person.user.username_id, f"Bybit 2fa: {rc}")
474
+ raise Exception(f"Bybit 2fa: {rc}")
475
+ # еще раз уже с токеном
476
+ req.securityRiskToken = resp.securityRiskToken
477
+ resp = await self.__order_create(req, bor)
478
+ return resp
363
479
 
364
480
  async def cancel_order(self, order_id: str) -> bool:
365
481
  cr = CancelOrderReq(orderId=order_id)
366
- res = await self._post("/fiat/otc/order/cancel", cr.model_dump())
482
+ res = await self._post("/x-api/fiat/otc/order/cancel", cr.model_dump())
367
483
  return res["ret_code"] == 0
368
484
 
369
- def get_order_info(self, order_id: str) -> dict:
370
- data = self._post("/fiat/otc/order/info", json={"orderId": order_id})
371
- return data["result"]
485
+ async def get_order_info(self, order_id: str) -> OrderFull:
486
+ data = await self._post("/x-api/fiat/otc/order/info", json={"orderId": order_id})
487
+ return OrderFull.model_validate(data["result"])
372
488
 
373
489
  def get_chat_msg(self, order_id):
374
- data = self._post("/fiat/otc/order/message/listpage", json={"orderId": order_id, "size": 100})
490
+ data = self._post("/x-api/fiat/otc/order/message/listpage", json={"orderId": order_id, "size": 100})
375
491
  msgs = [
376
492
  {"text": msg["message"], "type": msg["contentType"], "role": msg["roleType"], "user_id": msg["userId"]}
377
493
  for msg in data["result"]["result"]
@@ -380,14 +496,14 @@ class AgentClient(BaseAgentClient): # Bybit client
380
496
  return msgs
381
497
 
382
498
  def block_user(self, user_id: str):
383
- return self._post("/fiat/p2p/user/add_block_user", {"blockedUserId": user_id})
499
+ return self._post("/x-api/fiat/p2p/user/add_block_user", {"blockedUserId": user_id})
384
500
 
385
501
  def unblock_user(self, user_id: str):
386
- return self._post("/fiat/p2p/user/delete_block_user", {"blockedUserId": user_id})
502
+ return self._post("/x-api/fiat/p2p/user/delete_block_user", {"blockedUserId": user_id})
387
503
 
388
504
  def user_review_post(self, order_id: str):
389
505
  return self._post(
390
- "/fiat/otc/order/appraise/modify",
506
+ "/x-api/fiat/otc/order/appraise/modify",
391
507
  {
392
508
  "orderId": order_id,
393
509
  "anonymous": "0",
@@ -403,11 +519,11 @@ class AgentClient(BaseAgentClient): # Bybit client
403
519
  {"makerUserId": self.actor.exid, "page": "1", "size": "10", "appraiseType": "1"}, # "0" - bad
404
520
  )
405
521
 
406
- async def get_orders_active(
522
+ async def get_pending_orders(
407
523
  self, side: int = None, status: int = None, begin_time: int = None, end_time: int = None, token_id: str = None
408
524
  ):
409
- return await self._post(
410
- "/fiat/otc/order/pending/simplifyList",
525
+ res = await self._post(
526
+ "/x-api/fiat/otc/order/pending/simplifyList",
411
527
  {
412
528
  "status": status,
413
529
  "tokenId": token_id,
@@ -415,13 +531,16 @@ class AgentClient(BaseAgentClient): # Bybit client
415
531
  "endTime": end_time,
416
532
  "side": side, # 1 - продажа, 0 - покупка
417
533
  "page": 1,
418
- "size": 10,
534
+ "size": 20,
419
535
  },
420
536
  )
537
+ if res["ret_code"] == 0:
538
+ return {o.id: OrderItem(**o) for o in res["result"]["items"]}
539
+ return res["ret_code"]
421
540
 
422
541
  def get_orders_done(self, begin_time: int, end_time: int, status: int, side: int, token_id: str):
423
542
  return self._post(
424
- "/fiat/otc/order/simplifyList",
543
+ "/x-api/fiat/otc/order/simplifyList",
425
544
  {
426
545
  "status": status, # 50 - завершено
427
546
  "tokenId": token_id,
@@ -433,24 +552,27 @@ class AgentClient(BaseAgentClient): # Bybit client
433
552
  },
434
553
  )
435
554
 
436
- async def create_order(self, order: OrderFull) -> models.Order:
437
- ad = Ad(**self.api.get_ad_details(itemId=order.itemId)["result"])
438
- await sleep(1)
555
+ async def create_order_db(self, order: OrderFull) -> models.Order:
439
556
  curex = await models.CurEx.get_or_none(ex=self.ex_client.ex, exid=order.currencyId).prefetch_related("cur")
440
557
  cur_scale = (curex.scale if curex.scale is not None else curex.cur.scale) if curex else 2
441
558
  coinex = await models.CoinEx.get(ex=self.ex_client.ex, exid=order.tokenId).prefetch_related("coin")
442
559
  coin_scale = coinex.scale if coinex.scale is not None else coinex.cur.scale
443
- maker_name = order.buyerRealName, order.sellerRealName
444
- im_maker = int(order.makerUserId == order.userId)
560
+ sb_names = order.sellerRealName, order.buyerRealName
561
+ im_maker = int(int(order.makerUserId) == self.actor.exid)
445
562
  taker_id = (order.userId, order.targetUserId)[im_maker]
446
- taker_person = await self.person_upsert(maker_name[::-1][ad.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)
447
565
  seller_person = (
448
- self.actor.person if order.side else await self.person_upsert(order.sellerRealName, int(order.targetUserId))
566
+ self.actor.person
567
+ if order.side
568
+ else await self.ex_client.person_name_update(order.sellerRealName, int(order.targetUserId))
449
569
  )
450
570
  taker_nick = (self.actor.name, order.targetNickName)[im_maker] # todo: check
451
- ad_db, cond_isnew = await self.cond_upsert(ad, maker_name[ad.side], force=True)
571
+ ad_db = await models.Ad.get(exid=order.itemId)
452
572
  if not ad_db:
453
- ...
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)
454
576
  ecredex: CredEpyd = order.confirmedPayTerm
455
577
 
456
578
  if ecredex.paymentType == 0 and im_maker and order.side:
@@ -458,22 +580,22 @@ class AgentClient(BaseAgentClient): # Bybit client
458
580
  if ecredex.paymentType:
459
581
  if ecredex.paymentType == 51:
460
582
  ecredex.accountNo = ecredex.accountNo.replace("p", "P").replace("р", "P").replace("Р", "P")
461
- if not re.match(r"^([Pp])\d{7,10}$", ecredex.accountNo):
462
- msgs = self.api.get_chat_messages(orderId=order.id, size=100)["result"]["result"]
463
- msgs = [m["message"] for m in msgs if m["roleType"] == "user" and m["userId"] == order.targetUserId]
464
- msgs = [g.group() for m in msgs if (g := re.match(r"([PpРр])\d{7,10}\b", m))]
465
- crd = await models.Cred.get_or_none(
466
- detail=ecredex.accountNo, credexs__exid=ecredex.id, credexs__ex=self.ex_client.ex
467
- )
468
- if not msgs and re.match(r"^\d{7,10}$", ecredex.accountNo):
469
- ecredex.accountNo = "P" + ecredex.accountNo
470
- elif msgs:
471
- ecredex.accountNo = msgs[-1]
472
- else:
473
- ...
474
- if crd:
475
- crd.detail = ecredex.accountNo
476
- await crd.save(update_fields=["detail"])
583
+ # if not re.match(r"^([Pp])\d{7,10}$", ecredex.accountNo):
584
+ # msgs = self.api.get_chat_messages(orderId=order.id, size=100)["result"]["result"]
585
+ # msgs = [m["message"] for m in msgs if m["roleType"] == "user" and m["userId"] == order.targetUserId]
586
+ # msgs = [g.group() for m in msgs if (g := re.match(r"([PpРр])\d{7,10}\b", m))]
587
+ # crd = await models.Cred.get_or_none(
588
+ # detail=ecredex.accountNo, credexs__exid=ecredex.id, credexs__ex=self.ex_client.ex
589
+ # )
590
+ # if not msgs and re.match(r"^\d{7,10}$", ecredex.accountNo):
591
+ # ecredex.accountNo = "P" + ecredex.accountNo
592
+ # elif msgs:
593
+ # ecredex.accountNo = msgs[-1]
594
+ # else:
595
+ # ...
596
+ # if crd:
597
+ # crd.detail = ecredex.accountNo
598
+ # await crd.save(update_fields=["detail"])
477
599
  if not (credex := await models.CredEx.get_or_none(exid=ecredex.id, ex=self.ex_client.ex)):
478
600
  # cur_id = await Cur.get(ticker=ad.currencyId).values_list('id', flat=True)
479
601
  # await self.cred_epyd2db(ecredex, ad_db.maker.person_id, cur_id)
@@ -481,7 +603,7 @@ class AgentClient(BaseAgentClient): # Bybit client
481
603
  await PmCur.filter(
482
604
  pm__pmexs__ex=self.ex_client.ex,
483
605
  pm__pmexs__exid=ecredex.paymentType,
484
- cur__ticker=ad.currencyId,
606
+ cur__ticker=order.currencyId,
485
607
  ).count()
486
608
  != 1
487
609
  ):
@@ -490,7 +612,7 @@ class AgentClient(BaseAgentClient): # Bybit client
490
612
  pmcur := await PmCur.get_or_none(
491
613
  pm__pmexs__ex=self.ex_client.ex,
492
614
  pm__pmexs__exid=ecredex.paymentType,
493
- cur__ticker=ad.currencyId,
615
+ cur__ticker=order.currencyId,
494
616
  )
495
617
  ):
496
618
  ...
@@ -545,13 +667,13 @@ class AgentClient(BaseAgentClient): # Bybit client
545
667
  to = ((odb.payed_at or odb.created_at) + timedelta(minutes=180 + 30)).isoformat(sep=" ").split("+")[0]
546
668
  tsa = [
547
669
  t
548
- for tid, t in self.hist.items()
670
+ for tid, t in (self.hist.items() if self.hist else [])
549
671
  if (ecredex.accountNo == t["to"] and t["from"] != "@merchant" and frm < t["date"] < to)
550
672
  ]
551
673
  buyer_person = (
552
674
  self.actor.person
553
675
  if not order.side
554
- else await self.person_upsert(order.buyerRealName, int(order.targetUserId))
676
+ else await self.ex_client.person_name_update(order.buyerRealName, int(order.targetUserId))
555
677
  )
556
678
  ts = [t for t in tsa if floor(fa := float(order.amount)) <= float(t["creditedAmount"]) <= round(fa)]
557
679
  if len(ts) != 1:
@@ -608,7 +730,7 @@ class AgentClient(BaseAgentClient): # Bybit client
608
730
  continue
609
731
  fo = self.api.get_order_details(orderId=o.id)
610
732
  order = OrderFull.model_validate(fo["result"])
611
- order_db = await self.create_order(order)
733
+ order_db = await self.create_order_db(order)
612
734
  await sleep(1)
613
735
  dmsgs = self.api.get_chat_messages(orderId=oid, size=200)["result"]["result"][::-1]
614
736
  msgs = [Message.model_validate(m) for m in dmsgs if m["msgType"] in (1, 2, 7, 8)]
@@ -633,53 +755,6 @@ class AgentClient(BaseAgentClient): # Bybit client
633
755
  # for t in papi.history():
634
756
  # os = self.api.get_orders(page=1, size=30)
635
757
 
636
- async def mad_upd(self, mad: Ad, attrs: dict, cxids: list[str]):
637
- if not [setattr(mad, k, v) for k, v in attrs.items() if getattr(mad, k) != v]:
638
- print(end="v" if mad.side else "^", flush=True)
639
- return await sleep(5)
640
- req = AdUpdateRequest.model_validate({**mad.model_dump(), "paymentIds": cxids})
641
- try:
642
- return self.ad_upd(req)
643
- except FailedRequestError as e:
644
- if ExcCode(e.status_code) == ExcCode.FixPriceLimit:
645
- if limits := re.search(
646
- r"The fixed price set is lower than ([0-9]+\.?[0-9]{0,2}) or higher than ([0-9]+\.?[0-9]{0,2})",
647
- e.message,
648
- ):
649
- return await self.mad_upd(mad, {"price": limits.group(1 if mad.side else 2)}, cxids)
650
- elif ExcCode(e.status_code) == ExcCode.RareLimit:
651
- await sleep(180)
652
- else:
653
- raise e
654
- except (ReadTimeoutError, ConnectionDoesNotExistError):
655
- logging.warning("Connection failed. Restarting..")
656
- print("-" if mad.side else "+", end=req.price, flush=True)
657
- await sleep(60)
658
-
659
- def overprice_filter(self, ads: list[Ad], ceil: float, k: Literal[-1, 1]):
660
- # вырезаем ads с ценами выше потолка
661
- if ads and (ceil - float(ads[0].price)) * k > 0:
662
- if int(ads[0].userId) != self.actor.exid:
663
- ads.pop(0)
664
- self.overprice_filter(ads, ceil, k)
665
-
666
- def get_cad(self, ads: list[Ad], ceil: float, k: Literal[-1, 1], target_place: int, cur_plc: int) -> Ad:
667
- if not ads:
668
- return None
669
- # чью цену будем обгонять, предыдущей или слещующей объявы?
670
- # cad: Ad = ads[place] if cur_plc > place else ads[cur_plc]
671
- # переделал пока на жесткую установку целевого места, даже если текущее выше:
672
- if len(ads) <= target_place:
673
- logging.error(f"target place {target_place} not found in ads list {ads}")
674
- cad: Ad = ads[target_place]
675
- # а цена обгоняемой объявы не выше нашего потолка?
676
- if (float(cad.price) - ceil) * k <= 0:
677
- # тогда берем следующую
678
- ads.pop(target_place)
679
- cad = self.get_cad(ads, ceil, k, target_place, cur_plc)
680
- # todo: добавить фильтр по лимитам min-max
681
- return cad
682
-
683
758
  # @staticmethod
684
759
  # def premium_up(mad: Ad, cad: Ad, k: Literal[-1, 1]):
685
760
  # mpc, mpm, cpc, cpm = Decimal(mad.price), Decimal(mad.premium), Decimal(cad.price), Decimal(cad.premium)
@@ -689,511 +764,531 @@ class AgentClient(BaseAgentClient): # Bybit client
689
764
  # if round(cpc * new_premium / cpm, 2) == m
690
765
  # mad.premium = new_premium.to_eng_string()
691
766
 
692
- async def racing(
693
- self,
694
- race: models.Race,
695
- ):
696
- coinex: models.CoinEx = await models.CoinEx.get(
697
- coin_id=race.road.ad.pair_side.pair.coin_id, ex=self.actor.ex
698
- ).prefetch_related("coin")
699
- curex: models.CurEx = await models.CurEx.get(
700
- cur_id=race.road.ad.pair_side.pair.cur_id, ex=self.actor.ex
701
- ).prefetch_related("cur")
702
- taker_side: bool = not race.road.ad.pair_side.is_sell
703
- pm_ids = [pm.id for pm in race.road.ad.pms]
704
- pmexs: list[models.PmEx] = await models.PmEx.filter(pm_id__in=pm_ids, ex=self.actor.ex).prefetch_related("pm")
705
- k = (-1) ** int(taker_side) # on_buy=1, on_sell=-1
706
- sleep_sec = 3 # 1 if set(pms) & {"volet"} and coinex.coin_id == 1 else 5
707
- creds: list[models.CredEx] = await self.get_credexs_by_pms(race.road.ad.pms, curex.cur_id)
708
- _lstat, volume = None, 0
709
-
710
- while self.actor.person.user.status > 0:
711
- # обновляем все обновления по текущей гонке из бд
712
- await race.refresh_from_db()
713
- if not race.started:
714
- await sleep(5)
715
- continue
716
- # если гонка дольше Х минут не обновлялась, обновляем ее (и ее пары) потолок
717
- expiration = datetime.now(timezone.utc) - timedelta(minutes=15)
718
- if race.updated_at < expiration:
719
- ceils, hp, vmf, zplace = await self.get_ceils(coinex, curex, pmexs, 0.001, True)
720
- race.ceil = int(ceils[int(taker_side)] * 10**curex.scale)
721
- await race.save()
722
- # upd pair race
723
- if prace := await models.Race.annotate(pms_count=Count("road__ad__pms")).get_or_none(
724
- road__ad__pair_side__pair_id=race.road.ad.pair_side.pair_id,
725
- road__ad__pair_side__is_sell=taker_side,
726
- road__ad__maker=self.actor,
727
- updated_at__lt=expiration,
728
- road__ad__pms__id__in=pm_ids,
729
- pms_count=len(pm_ids),
730
- ):
731
- prace.ceil = int(ceils[int(not taker_side)] * 10**curex.scale)
732
- await prace.save()
733
-
734
- last_vol = volume
735
- if taker_side: # гонка в стакане продажи - мы покупаем монету за ФИАТ
736
- fiat = max(await models.Fiat.filter(cred_id__in=[c.cred_id for c in creds]), key=lambda x: x.amount)
737
- volume = fiat.amount / race.road.ad.price
738
- else: # гонка в стакане покупки - мы продаем МОНЕТУ за фиат
739
- asset = await models.Asset.get(addr__actor=self.actor, addr__coin_id=coinex.coin_id)
740
- volume = asset.free * 10**-coinex.scale
741
- volume = str(round(volume, coinex.scale))
742
-
767
+ async def take_ad(self, req: TakeAdReq):
768
+ if req.price and req.is_sell and req.cur_:
769
+ ... # todo call the get_ad_details() only if lack of data
770
+ # res = self.api.get_ad_details(itemId=req.ad_id)["result"]
771
+ # ad: Ad = Ad.model_validate(res)
772
+ # pmexs = await models.PmEx.filter(ex_id=self.actor.ex_id, pm_id=req.pm_id)
773
+ # if len(pmexs) > 1:
774
+ # pmexs = [p for p in pmexs if p.exid in ad.payments]
775
+ #
776
+ # # todo: map pm->cred_pattern
777
+ # pmexid = exids.pop() if (exids := set(ad.payments) & set(px.exid for px in pmexs)) else "40"
778
+ pmexid = str(req.pm_id)
779
+ coinex = await models.CoinEx.get(coin_id=req.coin_id, ex=self.ex_client.ex)
780
+ curex = await models.CurEx.get(cur_id=req.cur_id, ex=self.ex_client.ex)
781
+
782
+ # if ad.side: # продажа, я (тейкер) покупатель
783
+ # pmexs = await models.PmEx.filter(ex_id=self.actor.ex_id, pm_id=req.pm_id)
784
+ # if len(pmexs) > 1:
785
+ # pmexs = [p for p in pmexs if p.name.endswith(f" ({ad.currencyId})")]
786
+ # else:
787
+ # pmexs = await models.CredEx.filter(
788
+ # ex_id=self.actor.ex_id, cred__person_id=self.actor.person_id,
789
+ # cred__pmcur__pm_id=req.pm_id, cred__pmcur__cur__ticker=ad.currencyId
790
+ # )
791
+ # req.pm_id = pmexs[0].exid
792
+ # req.quantity = round(req.amount / float(ad.price) - 0.00005, 4) # todo: to get the scale from coinEx
793
+
794
+ bor = BaseOrderReq(
795
+ ad_id=str(req.ad_id),
796
+ fiat_amount=req.amount,
797
+ is_sell=req.is_sell,
798
+ cur_exid=curex.exid,
799
+ coin_exid=coinex.exid,
800
+ coin_scale=coinex.scale,
801
+ pmex_exid=pmexid,
802
+ )
803
+ resp: OrderResp = await self._order_request(bor)
804
+ return resp
805
+
806
+ async def watch_payeer(self, mcs: dict[int, "AgentClient"]):
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")
809
+ post_pmexs = set(await models.PmEx.filter(pm_id=366, ex=self.actor.ex).prefetch_related("pm"))
810
+ i = 0
811
+ while True:
743
812
  try:
744
- ads: list[Ad] = await self.ads(coinex, curex, taker_side, pmexs)
745
- except Exception:
746
- await sleep(1)
747
- ads: list[Ad] = await self.ads(coinex, curex, taker_side, pmexs)
748
-
749
- if race.vm_filter:
750
- ads = [ad for ad in ads if "VA" in ad.authTag]
751
- self.overprice_filter(ads, race.ceil * 10**-curex.scale, k) # обрезаем сверху все ads дороже нашего потолка
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]
818
+ await self.bbot.send(
819
+ 193017646,
820
+ f"price: {ad.price}\nnick: {ad.nickName}\nprice: {ad.price}"
821
+ f"\nqty: {ad.quantity} [{ad.minAmount}-{ad.maxAmount}]",
822
+ )
823
+ am = min(float(ad.maxAmount), max(8000 + i, float(ad.minAmount)))
824
+ req = TakeAdReq(
825
+ ad_id=ad.id,
826
+ amount=am,
827
+ pm_id=14,
828
+ is_sell=False,
829
+ coin_id=1,
830
+ cur_id=1,
831
+ )
832
+ ord_resp: OrderResp = await self.take_ad(req)
833
+ # order: OrderFull = OrderFull(**self.api.get_order_details(orderId=ord_resp.orderId)["result"])
834
+ order: OrderFull = await self.get_order_info(ord_resp.orderId)
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)
846
+ if int(ad.userId) in mcs:
847
+ mcs[int(ad.userId)].api.release_assets(orderId=order.id)
752
848
 
753
- if 571 in pm_ids and coinex.coin.ticker == "USDT" and not taker_side:
754
- ...
849
+ await sleep(5)
755
850
 
756
- if not ads:
757
- print(coinex.exid, curex.exid, taker_side, "no ads!")
758
- await sleep(15)
759
- continue
760
- # определяем наше текущее место в уже обрезанном списке ads
761
- if not (cur_plc := [i for i, ad in enumerate(ads) if int(ad.userId) == self.actor.exid]):
762
- logging.warning(f"No racing in {pmexs[0].name} {'-' if taker_side else '+'}{coinex.exid}/{curex.exid}")
763
- await sleep(15)
764
- continue
765
- (cur_plc,) = cur_plc # может упасть если в списке > 1 наш ad
766
- [(await self.cond_upsert(ad, ps=race.road.ad.pair_side, force=True))[0] for ad in ads[:cur_plc]]
767
- # rivals = [
768
- # (await models.RaceStat.update_or_create({"place": plc, "price": ad.price, "premium": ad.premium}, ad=ad))[
769
- # 0
770
- # ]
771
- # for plc, ad in enumerate(rads)
772
- # ]
773
- mad: Ad = ads.pop(cur_plc)
774
- # if (
775
- # not (lstat := lstat or await race.stats.order_by("-created_at").first())
776
- # or lstat.place != cur_plc
777
- # or lstat.price != float(mad.price)
778
- # or set(rivals) != set(await lstat.rivals)
779
- # ):
780
- # lstat = await models.RaceStat.create(race=race, place=cur_plc, price=mad.price, premium=mad.premium)
781
- # await lstat.rivals.add(*rivals)
782
- if not ads:
783
- await sleep(60)
784
- continue
785
- if not (cad := self.get_cad(ads, race.ceil * 10**-curex.scale, k, race.target_place, cur_plc)):
786
- continue
787
- new_price = round(float(cad.price) - k * step(mad, cad, curex.cur.scale), curex.cur.scale)
788
- if (
789
- float(mad.price) == new_price and volume == last_vol
790
- ): # Если место уже нужное или нужная цена и так уже стоит
791
- print(
792
- f"{'v' if taker_side else '^'}{mad.price}",
793
- end=f"[{race.ceil * 10**-curex.scale}+{cur_plc}] ",
794
- flush=True,
795
- )
796
- await sleep(sleep_sec)
797
- continue
798
- if cad.priceType: # Если цена конкурента плавающая, то повышаем себе не цену, а %
799
- new_premium = float(cad.premium) - k * step(mad, cad, 2)
800
- if float(mad.premium) == new_premium: # Если нужный % и так уже стоит
801
- if mad.priceType and cur_plc != race.target_place:
802
- new_premium -= k * step(mad, cad, 2)
803
- elif volume == last_vol:
804
- print(end="v" if taker_side else "^", flush=True)
805
- await sleep(sleep_sec)
806
- continue
807
- mad.premium = str(round(new_premium, 2))
808
- mad.priceType = cad.priceType
809
- mad.quantity = volume
810
- mad.maxAmount = str(2_000_000)
811
- req = AdUpdateRequest.model_validate(
812
- {
813
- **mad.model_dump(),
814
- "price": str(round(new_price, curex.scale)),
815
- "paymentIds": [str(p.exid) for p in creds],
816
- }
817
- )
818
- try:
819
- print(
820
- f"c{race.ceil * 10**-curex.scale}+{cur_plc} {coinex.coin.ticker}{'-' if taker_side else '+'}{req.price}{curex.cur.ticker}"
821
- f"{[pm.norm for pm in race.road.ad.pms]}{f'({req.premium}%)' if req.premium != '0' else ''} "
822
- f"t{race.target_place} ;",
823
- flush=True,
824
- )
825
- _res = self.ad_upd(req)
826
- except FailedRequestError as e:
827
- if ExcCode(e.status_code) == ExcCode.FixPriceLimit:
828
- if limits := re.search(
829
- r"The fixed price set is lower than ([0-9]+\.?[0-9]{0,2}) or higher than ([0-9]+\.?[0-9]{0,2})",
830
- e.message,
831
- ):
832
- req.price = limits.group(1 if taker_side else 2)
833
- if req.price != mad.price:
834
- _res = self.ad_upd(req)
835
- else:
836
- raise e
837
- elif ExcCode(e.status_code) == ExcCode.InsufficientBalance:
838
- asset = await models.Asset.get(addr__actor=self.actor, addr__coin_id=coinex.coin_id)
839
- req.quantity = str(round(asset.free * 10**-coinex.scale, coinex.scale))
840
- _res = self.ad_upd(req)
841
- elif ExcCode(e.status_code) == ExcCode.RareLimit:
842
- sad = [
843
- ma
844
- for ma in self.my_ads(False)
845
- if (
846
- ma.currencyId == curex.exid
847
- and ma.tokenId == coinex.exid
848
- and taker_side == (not ma.side)
849
- and ma.payments == [pe.exid for pe in pmexs]
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]
856
+ await self.bbot.send(
857
+ 193017646,
858
+ f"price: {ad.price}\nnick: {ad.nickName}\nprice: {ad.price}"
859
+ f"\nqty: {ad.quantity} [{ad.minAmount}-{ad.maxAmount}]",
860
+ )
861
+ am = min(float(ad.maxAmount), max(10000 + i, float(ad.minAmount)))
862
+ req = TakeAdReq(
863
+ ad_id=ad.id,
864
+ amount=am,
865
+ pm_id=14,
866
+ is_sell=True,
867
+ coin_id=1,
868
+ cur_id=1,
869
+ )
870
+ ord_resp: OrderResp = await self.take_ad(req)
871
+ # order: OrderFull = OrderFull(**self.api.get_order_details(orderId=ord_resp.orderId)["result"])
872
+ order: OrderFull = await self.get_order_info(ord_resp.orderId)
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)
878
+ if int(ad.userId) in mcs:
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
850
883
  )
851
- ][0]
852
- self.ad_del(ad_id=int(mad.id))
853
- req.id = sad.id
854
- req.actionType = "ACTIVE"
855
- self.api.update_ad(**req.model_dump())
856
- logging.warning(f"Ad#{mad.id} recreated")
857
- elif ExcCode(e.status_code) == ExcCode.Timestamp:
858
884
  await sleep(3)
859
- else:
860
- raise e
861
- except (ReadTimeoutError, ConnectionDoesNotExistError):
862
- logging.warning("Connection failed. Restarting..")
863
- await sleep(6)
864
-
865
- async def get_books(
866
- self, coinex: models.CoinEx, curex: models.CurEx, pmexs: list[models.PmEx]
867
- ) -> tuple[list[Ad], list[Ad]]:
868
- buy: list[Ad] = await self.ads(coinex, curex, False, pmexs, None, 30)
869
- sell: list[Ad] = await self.ads(coinex, curex, True, pmexs, None, 30)
870
- return buy, sell
871
-
872
- async def get_spread(
873
- self, bb: list[Ad], sb: list[Ad], perc: float, vmf: bool = None, place: int = 0, exact: bool = False
874
- ) -> tuple[tuple[float, float], float, bool, int]:
875
- if len(bb) <= place or len(sb) <= place:
876
- ...
877
- buy_price, sell_price = float(bb[place].price), float(sb[place].price)
878
- half_spread = (buy_price - sell_price) / (buy_price + sell_price)
879
- if half_spread * 2 < perc:
880
- if not exact:
881
- if vmf is None: # сначала фильтруем только VA
882
- return await self.get_spread(bb, sb, perc, True, place)
883
- # если даже по VA не хватает спреда - увеличиваем место
884
- return await self.get_spread(bb, sb, perc, vmf, place + 1)
885
-
886
- return (buy_price, sell_price), half_spread, vmf, place
887
-
888
- async def get_ceils(
889
- self,
890
- coinex: models.CoinEx,
891
- curex: models.CurEx,
892
- pmexs: list[models.PmEx],
893
- min_prof=0.02,
894
- vmf: bool = False,
895
- place: int = 0,
896
- ) -> tuple[tuple[float, float], float, bool, int]: # todo: refact to Pairex
897
- bb, sb = await self.get_books(coinex, curex, pmexs)
898
- if vmf:
899
- bb = [b for b in bb if "VA" in b.authTag]
900
- sb = [s for s in sb if "VA" in s.authTag]
901
- perc = pmexs[0].pm.fee * 0.0001 + min_prof
902
- (bf, sf), hp, vmf, zplace = await self.get_spread(bb, sb, perc, vmf, place)
903
- mdl = (bf + sf) / 2
904
- bc, sc = mdl + mdl * (perc / 2), mdl - mdl * (perc / 2)
905
- return (bc, sc), hp, vmf, zplace
906
-
907
- async def parse_ads(
908
- self,
909
- coinex: models.CoinEx,
910
- curex: models.CurEx,
911
- taker_side: bool,
912
- pms: list[str] = None,
913
- ceil: float = None,
914
- volume: float = 9000,
915
- min_fiat: int = None,
916
- max_fiat: int = None,
917
- ):
918
- k = (-1) ** int(taker_side) # on_buy=1, on_sell=-1
919
-
920
- if pms:
921
- creds: dict[models.PmEx, models.CredEx] = await self.get_credexs_by_norms(pms, curex.cur_id)
922
- [str(p.exid) for p in creds.values()]
923
-
924
- if taker_side: # гонка в стакане продажи - мы покупаем монету за ФИАТ
925
- fiats = await models.Fiat.filter(
926
- cred_id__in=[cx.cred_id for cx in creds.values()], amount__not=F("target")
927
- )
928
- volume = min(volume, max(fiats, key=lambda f: f.target - f.amount).amount / ceil)
929
- else: # гонка в стакане покупки - мы продаем МОНЕТУ за фиат
930
- asset = await models.Asset.get(addr__actor=self.actor, addr__coin_id=coinex.coin_id)
931
- volume = min(volume, asset.free)
932
- volume = str(round(volume, coinex.coin.scale))
933
- ps = await PairSide.get(
934
- is_sell=taker_side,
935
- pair__coin_id=coinex.coin_id,
936
- pair__cur_id=curex.cur_id,
937
- )
938
- while self.actor.person.user.status > 0: # todo: depends on rest asset/fiat
939
- ads: list[Ad] = await self.ads(coinex, curex, taker_side, pms and list(creds.keys()))
940
-
941
- if not ads:
942
- print(coinex.exid, curex.exid, taker_side, "no ads!")
943
- await sleep(300)
944
- continue
885
+ self.api.release_assets(orderId=order.id)
886
+ await sleep(5)
945
887
 
946
- for i, ad in enumerate(ads):
947
- if (ceil - float(ad.price)) * k < 0:
948
- break
949
- if int(ad.userId) == self.actor.exid:
950
- logging.info(f"My ad {'-' if taker_side else '+'}{coinex.exid}/{curex.exid} on place#{i}")
951
- continue
952
- ad_db, isnew = await self.cond_upsert(ad, ps=ps)
953
- if isnew:
954
- s = f"{'-' if taker_side else '+'}{ad.price}[{ad.minAmount}-{ad.maxAmount}]{coinex.exid}/{curex.exid}"
955
- print(s, end=" | ", flush=True)
956
- try:
957
- # take
888
+ except Exception as e:
889
+ logging.exception(e)
890
+ await sleep(30)
891
+ except HttpProcessingError as e:
892
+ logging.error(e)
893
+ print(end=".", flush=True)
894
+ i += 1
895
+ await sleep(5)
896
+
897
+ async def boost_acc(self):
898
+ await sleep(45)
899
+ for i in range(10):
900
+ am = 500 + i
901
+ req = TakeAdReq(ad_id="1856989782009487360", amount=am, pm_id=366)
902
+ ord_resp: OrderResp = await self.take_ad(req)
903
+ order: OrderFull = OrderFull(**self.api.get_order_details(orderId=ord_resp.orderId)["result"])
904
+ odb = await self.create_order_db(order)
905
+ t = await models.Transfer(order=odb, amount=odb.amount, updated_at=now())
906
+ await t.fetch_related("order__cred__pmcur__cur")
907
+ await self.pm_clients[366].send(t)
908
+ ...
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")
958
1163
  ...
959
- except FailedRequestError as e:
960
- if ExcCode(e.status_code) == ExcCode.RareLimit:
961
- await sleep(195)
962
- elif ExcCode(e.status_code) == ExcCode.Timestamp:
963
- await sleep(2)
964
- else:
965
- raise e
966
- except (ReadTimeoutError, ConnectionDoesNotExistError):
967
- logging.warning("Connection failed. Restarting..")
968
- await sleep(3)
969
-
970
- async def cond_upsert(
971
- self, ad: Ad, rname: str = None, ps: PairSide = None, force: bool = False
972
- ) -> tuple[models.Ad, bool]:
973
- _sim, cid = None, None
974
- ad_db = await models.Ad.get_or_none(exid=ad.id, maker__ex=self.ex_client.ex).prefetch_related("cond")
975
- # если точно такое условие уже есть в бд
976
- if not (cleaned := clean(ad.remark)) or (cid := {oc[0]: ci for ci, oc in self.all_conds.items()}.get(cleaned)):
977
- # и объява с таким ид уже есть, но у нее другое условие
978
- if ad_db and ad_db.cond_id != cid:
979
- # то обновляем ид ее условия
980
- ad_db.cond_id = cid
981
- await ad_db.save()
982
- logging.info(f"{ad.nickName} upd cond#{ad_db.cond_id}->{cid}")
983
- # old_cid = ad_db.cond_id # todo: solve race-condition, а пока что очищаем при каждом запуске
984
- # if not len((old_cond := await Cond.get(id=old_cid).prefetch_related('ads')).ads):
985
- # await old_cond.delete()
986
- # logging.warning(f"Cond#{old_cid} deleted!")
987
- return (ad_db or force and await self.ad_create(ad, cid, rname, ps)), False
988
- # если эта объява в таким ид уже есть в бд, но с другим условием (или без), а текущего условия еще нет в бд
989
- if ad_db:
990
- await ad_db.fetch_related("cond__ads", "maker")
991
- if not ad_db.cond_id or (
992
- # у измененного условия этой объявы есть другие объявы?
993
- (rest_ads := set(ad_db.cond.ads) - {ad_db})
994
- and
995
- # другие объявы этого условия принадлежат другим юзерам
996
- {ra.maker_id for ra in rest_ads} - {ad_db.maker_id}
997
- ):
998
- # создадим новое условие и присвоим его только текущей объяве
999
- cid = await self.cond_new(cleaned, {int(ad.userId)})
1000
- ad_db.cond_id = cid
1001
- await ad_db.save()
1002
-
1003
- return ad_db, True
1004
- # а если других объяв со старым условием этой обявы нет, либо они все этого же юзера
1005
- # обновляем условие (в тч во всех ЕГО объявах)
1006
- ad_db.cond.last_ver = ad_db.cond.raw_txt
1007
- ad_db.cond.raw_txt = cleaned
1008
- await ad_db.cond.save()
1009
- await self.cond_upd(ad_db.cond, {ad_db.maker.exid})
1010
- # и подправим коэфициенты похожести нового текста
1011
- await self.fix_rel_sims(ad_db.cond_id, cleaned)
1012
- return ad_db, False
1013
-
1014
- cid = await self.cond_new(cleaned, {int(ad.userId)})
1015
- return await self.ad_create(ad, cid, rname, ps), True
1016
-
1017
- async def cond_new(self, txt: str, uids: set[int]) -> int:
1018
- new_cond, _ = await Cond.update_or_create(raw_txt=txt)
1019
- # и максимально похожую связь для нового условия (если есть >= 60%)
1020
- await self.cond_upd(new_cond, uids)
1021
- return new_cond.id
1022
-
1023
- async def cond_upd(self, cond: Cond, uids: set[int]):
1024
- self.all_conds[cond.id] = cond.raw_txt, uids
1025
- # и максимально похожую связь для нового условия (если есть >= 60%)
1026
- old_cid, sim = await self.cond_get_max_sim(cond.id, cond.raw_txt, uids)
1027
- await self.actual_sim(cond.id, old_cid, sim)
1028
-
1029
- def find_in_tree(self, cid: int, old_cid: int) -> bool:
1030
- if p := self.cond_sims.get(old_cid):
1031
- if p == cid:
1032
- return True
1033
- return self.find_in_tree(cid, p)
1034
- return False
1035
-
1036
- async def cond_get_max_sim(self, cid: int, txt: str, uids: set[int]) -> tuple[int | None, int | None]:
1037
- # находим все старые тексты похожие на 90% и более
1038
- if len(txt) < 15:
1039
- return None, None
1040
- sims: dict[int, int] = {}
1041
- for old_cid, (old_txt, old_uids) in self.all_conds.items():
1042
- if len(old_txt) < 15 or uids == old_uids:
1043
- continue
1044
- elif not self.can_add_sim(cid, old_cid):
1045
- continue
1046
- if sim := get_sim(txt, old_txt):
1047
- sims[old_cid] = sim
1048
- # если есть, берем самый похожий из них
1049
- if sims:
1050
- old_cid, sim = max(sims.items(), key=lambda x: x[1])
1051
- await sleep(0.3)
1052
- return old_cid, sim
1053
- return None, None
1054
-
1055
- def can_add_sim(self, cid: int, old_cid: int) -> bool:
1056
- if cid == old_cid:
1057
- return False
1058
- elif self.cond_sims.get(cid) == old_cid:
1059
- return False
1060
- elif self.find_in_tree(cid, old_cid):
1061
- return False
1062
- elif self.cond_sims.get(old_cid) == cid:
1063
- return False
1064
- elif cid in self.rcond_sims.get(old_cid, {}):
1065
- return False
1066
- elif old_cid in self.rcond_sims.get(cid, {}):
1067
- return False
1068
- return True
1069
-
1070
- async def person_upsert(self, name: str, exid: int) -> models.Person:
1071
- if actor := await models.Actor.get_or_none(exid=exid, ex=self.ex_client.ex).prefetch_related("person"):
1072
- if not actor.person:
1073
- actor.person = await models.Person.create(name=name)
1074
- await actor.save()
1075
- return actor.person
1076
- return await models.Person.create(name=name)
1077
-
1078
- async def ad_create(
1079
- self, ad: Ad, cid: int = None, rname: str = None, ps: PairSide = None, actor: Actor = None
1080
- ) -> models.Ad:
1081
- act_df = {}
1082
- if int(ad.userId) != self.actor.exid:
1083
- act_df |= {"name": ad.nickName}
1084
- if rname:
1085
- act_df |= {"person": await self.person_upsert(rname, int(ad.userId))}
1086
- if not actor:
1087
- act_df |= {"person": await self.person_upsert(ad.nickName, int(ad.userId))}
1088
- actor, _ = await Actor.update_or_create(act_df, exid=ad.userId, ex=self.ex_client.ex)
1089
- ps = ps or await PairSide.get_or_none(
1090
- is_sell=ad.side,
1091
- pair__coin__ticker=ad.tokenId,
1092
- pair__cur__ticker=ad.currencyId,
1093
- ).prefetch_related("pair__cur", "pair__coin")
1094
- if not ps or not ps.pair:
1095
- ... # THB/USDC
1096
- ad_upd = models.Ad.validate(ad.model_dump(by_alias=True))
1097
- cur_scale = 10**ps.pair.cur.scale
1098
- coinex = await models.CoinEx.get(coin_id=ps.pair.coin_id, ex=self.ex_client.ex)
1099
- df_unq = ad_upd.df_unq(
1100
- maker_id=actor.id,
1101
- pair_side_id=ps.id,
1102
- amount=int(float(ad.quantity) * float(ad.price) * cur_scale),
1103
- quantity=int(float(ad.quantity) * 10**coinex.scale),
1104
- min_fiat=int(float(ad.minAmount) * cur_scale),
1105
- max_fiat=ad.maxAmount and int(float(ad.maxAmount) * cur_scale),
1106
- price=int(float(ad.price) * cur_scale),
1107
- premium=int(float(ad.premium) * cur_scale),
1108
- )
1109
- ad_db, _ = await models.Ad.update_or_create(**df_unq)
1110
- await ad_db.pms.add(*(await models.Pm.filter(pmexs__ex=self.ex_client.ex, pmexs__exid__in=ad.payments)))
1111
- return ad_db
1112
-
1113
- async def fix_rel_sims(self, cid: int, new_txt: str):
1114
- for rel_sim in await CondSim.filter(cond_rel_id=cid).prefetch_related("cond"):
1115
- if sim := get_sim(new_txt, rel_sim.cond.raw_txt):
1116
- rel_sim.similarity = sim
1117
- await rel_sim.save()
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")
1118
1178
  else:
1119
- await rel_sim.delete()
1120
-
1121
- async def actual_cond(self):
1122
- for curr, old in await CondSim.all().values_list("cond_id", "cond_rel_id"):
1123
- self.cond_sims[curr] = old
1124
- self.rcond_sims[old] |= {curr}
1125
- for cid, (txt, uids) in self.all_conds.items():
1126
- old_cid, sim = await self.cond_get_max_sim(cid, txt, uids)
1127
- await self.actual_sim(cid, old_cid, sim)
1128
- # хз бля чо это ваще
1129
- # for ad_db in await models.Ad.filter(direction__pairex__ex=self.ex_client.ex).prefetch_related("cond", "maker"):
1130
- # ad = Ad(id=str(ad_db.exid), userId=str(ad_db.maker.exid), remark=ad_db.cond.raw_txt)
1131
- # await self.cond_upsert(ad, force=True)
1132
-
1133
- async def actual_sim(self, cid: int, old_cid: int, sim: int):
1134
- if not sim:
1135
- return
1136
- if old_sim := await CondSim.get_or_none(cond_id=cid):
1137
- if old_sim.cond_rel_id != old_cid:
1138
- if sim > old_sim.similarity:
1139
- logging.warning(f"R {cid}: {old_sim.similarity}->{sim} ({old_sim.cond_rel_id}->{old_cid})")
1140
- await old_sim.update_from_dict({"similarity": sim, "old_rel_id": old_cid}).save()
1141
- self._cond_sim_upd(cid, old_cid)
1142
- elif sim != old_sim.similarity:
1143
- logging.info(f"{cid}: {old_sim.similarity}->{sim}")
1144
- await old_sim.update_from_dict({"similarity": sim}).save()
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
1145
1208
  else:
1146
- await CondSim.create(cond_id=cid, cond_rel_id=old_cid, similarity=sim)
1147
- self._cond_sim_upd(cid, old_cid)
1148
-
1149
- def _cond_sim_upd(self, cid: int, old_cid: int):
1150
- if old_old_cid := self.cond_sims.get(cid): # если старый cid уже был в дереве:
1151
- self.rcond_sims[old_old_cid].remove(cid) # удаляем из обратного
1152
- self.cond_sims[cid] = old_cid # а в прямом он автоматом переопределится, даже если и был
1153
- self.rcond_sims[old_cid] |= {cid} # ну и в обратное добавим новый
1154
-
1155
- async def get_credexs_by_pms(self, pms: list[models.Pm], cur_id: int) -> list[models.CredEx]:
1156
- return await models.CredEx.filter(
1157
- ex_id=self.actor.ex_id,
1158
- cred__pmcur__pm_id__in=[pm.id for pm in pms],
1159
- cred__person_id=self.actor.person_id,
1160
- cred__pmcur__cur_id=cur_id,
1161
- )
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}")
1162
1213
 
1163
- def build_tree(self):
1164
- set(self.cond_sims.keys()) | set(self.cond_sims.values())
1165
- tree = defaultdict(dict)
1166
- # Группируем родителей по детям
1167
- for child, par in self.cond_sims.items():
1168
- tree[par] |= {child: {}} # todo: make from self.rcond_sim
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
+ )
1169
1237
 
1170
- # Строим дерево снизу вверх
1171
- def subtree(node):
1172
- if not node:
1173
- return node
1174
- for key in node:
1175
- subnode = tree.pop(key, {})
1176
- d = subtree(subnode)
1177
- node[key] |= d # actual tree rebuilding here!
1178
- return node # todo: refact?
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]
1179
1275
 
1180
- # Находим корни / без родителей
1181
- roots = set(self.cond_sims.values()) - set(self.cond_sims.keys())
1182
- for root in roots:
1183
- _ = subtree(tree[root])
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}")
1184
1281
 
1185
- self.tree = tree
1282
+ @staticmethod
1283
+ def listen(data: dict | None):
1284
+ # print(data)
1285
+ ...
1186
1286
 
1187
1287
 
1188
1288
  def ms2utc(msk_ts_str: str):
1189
1289
  return datetime.fromtimestamp(int(msk_ts_str) / 1000, timezone(timedelta(hours=3), name="MSK"))
1190
1290
 
1191
1291
 
1192
- def get_sim(s1, s2) -> int:
1193
- sim = int((SequenceMatcher(None, s1, s2).ratio() - 0.6) * 10_000)
1194
- return sim if sim > 0 else 0
1195
-
1196
-
1197
1292
  def detailed_diff(str1, str2):
1198
1293
  matcher = SequenceMatcher(None, str1, str2)
1199
1294
  result = []
@@ -1211,31 +1306,6 @@ def detailed_diff(str1, str2):
1211
1306
  return "".join(result)
1212
1307
 
1213
1308
 
1214
- def clean(s) -> str:
1215
- clear = r"[^\w\s.,!?;:()\-]"
1216
- repeat = r"(.)\1{2,}"
1217
- s = re.sub(clear, "", s).lower()
1218
- s = re.sub(repeat, r"\1", s)
1219
- return s.replace("\n\n", "\n").replace(" ", " ").strip(" \n/.,!?-")
1220
-
1221
-
1222
- def step_is_need(mad, cad) -> bool:
1223
- # todo: пока не решен непонятный кейс, почему то конкурент по всем параметрам слабже, но в списке ранжируется выше.
1224
- # текущая версия: recentExecuteRate округляется до целого, но на бэке байбита его дробная часть больше
1225
- return (
1226
- bool(set(cad.authTag) & {"VA2", "BA"})
1227
- or cad.recentExecuteRate > mad.recentExecuteRate
1228
- or (
1229
- cad.recentExecuteRate
1230
- == mad.recentExecuteRate # and cad.finishNum > mad.finishNum # пока прибавляем для равных
1231
- )
1232
- )
1233
-
1234
-
1235
- def step(mad, cad, scale: int = 2) -> float:
1236
- return float(int(step_is_need(mad, cad)) * 10**-scale).__round__(scale)
1237
-
1238
-
1239
1309
  class ExcCode(IntEnum):
1240
1310
  FixPriceLimit = 912120022
1241
1311
  RareLimit = 912120050
@@ -1243,37 +1313,82 @@ class ExcCode(IntEnum):
1243
1313
  Timestamp = 10002
1244
1314
  IP = 10010
1245
1315
  Quantity = 912300019
1316
+ PayMethod = 912300013
1246
1317
  Unknown = 912300014
1247
1318
 
1248
1319
 
1249
- async def main():
1250
- logging.basicConfig(level=logging.INFO)
1251
- _ = await init_db(TORM)
1320
+ @post_save(models.Race)
1321
+ async def race_upserted(
1322
+ _cls: type[models.Race], race: models.Race, created: bool, _db: BaseDBAsyncClient, _updated: list[str]
1323
+ ):
1324
+ logging.warning(f"Race {race.id} is now upserted")
1325
+ asyncio.all_tasks()
1326
+ if created:
1327
+ ...
1328
+ else: # параметры гонки изменены
1329
+ ...
1252
1330
 
1253
- @post_save(models.Race)
1254
- async def race_upserted(
1255
- _cls: type[models.Race], race: models.Race, created: bool, _db: BaseDBAsyncClient, _updated: list[str]
1256
- ):
1257
- logging.warning(f"Race {race.id} is now upserted")
1258
- asyncio.all_tasks()
1259
- if created:
1260
- ...
1261
- else: # параметры гонки изменены
1262
- ...
1263
1331
 
1264
- actor = (
1265
- await models.Actor.filter(ex_id=4, agent__isnull=False).prefetch_related("ex", "agent", "person__user").first()
1332
+ async def main():
1333
+ logging.basicConfig(level=logging.INFO)
1334
+ cn = await init_db(TORM)
1335
+
1336
+ agent = (
1337
+ await models.Agent.filter(actor__ex_id=4, auth__isnull=False, status__gt=AgentStatus.off, id=2)
1338
+ .prefetch_related(
1339
+ "actor__ex",
1340
+ "actor__person__user__gmail",
1341
+ "actor__my_ads__my_ad__race",
1342
+ "actor__my_ads__pair_side__pair__cur",
1343
+ "actor__my_ads__pms",
1344
+ )
1345
+ .first()
1266
1346
  )
1267
1347
  filebot = FileClient(NET_TOKEN)
1268
- await filebot.start()
1348
+ # await filebot.start()
1269
1349
  # b.add_handler(MessageHandler(cond_start_handler, command("cond")))
1270
- cl: AgentClient = actor.client(filebot)
1350
+ ex = await models.Ex.get(name="Bybit")
1351
+ ecl: ExClient = ex.client(filebot)
1352
+ abot = XyncBot(PAY_TOKEN, cn)
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)
1357
+
1358
+ # req = TakeAdReq(ad_id=1955696985964089344, amount=504, pm_id=128)
1359
+ # await cl.take_ad(req)
1360
+
1361
+ # await cl.actual_cond()
1362
+ # cl.get_api_orders(), # 10, 1738357200000, 1742504399999
1271
1363
 
1272
1364
  # await cl.ex_client.set_pairs()
1273
1365
  # await cl.ex_client.set_pms()
1366
+
1274
1367
  # await cl.set_creds()
1275
1368
  # await cl.export_my_ads()
1276
1369
 
1370
+ my_ad = await models.MyAd[5]
1371
+ await cl.ad_share(my_ad.id)
1372
+ await cl.start_listen()
1373
+
1374
+ ms = await models.Agent.filter(
1375
+ actor__ex_id=4, auth__isnull=False, status__gt=AgentStatus.off, actor__person__user__id__in=[3]
1376
+ ).prefetch_related(
1377
+ "actor__ex",
1378
+ "actor__person__user__gmail",
1379
+ "actor__my_ads__my_ad__race",
1380
+ "actor__my_ads__pair_side__pair__cur",
1381
+ "actor__my_ads__pms",
1382
+ )
1383
+ mcs = {m.actor.exid: m.client(ecl, filebot, abot) for m in ms}
1384
+
1385
+ await gather(
1386
+ # create_task(cl.start()),
1387
+ create_task(cl.watch_payeer(mcs)),
1388
+ )
1389
+ # ensure_future(cl.start(True))
1390
+ # await cl.boost_acc()
1391
+
1277
1392
  # создание гонок по мои активным объявам:
1278
1393
  # for ma in cl.my_ads():
1279
1394
  # my_ad = await models.MyAd.get(ad__exid=ma.id).prefetch_related('ad__pms', 'ad__pair_side__pair')
@@ -1286,28 +1401,6 @@ async def main():
1286
1401
  # s, _ = await models.Synonym.update_or_create(typ=SynonymType.name, txt=name)
1287
1402
  # await s.curs.add(rub.cur)
1288
1403
 
1289
- # пока порешали рейс-кондишн, очищаем сиротские условия при каждом запуске
1290
- # [await c.delete() for c in await Cond.filter(ads__isnull=True)]
1291
- cl.all_conds = {
1292
- c.id: (c.raw_txt, {a.maker.exid for a in c.ads}) for c in await Cond.all().prefetch_related("ads__maker")
1293
- }
1294
- for curr, old in await CondSim.filter().values_list("cond_id", "cond_rel_id"):
1295
- cl.cond_sims[curr] = old
1296
- cl.rcond_sims[old] |= {curr}
1297
-
1298
- cl.build_tree()
1299
- a = set()
1300
-
1301
- def check_tree(tre):
1302
- for p, c in tre.items():
1303
- a.add(p)
1304
- check_tree(c)
1305
-
1306
- for pr, ch in cl.tree.items():
1307
- check_tree(ch)
1308
- if ct := set(cl.tree.keys()) & a:
1309
- logging.exception(f"cycle cids: {ct}")
1310
-
1311
1404
  pauth = (await models.PmAgent[1]).auth
1312
1405
  papi = PayeerAPI(pauth["email"], pauth["api_id"], pauth["api_sec"])
1313
1406
  hist: dict = papi.history(count=1000)
@@ -1320,32 +1413,6 @@ async def main():
1320
1413
  # )
1321
1414
  # await cl.get_api_orders() # 43, 1741294800000, 1749157199999)
1322
1415
 
1323
- races = await models.Race.filter(started=True).prefetch_related(
1324
- "road__ad__pair_side__pair__cur",
1325
- "road__ad__pms",
1326
- )
1327
- tasks = [asyncio.create_task(cl.racing(race), name=f"Rc{race.id}") for race in races]
1328
- # await cl.actual_cond()
1329
- try:
1330
- await gather(
1331
- *tasks
1332
- # cl.get_api_orders(), # 10, 1738357200000, 1742504399999
1333
- )
1334
- except Exception as e:
1335
- await filebot.send("🤬Bybit agent CRASHED!!!🤬", actor.person.user.username_id)
1336
- await filebot.send(e.__repr__(), actor.person.user.username_id)
1337
- raise e
1338
- # bor = BaseOrderReq(
1339
- # ad_id="1861440060199632896",
1340
- # # asset_amount=40,
1341
- # fiat_amount=3000,
1342
- # amount_is_fiat=True,
1343
- # is_sell=False,
1344
- # cur_exid=rub.exid,
1345
- # coin_exid=usdt.exid,
1346
- # coin_scale=usdt.coin.scale,
1347
- # )
1348
- # res: OrderResp = await cl.order_request(bor)
1349
1416
  # await cl.cancel_order(res.orderId)
1350
1417
  await filebot.stop()
1351
1418
  await cl.close()