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