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.
- xync_client/Abc/AdLoader.py +5 -0
- xync_client/Abc/Agent.py +354 -8
- xync_client/Abc/Ex.py +432 -25
- xync_client/Abc/HasAbotUid.py +10 -0
- xync_client/Abc/InAgent.py +0 -11
- xync_client/Abc/PmAgent.py +34 -26
- xync_client/Abc/xtype.py +57 -3
- xync_client/Bybit/InAgent.py +233 -409
- xync_client/Bybit/agent.py +844 -777
- xync_client/Bybit/etype/__init__.py +0 -0
- xync_client/Bybit/etype/ad.py +54 -86
- xync_client/Bybit/etype/cred.py +29 -9
- xync_client/Bybit/etype/order.py +75 -103
- xync_client/Bybit/ex.py +35 -48
- xync_client/Gmail/__init__.py +119 -98
- xync_client/Htx/agent.py +213 -40
- xync_client/Htx/etype/ad.py +40 -16
- xync_client/Htx/etype/order.py +194 -0
- xync_client/Htx/ex.py +17 -19
- xync_client/Mexc/agent.py +268 -0
- xync_client/Mexc/api.py +1255 -0
- xync_client/Mexc/etype/ad.py +52 -1
- xync_client/Mexc/etype/order.py +354 -0
- xync_client/Mexc/ex.py +34 -22
- xync_client/Okx/1.py +14 -0
- xync_client/Okx/agent.py +39 -0
- xync_client/Okx/ex.py +8 -8
- xync_client/Pms/Payeer/agent.py +396 -0
- xync_client/Pms/Payeer/login.py +1 -59
- xync_client/Pms/Payeer/trade.py +58 -0
- xync_client/Pms/Volet/__init__.py +82 -63
- xync_client/Pms/Volet/api.py +5 -4
- xync_client/loader.py +2 -0
- xync_client/pm_unifier.py +1 -1
- {xync_client-0.0.141.dist-info → xync_client-0.0.156.dev18.dist-info}/METADATA +5 -1
- {xync_client-0.0.141.dist-info → xync_client-0.0.156.dev18.dist-info}/RECORD +38 -29
- xync_client/Pms/Payeer/__init__.py +0 -253
- xync_client/Pms/Payeer/api.py +0 -25
- {xync_client-0.0.141.dist-info → xync_client-0.0.156.dev18.dist-info}/WHEEL +0 -0
- {xync_client-0.0.141.dist-info → xync_client-0.0.156.dev18.dist-info}/top_level.txt +0 -0
xync_client/Bybit/agent.py
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
|
26
|
+
from tortoise.expressions import Q
|
|
22
27
|
from tortoise.functions import Count
|
|
23
28
|
from tortoise.signals import post_save
|
|
24
|
-
from
|
|
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,
|
|
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,
|
|
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.
|
|
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
|
-
|
|
55
|
-
|
|
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__(
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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
|
-
|
|
119
|
-
|
|
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#{
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
"
|
|
159
|
-
|
|
160
|
-
"
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
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
|
|
207
|
-
fiat
|
|
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
|
|
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[
|
|
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 = [
|
|
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
|
-
|
|
265
|
-
|
|
266
|
-
|
|
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
|
-
|
|
277
|
-
|
|
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
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
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
|
|
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
|
|
343
|
-
|
|
344
|
-
if
|
|
345
|
-
|
|
346
|
-
|
|
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=
|
|
349
|
-
tokenId=
|
|
350
|
-
currencyId=
|
|
351
|
-
side=
|
|
352
|
-
amount=
|
|
353
|
-
curPrice=
|
|
354
|
-
quantity=str(
|
|
355
|
-
flag="amount"
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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) ->
|
|
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
|
|
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
|
-
|
|
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":
|
|
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
|
|
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
|
-
|
|
444
|
-
im_maker = int(order.makerUserId ==
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
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=
|
|
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=
|
|
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.
|
|
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.
|
|
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
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
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
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
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
|
-
|
|
754
|
-
...
|
|
849
|
+
await sleep(5)
|
|
755
850
|
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
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
|
-
|
|
860
|
-
|
|
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
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
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}×tamp={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
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
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
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
await
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
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
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
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
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
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
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
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
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
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
|
-
|
|
1265
|
-
|
|
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
|
-
|
|
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()
|