xync-client 0.0.147__py3-none-any.whl → 0.0.148__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.
Potentially problematic release.
This version of xync-client might be problematic. Click here for more details.
- xync_client/Abc/AdLoader.py +285 -0
- xync_client/Abc/Ex.py +11 -9
- xync_client/Bybit/agent.py +82 -346
- xync_client/Bybit/etype/ad.py +1 -1
- xync_client/Bybit/etype/cred.py +1 -0
- {xync_client-0.0.147.dist-info → xync_client-0.0.148.dist-info}/METADATA +4 -1
- {xync_client-0.0.147.dist-info → xync_client-0.0.148.dist-info}/RECORD +9 -8
- {xync_client-0.0.147.dist-info → xync_client-0.0.148.dist-info}/WHEEL +0 -0
- {xync_client-0.0.147.dist-info → xync_client-0.0.148.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import re
|
|
3
|
+
from asyncio import sleep
|
|
4
|
+
from collections import defaultdict
|
|
5
|
+
from difflib import SequenceMatcher
|
|
6
|
+
|
|
7
|
+
from xync_schema import models
|
|
8
|
+
from xync_schema.xtype import BaseAd
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class AdLoader:
|
|
12
|
+
ex: models.Ex
|
|
13
|
+
all_conds: dict[int, tuple[str, set[int]]] = {}
|
|
14
|
+
cond_sims: dict[int, int] = defaultdict(set)
|
|
15
|
+
rcond_sims: dict[int, set[int]] = defaultdict(set) # backward
|
|
16
|
+
tree: dict = {}
|
|
17
|
+
|
|
18
|
+
async def old_conds_load(self):
|
|
19
|
+
# пока не порешали рейс-кондишн, очищаем сиротские условия при каждом запуске
|
|
20
|
+
# [await c.delete() for c in await Cond.filter(ads__isnull=True)]
|
|
21
|
+
self.all_conds = {
|
|
22
|
+
c.id: (c.raw_txt, {a.maker.exid for a in c.ads})
|
|
23
|
+
for c in await models.Cond.all().prefetch_related("ads__maker")
|
|
24
|
+
}
|
|
25
|
+
for curr, old in await models.CondSim.filter().values_list("cond_id", "cond_rel_id"):
|
|
26
|
+
self.cond_sims[curr] = old
|
|
27
|
+
self.rcond_sims[old] |= {curr}
|
|
28
|
+
|
|
29
|
+
self.build_tree()
|
|
30
|
+
a = set()
|
|
31
|
+
|
|
32
|
+
def check_tree(tre):
|
|
33
|
+
for p, c in tre.items():
|
|
34
|
+
a.add(p)
|
|
35
|
+
check_tree(c)
|
|
36
|
+
|
|
37
|
+
for pr, ch in self.tree.items():
|
|
38
|
+
check_tree(ch)
|
|
39
|
+
if ct := set(self.tree.keys()) & a:
|
|
40
|
+
logging.exception(f"cycle cids: {ct}")
|
|
41
|
+
|
|
42
|
+
async def person_name_update(self, name: str, exid: int) -> models.Person:
|
|
43
|
+
if actor := await models.Actor.get_or_none(exid=exid, ex=self.ex).prefetch_related("person"):
|
|
44
|
+
actor.person.name = name
|
|
45
|
+
await actor.person.save()
|
|
46
|
+
return actor.person
|
|
47
|
+
# return await models.Person.create(note=f'{actor.ex_id}:{actor.exid}:{name}') # no person for just ads with no orders
|
|
48
|
+
raise ValueError(f"Agent #{exid} not found")
|
|
49
|
+
|
|
50
|
+
async def ad_load(
|
|
51
|
+
self,
|
|
52
|
+
pad: BaseAd,
|
|
53
|
+
cid: int = None,
|
|
54
|
+
ps: models.PairSide = None,
|
|
55
|
+
maker: models.Actor = None,
|
|
56
|
+
coinex: models.CoinEx = None,
|
|
57
|
+
curex: models.CurEx = None,
|
|
58
|
+
rname: str = None,
|
|
59
|
+
pms_from_cond: bool = False,
|
|
60
|
+
) -> models.Ad:
|
|
61
|
+
if not maker:
|
|
62
|
+
if not (maker := await models.Actor.get_or_none(exid=pad.userId, ex=self.ex)):
|
|
63
|
+
person = await models.Person.create(name=rname, note=f"{self.ex.id}:{pad.userId}:{pad.nickName}")
|
|
64
|
+
maker = await models.Actor.create(name=pad.nickName, person=person, exid=pad.userId, ex=self.ex)
|
|
65
|
+
if rname:
|
|
66
|
+
await self.person_name_update(rname, int(pad.userId))
|
|
67
|
+
ps = ps or await models.PairSide.get_or_none(
|
|
68
|
+
is_sell=pad.side,
|
|
69
|
+
pair__coin__ticker=pad.tokenId,
|
|
70
|
+
pair__cur__ticker=pad.currencyId,
|
|
71
|
+
).prefetch_related("pair")
|
|
72
|
+
# if not ps or not ps.pair:
|
|
73
|
+
# ... # THB/USDC: just for initial filling
|
|
74
|
+
ad_upd = models.Ad.validate(pad.model_dump(by_alias=True))
|
|
75
|
+
cur_scale = 10 ** (curex or await models.CurEx.get(coin_id=ps.pair.cur_id, ex=self.ex)).scale
|
|
76
|
+
coin_scale = 10 ** (coinex or await models.CoinEx.get(coin_id=ps.pair.coin_id, ex=self.ex)).scale
|
|
77
|
+
df_unq = ad_upd.df_unq(
|
|
78
|
+
maker_id=maker.id,
|
|
79
|
+
pair_side_id=ps.id,
|
|
80
|
+
amount=int(float(pad.quantity) * float(pad.price) * cur_scale),
|
|
81
|
+
quantity=int(float(pad.quantity) * coin_scale),
|
|
82
|
+
min_fiat=int(float(pad.minAmount) * cur_scale),
|
|
83
|
+
max_fiat=pad.maxAmount and int(float(pad.maxAmount) * cur_scale),
|
|
84
|
+
price=int(float(pad.price) * cur_scale),
|
|
85
|
+
premium=int(float(pad.premium) * 100),
|
|
86
|
+
cond_id=cid,
|
|
87
|
+
)
|
|
88
|
+
ad_db, _ = await models.Ad.update_or_create(**df_unq)
|
|
89
|
+
if not pms_from_cond:
|
|
90
|
+
await ad_db.pms.add(*(await models.Pm.filter(pmexs__ex=self.ex, pmexs__exid__in=pad.payments)))
|
|
91
|
+
return ad_db
|
|
92
|
+
|
|
93
|
+
async def cond_load( # todo: refact from Bybit Ad format to universal
|
|
94
|
+
self,
|
|
95
|
+
ad: BaseAd,
|
|
96
|
+
ps: models.PairSide = None,
|
|
97
|
+
force: bool = False,
|
|
98
|
+
rname: str = None,
|
|
99
|
+
coinex: models.CoinEx = None,
|
|
100
|
+
curex: models.CurEx = None,
|
|
101
|
+
pms_from_cond: bool = False,
|
|
102
|
+
) -> tuple[models.Ad, bool]:
|
|
103
|
+
_sim, cid = None, None
|
|
104
|
+
ad_db = await models.Ad.get_or_none(exid=ad.id, maker__ex=self.ex).prefetch_related("cond")
|
|
105
|
+
# если точно такое условие уже есть в бд
|
|
106
|
+
if not (cleaned := clean(ad.remark)) or (cid := {oc[0]: ci for ci, oc in self.all_conds.items()}.get(cleaned)):
|
|
107
|
+
# и объява с таким ид уже есть, но у нее другое условие
|
|
108
|
+
if ad_db and ad_db.cond_id != cid:
|
|
109
|
+
# то обновляем ид ее условия
|
|
110
|
+
ad_db.cond_id = cid
|
|
111
|
+
await ad_db.save()
|
|
112
|
+
logging.info(f"{ad.nickName} upd cond#{ad_db.cond_id}->{cid}")
|
|
113
|
+
# old_cid = ad_db.cond_id # todo: solve race-condition, а пока что очищаем при каждом запуске
|
|
114
|
+
# if not len((old_cond := await Cond.get(id=old_cid).prefetch_related('ads')).ads):
|
|
115
|
+
# await old_cond.delete()
|
|
116
|
+
# logging.warning(f"Cond#{old_cid} deleted!")
|
|
117
|
+
return (
|
|
118
|
+
ad_db
|
|
119
|
+
or force
|
|
120
|
+
and await self.ad_load(
|
|
121
|
+
ad, cid, ps, coinex=coinex, curex=curex, rname=rname, pms_from_cond=pms_from_cond
|
|
122
|
+
)
|
|
123
|
+
), False
|
|
124
|
+
# если эта объява в таким ид уже есть в бд, но с другим условием (или без), а текущего условия еще нет в бд
|
|
125
|
+
if ad_db:
|
|
126
|
+
await ad_db.fetch_related("cond__ads", "maker")
|
|
127
|
+
if not ad_db.cond_id or (
|
|
128
|
+
# у измененного условия этой объявы есть другие объявы?
|
|
129
|
+
(rest_ads := set(ad_db.cond.ads) - {ad_db})
|
|
130
|
+
and
|
|
131
|
+
# другие объявы этого условия принадлежат другим юзерам
|
|
132
|
+
{ra.maker_id for ra in rest_ads} - {ad_db.maker_id}
|
|
133
|
+
):
|
|
134
|
+
# создадим новое условие и присвоим его только текущей объяве
|
|
135
|
+
cid = await self.cond_new(cleaned, {int(ad.userId)})
|
|
136
|
+
ad_db.cond_id = cid
|
|
137
|
+
await ad_db.save()
|
|
138
|
+
|
|
139
|
+
return ad_db, True
|
|
140
|
+
# а если других объяв со старым условием этой обявы нет, либо они все этого же юзера
|
|
141
|
+
# обновляем условие (в тч во всех ЕГО объявах)
|
|
142
|
+
ad_db.cond.last_ver = ad_db.cond.raw_txt
|
|
143
|
+
ad_db.cond.raw_txt = cleaned
|
|
144
|
+
await ad_db.cond.save()
|
|
145
|
+
await self.cond_upd(ad_db.cond, {ad_db.maker.exid})
|
|
146
|
+
# и подправим коэфициенты похожести нового текста
|
|
147
|
+
await self.fix_rel_sims(ad_db.cond_id, cleaned)
|
|
148
|
+
return ad_db, False
|
|
149
|
+
|
|
150
|
+
cid = await self.cond_new(cleaned, {int(ad.userId)})
|
|
151
|
+
return await self.ad_load(
|
|
152
|
+
ad, cid, ps, coinex=coinex, curex=curex, rname=rname, pms_from_cond=pms_from_cond
|
|
153
|
+
), True
|
|
154
|
+
|
|
155
|
+
async def cond_new(self, txt: str, uids: set[int]) -> int:
|
|
156
|
+
new_cond, _ = await models.Cond.update_or_create(raw_txt=txt)
|
|
157
|
+
# и максимально похожую связь для нового условия (если есть >= 60%)
|
|
158
|
+
await self.cond_upd(new_cond, uids)
|
|
159
|
+
return new_cond.id
|
|
160
|
+
|
|
161
|
+
async def cond_upd(self, cond: models.Cond, uids: set[int]):
|
|
162
|
+
self.all_conds[cond.id] = cond.raw_txt, uids
|
|
163
|
+
# и максимально похожую связь для нового условия (если есть >= 60%)
|
|
164
|
+
old_cid, sim = await self.cond_get_max_sim(cond.id, cond.raw_txt, uids)
|
|
165
|
+
await self.actual_sim(cond.id, old_cid, sim)
|
|
166
|
+
|
|
167
|
+
def find_in_tree(self, cid: int, old_cid: int) -> bool:
|
|
168
|
+
if p := self.cond_sims.get(old_cid):
|
|
169
|
+
if p == cid:
|
|
170
|
+
return True
|
|
171
|
+
return self.find_in_tree(cid, p)
|
|
172
|
+
return False
|
|
173
|
+
|
|
174
|
+
async def cond_get_max_sim(self, cid: int, txt: str, uids: set[int]) -> tuple[int | None, int | None]:
|
|
175
|
+
# находим все старые тексты похожие на 90% и более
|
|
176
|
+
if len(txt) < 15:
|
|
177
|
+
return None, None
|
|
178
|
+
sims: dict[int, int] = {}
|
|
179
|
+
for old_cid, (old_txt, old_uids) in self.all_conds.items():
|
|
180
|
+
if len(old_txt) < 15 or uids == old_uids:
|
|
181
|
+
continue
|
|
182
|
+
elif not self.can_add_sim(cid, old_cid):
|
|
183
|
+
continue
|
|
184
|
+
if sim := get_sim(txt, old_txt):
|
|
185
|
+
sims[old_cid] = sim
|
|
186
|
+
# если есть, берем самый похожий из них
|
|
187
|
+
if sims:
|
|
188
|
+
old_cid, sim = max(sims.items(), key=lambda x: x[1])
|
|
189
|
+
await sleep(0.3)
|
|
190
|
+
return old_cid, sim
|
|
191
|
+
return None, None
|
|
192
|
+
|
|
193
|
+
def can_add_sim(self, cid: int, old_cid: int) -> bool:
|
|
194
|
+
if cid == old_cid:
|
|
195
|
+
return False
|
|
196
|
+
elif self.cond_sims.get(cid) == old_cid:
|
|
197
|
+
return False
|
|
198
|
+
elif self.find_in_tree(cid, old_cid):
|
|
199
|
+
return False
|
|
200
|
+
elif self.cond_sims.get(old_cid) == cid:
|
|
201
|
+
return False
|
|
202
|
+
elif cid in self.rcond_sims.get(old_cid, {}):
|
|
203
|
+
return False
|
|
204
|
+
elif old_cid in self.rcond_sims.get(cid, {}):
|
|
205
|
+
return False
|
|
206
|
+
return True
|
|
207
|
+
|
|
208
|
+
async def fix_rel_sims(self, cid: int, new_txt: str):
|
|
209
|
+
for rel_sim in await models.CondSim.filter(cond_rel_id=cid).prefetch_related("cond"):
|
|
210
|
+
if sim := get_sim(new_txt, rel_sim.cond.raw_txt):
|
|
211
|
+
rel_sim.similarity = sim
|
|
212
|
+
await rel_sim.save()
|
|
213
|
+
else:
|
|
214
|
+
await rel_sim.delete()
|
|
215
|
+
|
|
216
|
+
async def actual_cond(self):
|
|
217
|
+
for curr, old in await models.CondSim.all().values_list("cond_id", "cond_rel_id"):
|
|
218
|
+
self.cond_sims[curr] = old
|
|
219
|
+
self.rcond_sims[old] |= {curr}
|
|
220
|
+
for cid, (txt, uids) in self.all_conds.items():
|
|
221
|
+
old_cid, sim = await self.cond_get_max_sim(cid, txt, uids)
|
|
222
|
+
await self.actual_sim(cid, old_cid, sim)
|
|
223
|
+
# хз бля чо это ваще
|
|
224
|
+
# for ad_db in await models.Ad.filter(direction__pairex__ex=self.ex).prefetch_related("cond", "maker"):
|
|
225
|
+
# ad = Ad(id=str(ad_db.exid), userId=str(ad_db.maker.exid), remark=ad_db.cond.raw_txt)
|
|
226
|
+
# await self.cond_upsert(ad, force=True)
|
|
227
|
+
|
|
228
|
+
async def actual_sim(self, cid: int, old_cid: int, sim: int):
|
|
229
|
+
if not sim:
|
|
230
|
+
return
|
|
231
|
+
if old_sim := await models.CondSim.get_or_none(cond_id=cid):
|
|
232
|
+
if old_sim.cond_rel_id != old_cid:
|
|
233
|
+
if sim > old_sim.similarity:
|
|
234
|
+
logging.warning(f"R {cid}: {old_sim.similarity}->{sim} ({old_sim.cond_rel_id}->{old_cid})")
|
|
235
|
+
await old_sim.update_from_dict({"similarity": sim, "old_rel_id": old_cid}).save()
|
|
236
|
+
self._cond_sim_upd(cid, old_cid)
|
|
237
|
+
elif sim != old_sim.similarity:
|
|
238
|
+
logging.info(f"{cid}: {old_sim.similarity}->{sim}")
|
|
239
|
+
await old_sim.update_from_dict({"similarity": sim}).save()
|
|
240
|
+
else:
|
|
241
|
+
await models.CondSim.create(cond_id=cid, cond_rel_id=old_cid, similarity=sim)
|
|
242
|
+
self._cond_sim_upd(cid, old_cid)
|
|
243
|
+
|
|
244
|
+
def _cond_sim_upd(self, cid: int, old_cid: int):
|
|
245
|
+
if old_old_cid := self.cond_sims.get(cid): # если старый cid уже был в дереве:
|
|
246
|
+
self.rcond_sims[old_old_cid].remove(cid) # удаляем из обратного
|
|
247
|
+
self.cond_sims[cid] = old_cid # а в прямом он автоматом переопределится, даже если и был
|
|
248
|
+
self.rcond_sims[old_cid] |= {cid} # ну и в обратное добавим новый
|
|
249
|
+
|
|
250
|
+
def build_tree(self):
|
|
251
|
+
set(self.cond_sims.keys()) | set(self.cond_sims.values())
|
|
252
|
+
tree = defaultdict(dict)
|
|
253
|
+
# Группируем родителей по детям
|
|
254
|
+
for child, par in self.cond_sims.items():
|
|
255
|
+
tree[par] |= {child: {}} # todo: make from self.rcond_sim
|
|
256
|
+
|
|
257
|
+
# Строим дерево снизу вверх
|
|
258
|
+
def subtree(node):
|
|
259
|
+
if not node:
|
|
260
|
+
return node
|
|
261
|
+
for key in node:
|
|
262
|
+
subnode = tree.pop(key, {})
|
|
263
|
+
d = subtree(subnode)
|
|
264
|
+
node[key] |= d # actual tree rebuilding here!
|
|
265
|
+
return node # todo: refact?
|
|
266
|
+
|
|
267
|
+
# Находим корни / без родителей
|
|
268
|
+
roots = set(self.cond_sims.values()) - set(self.cond_sims.keys())
|
|
269
|
+
for root in roots:
|
|
270
|
+
_ = subtree(tree[root])
|
|
271
|
+
|
|
272
|
+
self.tree = tree
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def get_sim(s1, s2) -> int:
|
|
276
|
+
sim = int((SequenceMatcher(None, s1, s2).ratio() - 0.6) * 10_000)
|
|
277
|
+
return sim if sim > 0 else 0
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def clean(s) -> str:
|
|
281
|
+
clear = r"[^\w\s.,!?;:()\-]"
|
|
282
|
+
repeat = r"(.)\1{2,}"
|
|
283
|
+
s = re.sub(clear, "", s).lower()
|
|
284
|
+
s = re.sub(repeat, r"\1", s)
|
|
285
|
+
return s.replace("\n\n", "\n").replace(" ", " ").strip(" \n/.,!?-")
|
xync_client/Abc/Ex.py
CHANGED
|
@@ -11,11 +11,12 @@ from xync_schema import models
|
|
|
11
11
|
from xync_schema.enums import FileType
|
|
12
12
|
from xync_schema.xtype import CurEx, CoinEx, BaseAd, BaseAdIn
|
|
13
13
|
|
|
14
|
+
from xync_client.Abc.AdLoader import AdLoader
|
|
14
15
|
from xync_client.Abc.xtype import PmEx, MapOfIdsList
|
|
15
16
|
from xync_client.pm_unifier import PmUnifier, PmUni
|
|
16
17
|
|
|
17
18
|
|
|
18
|
-
class BaseExClient(HttpClient):
|
|
19
|
+
class BaseExClient(HttpClient, AdLoader):
|
|
19
20
|
cur_map: dict[int, str] = {}
|
|
20
21
|
unifier_class: type = PmUnifier
|
|
21
22
|
logo_pre_url: str
|
|
@@ -72,6 +73,7 @@ class BaseExClient(HttpClient):
|
|
|
72
73
|
pm_exids: list[str | int] = None,
|
|
73
74
|
amount: int = None,
|
|
74
75
|
lim: int = None,
|
|
76
|
+
vm_filter: bool = False,
|
|
75
77
|
) -> list[BaseAd]: # {ad.id: ad}
|
|
76
78
|
...
|
|
77
79
|
|
|
@@ -250,14 +252,14 @@ class BaseExClient(HttpClient):
|
|
|
250
252
|
return True
|
|
251
253
|
|
|
252
254
|
# Сохранение чужого объявления (с Pm-ами) в бд
|
|
253
|
-
async def ad_pydin2db(self, ad_pydin: BaseAdIn) -> models.Ad:
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
255
|
+
# async def ad_pydin2db(self, ad_pydin: BaseAdIn) -> models.Ad:
|
|
256
|
+
# dct = ad_pydin.model_dump()
|
|
257
|
+
# dct["exid"] = dct.pop("id")
|
|
258
|
+
# ad_in = models.Ad.validate(dct)
|
|
259
|
+
# ad_db, _ = await models.Ad.update_or_create(**ad_in.df_unq())
|
|
260
|
+
# await ad_db.credexs.add(*getattr(ad_pydin, "credexs_", []))
|
|
261
|
+
# await ad_db.pmexs.add(*getattr(ad_pydin, "pmexs_", []))
|
|
262
|
+
# return ad_db
|
|
261
263
|
|
|
262
264
|
async def file_upsert(self, url: str, ss: ClientSession = None) -> models.File:
|
|
263
265
|
if not (file := await models.File.get_or_none(name__startswith=url.split("?")[0])):
|
xync_client/Bybit/agent.py
CHANGED
|
@@ -2,7 +2,6 @@ import asyncio
|
|
|
2
2
|
import logging
|
|
3
3
|
import re
|
|
4
4
|
from asyncio import sleep, gather
|
|
5
|
-
from collections import defaultdict
|
|
6
5
|
from datetime import datetime, timedelta, timezone
|
|
7
6
|
from difflib import SequenceMatcher
|
|
8
7
|
from enum import IntEnum
|
|
@@ -18,7 +17,7 @@ from payeer_api import PayeerAPI
|
|
|
18
17
|
from pyro_client.client.file import FileClient
|
|
19
18
|
from tortoise import BaseDBAsyncClient
|
|
20
19
|
from tortoise.exceptions import IntegrityError
|
|
21
|
-
from tortoise.expressions import
|
|
20
|
+
from tortoise.expressions import Q
|
|
22
21
|
from tortoise.functions import Count
|
|
23
22
|
from tortoise.signals import post_save
|
|
24
23
|
from urllib3.exceptions import ReadTimeoutError
|
|
@@ -28,7 +27,7 @@ from xync_bot import XyncBot
|
|
|
28
27
|
from xync_schema import models
|
|
29
28
|
from xync_schema.enums import OrderStatus
|
|
30
29
|
|
|
31
|
-
from xync_schema.models import Actor,
|
|
30
|
+
from xync_schema.models import Actor, PmCur, Agent
|
|
32
31
|
|
|
33
32
|
from xync_client.Abc.Agent import BaseAgentClient
|
|
34
33
|
from xync_client.Abc.xtype import FlatDict, BaseOrderReq
|
|
@@ -85,16 +84,12 @@ class AgentClient(BaseAgentClient): # Bybit client
|
|
|
85
84
|
"actionType": "MODIFY",
|
|
86
85
|
"securityRiskToken": "",
|
|
87
86
|
}
|
|
88
|
-
all_conds: dict[int, tuple[str, set[int]]] = {}
|
|
89
|
-
cond_sims: dict[int, int] = defaultdict(set)
|
|
90
|
-
rcond_sims: dict[int, set[int]] = defaultdict(set) # backward
|
|
91
|
-
tree: dict = {}
|
|
92
87
|
|
|
93
88
|
def __init__(self, agent: Agent, fbot: FileClient, bbot: XyncBot, **kwargs):
|
|
94
89
|
super().__init__(agent, fbot, bbot, **kwargs)
|
|
95
90
|
self.api = P2P(testnet=False, api_key=agent.auth["key"], api_secret=agent.auth["sec"])
|
|
96
|
-
self.hist: dict = None
|
|
97
|
-
self.completed_orders: list[int] = None
|
|
91
|
+
self.hist: dict | None = None
|
|
92
|
+
self.completed_orders: list[int] | None = None
|
|
98
93
|
|
|
99
94
|
""" Private METHs"""
|
|
100
95
|
|
|
@@ -118,12 +113,8 @@ class AgentClient(BaseAgentClient): # Bybit client
|
|
|
118
113
|
else:
|
|
119
114
|
return logging.exception(method1)
|
|
120
115
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
if fiat_id:
|
|
124
|
-
fiat = [m for m in list_methods if m["id"] == fiat_id][0]
|
|
125
|
-
return fiat
|
|
126
|
-
return list_methods[1]
|
|
116
|
+
def get_payment_method(self, fiat_id: int) -> CredEpyd:
|
|
117
|
+
return self.creds()[fiat_id]
|
|
127
118
|
|
|
128
119
|
def creds(self) -> dict[int, CredEpyd]:
|
|
129
120
|
data = self.api.get_user_payment_types()
|
|
@@ -206,13 +197,13 @@ class AgentClient(BaseAgentClient): # Bybit client
|
|
|
206
197
|
# 27
|
|
207
198
|
async def fiat_upd(self, fiat_id: int, detail: str, name: str = None) -> dict:
|
|
208
199
|
fiat = self.get_payment_method(fiat_id)
|
|
209
|
-
fiat
|
|
210
|
-
fiat
|
|
211
|
-
result = await self._post("/fiat/otc/user/payment/new_update", fiat)
|
|
200
|
+
fiat.realName = name
|
|
201
|
+
fiat.accountNo = detail
|
|
202
|
+
result = await self._post("/fiat/otc/user/payment/new_update", fiat.model_dump(exclude_none=True))
|
|
212
203
|
srt = result["result"]["securityRiskToken"]
|
|
213
204
|
await self._check_2fa(srt)
|
|
214
|
-
fiat
|
|
215
|
-
result2 = await self._post("/fiat/otc/user/payment/new_update", fiat)
|
|
205
|
+
fiat.securityRiskToken = srt
|
|
206
|
+
result2 = await self._post("/fiat/otc/user/payment/new_update", fiat.model_dump(exclude_none=True))
|
|
216
207
|
return result2
|
|
217
208
|
|
|
218
209
|
# 28
|
|
@@ -244,10 +235,6 @@ class AgentClient(BaseAgentClient): # Bybit client
|
|
|
244
235
|
cnx.exid, crx.exid, is_sell, [pmex.exid for pmex in pmxs or []], amount, lim, vm_filter
|
|
245
236
|
)
|
|
246
237
|
|
|
247
|
-
def online_ads(self) -> str:
|
|
248
|
-
online = self._get("/fiat/otc/maker/work-config/get")
|
|
249
|
-
return online["result"]["workStatus"]
|
|
250
|
-
|
|
251
238
|
@staticmethod
|
|
252
239
|
def get_rate(list_ads: list) -> float:
|
|
253
240
|
ads = [ad for ad in list_ads if set(ad["payments"]) - {"5", "51"}]
|
|
@@ -264,7 +251,7 @@ class AgentClient(BaseAgentClient): # Bybit client
|
|
|
264
251
|
ads = self.my_ads(True)
|
|
265
252
|
if not active:
|
|
266
253
|
ads += self.my_ads(False)
|
|
267
|
-
res = [await self.
|
|
254
|
+
res = [await self.ex_client.ad_load(ad, maker=self.actor) for ad in ads]
|
|
268
255
|
res = [await models.MyAd.update_or_create(ad=ad) for ad in res]
|
|
269
256
|
return len(res)
|
|
270
257
|
|
|
@@ -494,12 +481,14 @@ class AgentClient(BaseAgentClient): # Bybit client
|
|
|
494
481
|
maker_name = order.buyerRealName, order.sellerRealName
|
|
495
482
|
im_maker = int(order.makerUserId == order.userId)
|
|
496
483
|
taker_id = (order.userId, order.targetUserId)[im_maker]
|
|
497
|
-
taker_person = await self.
|
|
484
|
+
taker_person = await self.ex_client.person_name_update(maker_name[::-1][ad.side], taker_id)
|
|
498
485
|
seller_person = (
|
|
499
|
-
self.actor.person
|
|
486
|
+
self.actor.person
|
|
487
|
+
if order.side
|
|
488
|
+
else await self.ex_client.person_name_update(order.sellerRealName, int(order.targetUserId))
|
|
500
489
|
)
|
|
501
490
|
taker_nick = (self.actor.name, order.targetNickName)[im_maker] # todo: check
|
|
502
|
-
ad_db, cond_isnew = await self.
|
|
491
|
+
ad_db, cond_isnew = await self.ex_client.cond_load(ad, force=True, rname=maker_name[ad.side])
|
|
503
492
|
if not ad_db:
|
|
504
493
|
...
|
|
505
494
|
ecredex: CredEpyd = order.confirmedPayTerm
|
|
@@ -602,7 +591,7 @@ class AgentClient(BaseAgentClient): # Bybit client
|
|
|
602
591
|
buyer_person = (
|
|
603
592
|
self.actor.person
|
|
604
593
|
if not order.side
|
|
605
|
-
else await self.
|
|
594
|
+
else await self.ex_client.person_name_update(order.buyerRealName, int(order.targetUserId))
|
|
606
595
|
)
|
|
607
596
|
ts = [t for t in tsa if floor(fa := float(order.amount)) <= float(t["creditedAmount"]) <= round(fa)]
|
|
608
597
|
if len(ts) != 1:
|
|
@@ -755,7 +744,7 @@ class AgentClient(BaseAgentClient): # Bybit client
|
|
|
755
744
|
pmexs: list[models.PmEx] = await models.PmEx.filter(pm_id__in=pm_ids, ex=self.actor.ex).prefetch_related("pm")
|
|
756
745
|
k = (-1) ** int(taker_side) # on_buy=1, on_sell=-1
|
|
757
746
|
sleep_sec = 3 # 1 if set(pms) & {"volet"} and coinex.coin_id == 1 else 5
|
|
758
|
-
creds: list[models.CredEx] = await self.
|
|
747
|
+
creds: list[models.CredEx] = await self.actor.get_credexs_by(pm_ids, curex.cur_id)
|
|
759
748
|
_lstat, volume = None, 0
|
|
760
749
|
|
|
761
750
|
while self.actor.person.user.status > 0:
|
|
@@ -814,7 +803,7 @@ class AgentClient(BaseAgentClient): # Bybit client
|
|
|
814
803
|
await sleep(15)
|
|
815
804
|
continue
|
|
816
805
|
(cur_plc,) = cur_plc # может упасть если в списке > 1 наш ad
|
|
817
|
-
[(await self.
|
|
806
|
+
[(await self.ex_client.cond_load(ad, race.road.ad.pair_side, True))[0] for ad in ads[:cur_plc]]
|
|
818
807
|
# rivals = [
|
|
819
808
|
# (await models.RaceStat.update_or_create({"place": plc, "price": ad.price, "premium": ad.premium}, ad=ad))[
|
|
820
809
|
# 0
|
|
@@ -955,286 +944,6 @@ class AgentClient(BaseAgentClient): # Bybit client
|
|
|
955
944
|
bc, sc = mdl + mdl * (perc / 2), mdl - mdl * (perc / 2)
|
|
956
945
|
return (bc, sc), hp, vmf, zplace
|
|
957
946
|
|
|
958
|
-
async def parse_ads(
|
|
959
|
-
self,
|
|
960
|
-
coinex: models.CoinEx,
|
|
961
|
-
curex: models.CurEx,
|
|
962
|
-
taker_side: bool,
|
|
963
|
-
pms: list[str] = None,
|
|
964
|
-
ceil: float = None,
|
|
965
|
-
volume: float = 9000,
|
|
966
|
-
min_fiat: int = None,
|
|
967
|
-
max_fiat: int = None,
|
|
968
|
-
):
|
|
969
|
-
k = (-1) ** int(taker_side) # on_buy=1, on_sell=-1
|
|
970
|
-
|
|
971
|
-
if pms:
|
|
972
|
-
creds: dict[models.PmEx, models.CredEx] = await self.get_credexs_by_norms(pms, curex.cur_id)
|
|
973
|
-
[str(p.exid) for p in creds.values()]
|
|
974
|
-
|
|
975
|
-
if taker_side: # гонка в стакане продажи - мы покупаем монету за ФИАТ
|
|
976
|
-
fiats = await models.Fiat.filter(
|
|
977
|
-
cred_id__in=[cx.cred_id for cx in creds.values()], amount__not=F("target")
|
|
978
|
-
)
|
|
979
|
-
volume = min(volume, max(fiats, key=lambda f: f.target - f.amount).amount / ceil)
|
|
980
|
-
else: # гонка в стакане покупки - мы продаем МОНЕТУ за фиат
|
|
981
|
-
asset = await models.Asset.get(addr__actor=self.actor, addr__coin_id=coinex.coin_id)
|
|
982
|
-
volume = min(volume, asset.free)
|
|
983
|
-
volume = str(round(volume, coinex.coin.scale))
|
|
984
|
-
ps = await PairSide.get(
|
|
985
|
-
is_sell=taker_side,
|
|
986
|
-
pair__coin_id=coinex.coin_id,
|
|
987
|
-
pair__cur_id=curex.cur_id,
|
|
988
|
-
)
|
|
989
|
-
while self.actor.person.user.status > 0: # todo: depends on rest asset/fiat
|
|
990
|
-
ads: list[Ad] = await self.ads(coinex, curex, taker_side, pms and list(creds.keys()))
|
|
991
|
-
|
|
992
|
-
if not ads:
|
|
993
|
-
print(coinex.exid, curex.exid, taker_side, "no ads!")
|
|
994
|
-
await sleep(300)
|
|
995
|
-
continue
|
|
996
|
-
|
|
997
|
-
for i, ad in enumerate(ads):
|
|
998
|
-
if (ceil - float(ad.price)) * k < 0:
|
|
999
|
-
break
|
|
1000
|
-
if int(ad.userId) == self.actor.exid:
|
|
1001
|
-
logging.info(f"My ad {'-' if taker_side else '+'}{coinex.exid}/{curex.exid} on place#{i}")
|
|
1002
|
-
continue
|
|
1003
|
-
ad_db, isnew = await self.cond_upsert(ad, ps=ps)
|
|
1004
|
-
if isnew:
|
|
1005
|
-
s = f"{'-' if taker_side else '+'}{ad.price}[{ad.minAmount}-{ad.maxAmount}]{coinex.exid}/{curex.exid}"
|
|
1006
|
-
print(s, end=" | ", flush=True)
|
|
1007
|
-
try:
|
|
1008
|
-
# take
|
|
1009
|
-
...
|
|
1010
|
-
except FailedRequestError as e:
|
|
1011
|
-
if ExcCode(e.status_code) == ExcCode.RareLimit:
|
|
1012
|
-
await sleep(195)
|
|
1013
|
-
elif ExcCode(e.status_code) == ExcCode.Timestamp:
|
|
1014
|
-
await sleep(2)
|
|
1015
|
-
else:
|
|
1016
|
-
raise e
|
|
1017
|
-
except (ReadTimeoutError, ConnectionDoesNotExistError):
|
|
1018
|
-
logging.warning("Connection failed. Restarting..")
|
|
1019
|
-
await sleep(3)
|
|
1020
|
-
|
|
1021
|
-
async def cond_upsert(
|
|
1022
|
-
self, ad: Ad, rname: str = None, ps: PairSide = None, force: bool = False
|
|
1023
|
-
) -> tuple[models.Ad, bool]:
|
|
1024
|
-
_sim, cid = None, None
|
|
1025
|
-
ad_db = await models.Ad.get_or_none(exid=ad.id, maker__ex=self.ex_client.ex).prefetch_related("cond")
|
|
1026
|
-
# если точно такое условие уже есть в бд
|
|
1027
|
-
if not (cleaned := clean(ad.remark)) or (cid := {oc[0]: ci for ci, oc in self.all_conds.items()}.get(cleaned)):
|
|
1028
|
-
# и объява с таким ид уже есть, но у нее другое условие
|
|
1029
|
-
if ad_db and ad_db.cond_id != cid:
|
|
1030
|
-
# то обновляем ид ее условия
|
|
1031
|
-
ad_db.cond_id = cid
|
|
1032
|
-
await ad_db.save()
|
|
1033
|
-
logging.info(f"{ad.nickName} upd cond#{ad_db.cond_id}->{cid}")
|
|
1034
|
-
# old_cid = ad_db.cond_id # todo: solve race-condition, а пока что очищаем при каждом запуске
|
|
1035
|
-
# if not len((old_cond := await Cond.get(id=old_cid).prefetch_related('ads')).ads):
|
|
1036
|
-
# await old_cond.delete()
|
|
1037
|
-
# logging.warning(f"Cond#{old_cid} deleted!")
|
|
1038
|
-
return (ad_db or force and await self.ad_create(ad, cid, rname, ps)), False
|
|
1039
|
-
# если эта объява в таким ид уже есть в бд, но с другим условием (или без), а текущего условия еще нет в бд
|
|
1040
|
-
if ad_db:
|
|
1041
|
-
await ad_db.fetch_related("cond__ads", "maker")
|
|
1042
|
-
if not ad_db.cond_id or (
|
|
1043
|
-
# у измененного условия этой объявы есть другие объявы?
|
|
1044
|
-
(rest_ads := set(ad_db.cond.ads) - {ad_db})
|
|
1045
|
-
and
|
|
1046
|
-
# другие объявы этого условия принадлежат другим юзерам
|
|
1047
|
-
{ra.maker_id for ra in rest_ads} - {ad_db.maker_id}
|
|
1048
|
-
):
|
|
1049
|
-
# создадим новое условие и присвоим его только текущей объяве
|
|
1050
|
-
cid = await self.cond_new(cleaned, {int(ad.userId)})
|
|
1051
|
-
ad_db.cond_id = cid
|
|
1052
|
-
await ad_db.save()
|
|
1053
|
-
|
|
1054
|
-
return ad_db, True
|
|
1055
|
-
# а если других объяв со старым условием этой обявы нет, либо они все этого же юзера
|
|
1056
|
-
# обновляем условие (в тч во всех ЕГО объявах)
|
|
1057
|
-
ad_db.cond.last_ver = ad_db.cond.raw_txt
|
|
1058
|
-
ad_db.cond.raw_txt = cleaned
|
|
1059
|
-
await ad_db.cond.save()
|
|
1060
|
-
await self.cond_upd(ad_db.cond, {ad_db.maker.exid})
|
|
1061
|
-
# и подправим коэфициенты похожести нового текста
|
|
1062
|
-
await self.fix_rel_sims(ad_db.cond_id, cleaned)
|
|
1063
|
-
return ad_db, False
|
|
1064
|
-
|
|
1065
|
-
cid = await self.cond_new(cleaned, {int(ad.userId)})
|
|
1066
|
-
return await self.ad_create(ad, cid, rname, ps), True
|
|
1067
|
-
|
|
1068
|
-
async def cond_new(self, txt: str, uids: set[int]) -> int:
|
|
1069
|
-
new_cond, _ = await Cond.update_or_create(raw_txt=txt)
|
|
1070
|
-
# и максимально похожую связь для нового условия (если есть >= 60%)
|
|
1071
|
-
await self.cond_upd(new_cond, uids)
|
|
1072
|
-
return new_cond.id
|
|
1073
|
-
|
|
1074
|
-
async def cond_upd(self, cond: Cond, uids: set[int]):
|
|
1075
|
-
self.all_conds[cond.id] = cond.raw_txt, uids
|
|
1076
|
-
# и максимально похожую связь для нового условия (если есть >= 60%)
|
|
1077
|
-
old_cid, sim = await self.cond_get_max_sim(cond.id, cond.raw_txt, uids)
|
|
1078
|
-
await self.actual_sim(cond.id, old_cid, sim)
|
|
1079
|
-
|
|
1080
|
-
def find_in_tree(self, cid: int, old_cid: int) -> bool:
|
|
1081
|
-
if p := self.cond_sims.get(old_cid):
|
|
1082
|
-
if p == cid:
|
|
1083
|
-
return True
|
|
1084
|
-
return self.find_in_tree(cid, p)
|
|
1085
|
-
return False
|
|
1086
|
-
|
|
1087
|
-
async def cond_get_max_sim(self, cid: int, txt: str, uids: set[int]) -> tuple[int | None, int | None]:
|
|
1088
|
-
# находим все старые тексты похожие на 90% и более
|
|
1089
|
-
if len(txt) < 15:
|
|
1090
|
-
return None, None
|
|
1091
|
-
sims: dict[int, int] = {}
|
|
1092
|
-
for old_cid, (old_txt, old_uids) in self.all_conds.items():
|
|
1093
|
-
if len(old_txt) < 15 or uids == old_uids:
|
|
1094
|
-
continue
|
|
1095
|
-
elif not self.can_add_sim(cid, old_cid):
|
|
1096
|
-
continue
|
|
1097
|
-
if sim := get_sim(txt, old_txt):
|
|
1098
|
-
sims[old_cid] = sim
|
|
1099
|
-
# если есть, берем самый похожий из них
|
|
1100
|
-
if sims:
|
|
1101
|
-
old_cid, sim = max(sims.items(), key=lambda x: x[1])
|
|
1102
|
-
await sleep(0.3)
|
|
1103
|
-
return old_cid, sim
|
|
1104
|
-
return None, None
|
|
1105
|
-
|
|
1106
|
-
def can_add_sim(self, cid: int, old_cid: int) -> bool:
|
|
1107
|
-
if cid == old_cid:
|
|
1108
|
-
return False
|
|
1109
|
-
elif self.cond_sims.get(cid) == old_cid:
|
|
1110
|
-
return False
|
|
1111
|
-
elif self.find_in_tree(cid, old_cid):
|
|
1112
|
-
return False
|
|
1113
|
-
elif self.cond_sims.get(old_cid) == cid:
|
|
1114
|
-
return False
|
|
1115
|
-
elif cid in self.rcond_sims.get(old_cid, {}):
|
|
1116
|
-
return False
|
|
1117
|
-
elif old_cid in self.rcond_sims.get(cid, {}):
|
|
1118
|
-
return False
|
|
1119
|
-
return True
|
|
1120
|
-
|
|
1121
|
-
async def person_upsert(self, name: str, exid: int) -> models.Person:
|
|
1122
|
-
if actor := await models.Actor.get_or_none(exid=exid, ex=self.ex_client.ex).prefetch_related("person"):
|
|
1123
|
-
if not actor.person:
|
|
1124
|
-
actor.person = await models.Person.create(name=name)
|
|
1125
|
-
await actor.save()
|
|
1126
|
-
return actor.person
|
|
1127
|
-
return await models.Person.create(name=name)
|
|
1128
|
-
|
|
1129
|
-
async def ad_create(
|
|
1130
|
-
self, ad: Ad, cid: int = None, rname: str = None, ps: PairSide = None, actor: Actor = None
|
|
1131
|
-
) -> models.Ad:
|
|
1132
|
-
act_df = {}
|
|
1133
|
-
if int(ad.userId) != self.actor.exid:
|
|
1134
|
-
act_df |= {"name": ad.nickName}
|
|
1135
|
-
if rname:
|
|
1136
|
-
act_df |= {"person": await self.person_upsert(rname, int(ad.userId))}
|
|
1137
|
-
if not actor:
|
|
1138
|
-
act_df |= {"person": await self.person_upsert(ad.nickName, int(ad.userId))}
|
|
1139
|
-
actor, _ = await Actor.update_or_create(act_df, exid=ad.userId, ex=self.ex_client.ex)
|
|
1140
|
-
ps = ps or await PairSide.get_or_none(
|
|
1141
|
-
is_sell=ad.side,
|
|
1142
|
-
pair__coin__ticker=ad.tokenId,
|
|
1143
|
-
pair__cur__ticker=ad.currencyId,
|
|
1144
|
-
).prefetch_related("pair__cur", "pair__coin")
|
|
1145
|
-
if not ps or not ps.pair:
|
|
1146
|
-
... # THB/USDC
|
|
1147
|
-
ad_upd = models.Ad.validate(ad.model_dump(by_alias=True))
|
|
1148
|
-
cur_scale = 10**ps.pair.cur.scale
|
|
1149
|
-
coinex = await models.CoinEx.get(coin_id=ps.pair.coin_id, ex=self.ex_client.ex)
|
|
1150
|
-
df_unq = ad_upd.df_unq(
|
|
1151
|
-
maker_id=actor.id,
|
|
1152
|
-
pair_side_id=ps.id,
|
|
1153
|
-
amount=int(float(ad.quantity) * float(ad.price) * cur_scale),
|
|
1154
|
-
quantity=int(float(ad.quantity) * 10**coinex.scale),
|
|
1155
|
-
min_fiat=int(float(ad.minAmount) * cur_scale),
|
|
1156
|
-
max_fiat=ad.maxAmount and int(float(ad.maxAmount) * cur_scale),
|
|
1157
|
-
price=int(float(ad.price) * cur_scale),
|
|
1158
|
-
premium=int(float(ad.premium) * cur_scale),
|
|
1159
|
-
)
|
|
1160
|
-
ad_db, _ = await models.Ad.update_or_create(**df_unq)
|
|
1161
|
-
await ad_db.pms.add(*(await models.Pm.filter(pmexs__ex=self.ex_client.ex, pmexs__exid__in=ad.payments)))
|
|
1162
|
-
return ad_db
|
|
1163
|
-
|
|
1164
|
-
async def fix_rel_sims(self, cid: int, new_txt: str):
|
|
1165
|
-
for rel_sim in await CondSim.filter(cond_rel_id=cid).prefetch_related("cond"):
|
|
1166
|
-
if sim := get_sim(new_txt, rel_sim.cond.raw_txt):
|
|
1167
|
-
rel_sim.similarity = sim
|
|
1168
|
-
await rel_sim.save()
|
|
1169
|
-
else:
|
|
1170
|
-
await rel_sim.delete()
|
|
1171
|
-
|
|
1172
|
-
async def actual_cond(self):
|
|
1173
|
-
for curr, old in await CondSim.all().values_list("cond_id", "cond_rel_id"):
|
|
1174
|
-
self.cond_sims[curr] = old
|
|
1175
|
-
self.rcond_sims[old] |= {curr}
|
|
1176
|
-
for cid, (txt, uids) in self.all_conds.items():
|
|
1177
|
-
old_cid, sim = await self.cond_get_max_sim(cid, txt, uids)
|
|
1178
|
-
await self.actual_sim(cid, old_cid, sim)
|
|
1179
|
-
# хз бля чо это ваще
|
|
1180
|
-
# for ad_db in await models.Ad.filter(direction__pairex__ex=self.ex_client.ex).prefetch_related("cond", "maker"):
|
|
1181
|
-
# ad = Ad(id=str(ad_db.exid), userId=str(ad_db.maker.exid), remark=ad_db.cond.raw_txt)
|
|
1182
|
-
# await self.cond_upsert(ad, force=True)
|
|
1183
|
-
|
|
1184
|
-
async def actual_sim(self, cid: int, old_cid: int, sim: int):
|
|
1185
|
-
if not sim:
|
|
1186
|
-
return
|
|
1187
|
-
if old_sim := await CondSim.get_or_none(cond_id=cid):
|
|
1188
|
-
if old_sim.cond_rel_id != old_cid:
|
|
1189
|
-
if sim > old_sim.similarity:
|
|
1190
|
-
logging.warning(f"R {cid}: {old_sim.similarity}->{sim} ({old_sim.cond_rel_id}->{old_cid})")
|
|
1191
|
-
await old_sim.update_from_dict({"similarity": sim, "old_rel_id": old_cid}).save()
|
|
1192
|
-
self._cond_sim_upd(cid, old_cid)
|
|
1193
|
-
elif sim != old_sim.similarity:
|
|
1194
|
-
logging.info(f"{cid}: {old_sim.similarity}->{sim}")
|
|
1195
|
-
await old_sim.update_from_dict({"similarity": sim}).save()
|
|
1196
|
-
else:
|
|
1197
|
-
await CondSim.create(cond_id=cid, cond_rel_id=old_cid, similarity=sim)
|
|
1198
|
-
self._cond_sim_upd(cid, old_cid)
|
|
1199
|
-
|
|
1200
|
-
def _cond_sim_upd(self, cid: int, old_cid: int):
|
|
1201
|
-
if old_old_cid := self.cond_sims.get(cid): # если старый cid уже был в дереве:
|
|
1202
|
-
self.rcond_sims[old_old_cid].remove(cid) # удаляем из обратного
|
|
1203
|
-
self.cond_sims[cid] = old_cid # а в прямом он автоматом переопределится, даже если и был
|
|
1204
|
-
self.rcond_sims[old_cid] |= {cid} # ну и в обратное добавим новый
|
|
1205
|
-
|
|
1206
|
-
async def get_credexs_by_pms(self, pms: list[models.Pm], cur_id: int) -> list[models.CredEx]:
|
|
1207
|
-
return await models.CredEx.filter(
|
|
1208
|
-
ex_id=self.actor.ex_id,
|
|
1209
|
-
cred__pmcur__pm_id__in=[pm.id for pm in pms],
|
|
1210
|
-
cred__person_id=self.actor.person_id,
|
|
1211
|
-
cred__pmcur__cur_id=cur_id,
|
|
1212
|
-
)
|
|
1213
|
-
|
|
1214
|
-
def build_tree(self):
|
|
1215
|
-
set(self.cond_sims.keys()) | set(self.cond_sims.values())
|
|
1216
|
-
tree = defaultdict(dict)
|
|
1217
|
-
# Группируем родителей по детям
|
|
1218
|
-
for child, par in self.cond_sims.items():
|
|
1219
|
-
tree[par] |= {child: {}} # todo: make from self.rcond_sim
|
|
1220
|
-
|
|
1221
|
-
# Строим дерево снизу вверх
|
|
1222
|
-
def subtree(node):
|
|
1223
|
-
if not node:
|
|
1224
|
-
return node
|
|
1225
|
-
for key in node:
|
|
1226
|
-
subnode = tree.pop(key, {})
|
|
1227
|
-
d = subtree(subnode)
|
|
1228
|
-
node[key] |= d # actual tree rebuilding here!
|
|
1229
|
-
return node # todo: refact?
|
|
1230
|
-
|
|
1231
|
-
# Находим корни / без родителей
|
|
1232
|
-
roots = set(self.cond_sims.values()) - set(self.cond_sims.keys())
|
|
1233
|
-
for root in roots:
|
|
1234
|
-
_ = subtree(tree[root])
|
|
1235
|
-
|
|
1236
|
-
self.tree = tree
|
|
1237
|
-
|
|
1238
947
|
async def take_ad(self, req: TakeAdReq):
|
|
1239
948
|
ad: Ad = Ad.model_validate(self.api.get_ad_details(itemId=req.ad_id))
|
|
1240
949
|
bor = BaseOrderReq(
|
|
@@ -1249,16 +958,73 @@ class AgentClient(BaseAgentClient): # Bybit client
|
|
|
1249
958
|
resp: OrderResp = await self._order_request(bor)
|
|
1250
959
|
return resp
|
|
1251
960
|
|
|
961
|
+
# async def parse_ads(
|
|
962
|
+
# self,
|
|
963
|
+
# coinex: models.CoinEx,
|
|
964
|
+
# curex: models.CurEx,
|
|
965
|
+
# taker_side: bool,
|
|
966
|
+
# pms: list[str] = None,
|
|
967
|
+
# ceil: float = None,
|
|
968
|
+
# volume: float = 9000,
|
|
969
|
+
# min_fiat: int = None,
|
|
970
|
+
# max_fiat: int = None,
|
|
971
|
+
# ):
|
|
972
|
+
# k = (-1) ** int(taker_side) # on_buy=1, on_sell=-1
|
|
973
|
+
# if pms:
|
|
974
|
+
# creds: dict[models.PmEx, models.CredEx] = await self.get_credexs_by_norms(pms, curex.cur_id)
|
|
975
|
+
# [str(p.exid) for p in creds.values()]
|
|
976
|
+
#
|
|
977
|
+
# if taker_side: # гонка в стакане продажи - мы покупаем монету за ФИАТ
|
|
978
|
+
# fiats = await models.Fiat.filter(
|
|
979
|
+
# cred_id__in=[cx.cred_id for cx in creds.values()], amount__not=F("target")
|
|
980
|
+
# )
|
|
981
|
+
# volume = min(volume, max(fiats, key=lambda f: f.target - f.amount).amount / ceil)
|
|
982
|
+
# else: # гонка в стакане покупки - мы продаем МОНЕТУ за фиат
|
|
983
|
+
# asset = await models.Asset.get(addr__actor=self.actor, addr__coin_id=coinex.coin_id)
|
|
984
|
+
# volume = min(volume, asset.free)
|
|
985
|
+
# volume = str(round(volume, coinex.coin.scale))
|
|
986
|
+
# ps = await PairSide.get(
|
|
987
|
+
# is_sell=taker_side,
|
|
988
|
+
# pair__coin_id=coinex.coin_id,
|
|
989
|
+
# pair__cur_id=curex.cur_id,
|
|
990
|
+
# )
|
|
991
|
+
# while self.actor.person.user.status > 0: # todo: depends on rest asset/fiat
|
|
992
|
+
# ads: list[Ad] = await self.ads(coinex, curex, taker_side, pms and list(creds.keys()))
|
|
993
|
+
#
|
|
994
|
+
# if not ads:
|
|
995
|
+
# print(coinex.exid, curex.exid, taker_side, "no ads!")
|
|
996
|
+
# await sleep(300)
|
|
997
|
+
# continue
|
|
998
|
+
#
|
|
999
|
+
# for i, ad in enumerate(ads):
|
|
1000
|
+
# if (ceil - float(ad.price)) * k < 0:
|
|
1001
|
+
# break
|
|
1002
|
+
# if int(ad.userId) == self.actor.exid:
|
|
1003
|
+
# logging.info(f"My ad {'-' if taker_side else '+'}{coinex.exid}/{curex.exid} on place#{i}")
|
|
1004
|
+
# continue
|
|
1005
|
+
# ad_db, isnew = await self.cond_upsert(ad, ps=ps)
|
|
1006
|
+
# if isnew:
|
|
1007
|
+
# s = f"{'-' if taker_side else '+'}{ad.price}[{ad.minAmount}-{ad.maxAmount}]{coinex.exid}/{curex.exid}"
|
|
1008
|
+
# print(s, end=" | ", flush=True)
|
|
1009
|
+
# try:
|
|
1010
|
+
# # take
|
|
1011
|
+
# ...
|
|
1012
|
+
# except FailedRequestError as e:
|
|
1013
|
+
# if ExcCode(e.status_code) == ExcCode.RareLimit:
|
|
1014
|
+
# await sleep(195)
|
|
1015
|
+
# elif ExcCode(e.status_code) == ExcCode.Timestamp:
|
|
1016
|
+
# await sleep(2)
|
|
1017
|
+
# else:
|
|
1018
|
+
# raise e
|
|
1019
|
+
# except (ReadTimeoutError, ConnectionDoesNotExistError):
|
|
1020
|
+
# logging.warning("Connection failed. Restarting..")
|
|
1021
|
+
# await sleep(3)
|
|
1022
|
+
|
|
1252
1023
|
|
|
1253
1024
|
def ms2utc(msk_ts_str: str):
|
|
1254
1025
|
return datetime.fromtimestamp(int(msk_ts_str) / 1000, timezone(timedelta(hours=3), name="MSK"))
|
|
1255
1026
|
|
|
1256
1027
|
|
|
1257
|
-
def get_sim(s1, s2) -> int:
|
|
1258
|
-
sim = int((SequenceMatcher(None, s1, s2).ratio() - 0.6) * 10_000)
|
|
1259
|
-
return sim if sim > 0 else 0
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
1028
|
def detailed_diff(str1, str2):
|
|
1263
1029
|
matcher = SequenceMatcher(None, str1, str2)
|
|
1264
1030
|
result = []
|
|
@@ -1276,14 +1042,6 @@ def detailed_diff(str1, str2):
|
|
|
1276
1042
|
return "".join(result)
|
|
1277
1043
|
|
|
1278
1044
|
|
|
1279
|
-
def clean(s) -> str:
|
|
1280
|
-
clear = r"[^\w\s.,!?;:()\-]"
|
|
1281
|
-
repeat = r"(.)\1{2,}"
|
|
1282
|
-
s = re.sub(clear, "", s).lower()
|
|
1283
|
-
s = re.sub(repeat, r"\1", s)
|
|
1284
|
-
return s.replace("\n\n", "\n").replace(" ", " ").strip(" \n/.,!?-")
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
1045
|
def step_is_need(mad, cad) -> bool:
|
|
1288
1046
|
# todo: пока не решен непонятный кейс, почему то конкурент по всем параметрам слабже, но в списке ранжируется выше.
|
|
1289
1047
|
# текущая версия: recentExecuteRate округляется до целого, но на бэке байбита его дробная часть больше
|
|
@@ -1353,28 +1111,6 @@ async def main():
|
|
|
1353
1111
|
# s, _ = await models.Synonym.update_or_create(typ=SynonymType.name, txt=name)
|
|
1354
1112
|
# await s.curs.add(rub.cur)
|
|
1355
1113
|
|
|
1356
|
-
# пока порешали рейс-кондишн, очищаем сиротские условия при каждом запуске
|
|
1357
|
-
# [await c.delete() for c in await Cond.filter(ads__isnull=True)]
|
|
1358
|
-
cl.all_conds = {
|
|
1359
|
-
c.id: (c.raw_txt, {a.maker.exid for a in c.ads}) for c in await Cond.all().prefetch_related("ads__maker")
|
|
1360
|
-
}
|
|
1361
|
-
for curr, old in await CondSim.filter().values_list("cond_id", "cond_rel_id"):
|
|
1362
|
-
cl.cond_sims[curr] = old
|
|
1363
|
-
cl.rcond_sims[old] |= {curr}
|
|
1364
|
-
|
|
1365
|
-
cl.build_tree()
|
|
1366
|
-
a = set()
|
|
1367
|
-
|
|
1368
|
-
def check_tree(tre):
|
|
1369
|
-
for p, c in tre.items():
|
|
1370
|
-
a.add(p)
|
|
1371
|
-
check_tree(c)
|
|
1372
|
-
|
|
1373
|
-
for pr, ch in cl.tree.items():
|
|
1374
|
-
check_tree(ch)
|
|
1375
|
-
if ct := set(cl.tree.keys()) & a:
|
|
1376
|
-
logging.exception(f"cycle cids: {ct}")
|
|
1377
|
-
|
|
1378
1114
|
pauth = (await models.PmAgent[1]).auth
|
|
1379
1115
|
papi = PayeerAPI(pauth["email"], pauth["api_id"], pauth["api_sec"])
|
|
1380
1116
|
hist: dict = papi.history(count=1000)
|
xync_client/Bybit/etype/ad.py
CHANGED
|
@@ -113,7 +113,7 @@ class Ad(BaseAd):
|
|
|
113
113
|
recommend: bool = None # for initial actualize
|
|
114
114
|
recommendTag: str = None # for initial actualize
|
|
115
115
|
remark: str = Field(serialization_alias="auto_msg")
|
|
116
|
-
side: Literal[0, 1] = None # for initial actualize
|
|
116
|
+
side: Literal[0, 1] = None # for initial actualize # 0 - покупка, 1 - продажа (для мейкера, т.е КАКАЯ объява)
|
|
117
117
|
status: Literal[10, 20, 30] # 10: online; 20: offline; 30: completed
|
|
118
118
|
symbolInfo: SymbolInfo = None # for initial actualize
|
|
119
119
|
tokenId: str = None # for initial actualize
|
xync_client/Bybit/etype/cred.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: xync-client
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.148
|
|
4
4
|
Author-email: Mike Artemiev <mixartemev@gmail.com>
|
|
5
5
|
Project-URL: Homepage, https://gitlab.com/XyncNet/client
|
|
6
6
|
Project-URL: Repository, https://gitlab.com/XyncNet/client
|
|
@@ -8,6 +8,9 @@ Requires-Python: >=3.11
|
|
|
8
8
|
Requires-Dist: asynchuobi
|
|
9
9
|
Requires-Dist: bs4
|
|
10
10
|
Requires-Dist: bybit-p2p
|
|
11
|
+
Requires-Dist: google-api-python-client
|
|
12
|
+
Requires-Dist: google-auth-httplib2
|
|
13
|
+
Requires-Dist: google-auth-oauthlib
|
|
11
14
|
Requires-Dist: requests-toolbelt
|
|
12
15
|
Requires-Dist: msgspec
|
|
13
16
|
Requires-Dist: python-binance
|
|
@@ -2,11 +2,12 @@ xync_client/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
|
2
2
|
xync_client/details.py,sha256=21itVPCgAtaYRR1H9J9oYudj95gafcFjExUN6QL17OI,1330
|
|
3
3
|
xync_client/loader.py,sha256=hxf8ob50DO7r_qjr2qBoO7IyjkXeHHzVQ63YjXerjoU,600
|
|
4
4
|
xync_client/pm_unifier.py,sha256=T2Xh-tvcu114P2YBI6RK_XDiaIhyq6ABMrXDuXPlx7A,6541
|
|
5
|
+
xync_client/Abc/AdLoader.py,sha256=-9UpLgFdIKcpQ2Z6PLCin7c0JmlUQ66WMouNUTWyEHg,13362
|
|
5
6
|
xync_client/Abc/Agent.py,sha256=FmKT4EefJn2oNc9icIgxDqoJoRe33FGgaNwBw5P5B4A,5727
|
|
6
7
|
xync_client/Abc/Asset.py,sha256=hlgyFaU9byr2N2r8Heh-_ICx49SKuKxfRTUA4yQWmEw,454
|
|
7
8
|
xync_client/Abc/Auth.py,sha256=OPQXN7_XYQZP9431ylFksd6JDusbKG8N_1g6CXTZ6yY,1495
|
|
8
9
|
xync_client/Abc/BaseTest.py,sha256=vaAs5Z4HYV7k_C3zQz6JKO75s2hXtVbBI3-0Srkzv5Q,2388
|
|
9
|
-
xync_client/Abc/Ex.py,sha256=
|
|
10
|
+
xync_client/Abc/Ex.py,sha256=6i78hduLVULyaTYlKbu7hsSrCIWVGrFB0HMll0TeSxM,13091
|
|
10
11
|
xync_client/Abc/Exception.py,sha256=Sts7RpP370NBdjaH_cyXDdHtjge8zXNUGWCrKw49Zyk,482
|
|
11
12
|
xync_client/Abc/HasAbotUid.py,sha256=LsTHHjMHBauCwJoqgDa9Lx4R6xsDOHfsN4jM539Bpqg,279
|
|
12
13
|
xync_client/Abc/InAgent.py,sha256=XLf5czbxxEimsIIe653buoP7OsWZD6mc2w37q4TkNd0,703
|
|
@@ -36,14 +37,14 @@ xync_client/BitGet/ex.py,sha256=uEvvXuLaJv8o8BFi0bMA3XyBuTfVDWagAjLOHZl-xlE,3765
|
|
|
36
37
|
xync_client/BitGet/etype/ad.py,sha256=fysSW47wGYjSOPUqY864z857AJz4gjN-nOkI1Jxd27U,1838
|
|
37
38
|
xync_client/BitPapa/ex.py,sha256=U-RRB_RSOtErfRgxOZYWegZ_td_uZO37YKo3Jxchf_w,912
|
|
38
39
|
xync_client/Bybit/InAgent.py,sha256=1Y4ZyxqAY8xbAflku48fJjjlSs4pOz2mFFJSO9iWg8w,26405
|
|
39
|
-
xync_client/Bybit/agent.py,sha256=
|
|
40
|
+
xync_client/Bybit/agent.py,sha256=VftYukdo0nYysxUMFMwVpKH6xmeP4LVfnvoT3SIWCFo,51978
|
|
40
41
|
xync_client/Bybit/ex.py,sha256=3oARvReBoDs90FzQY31-L-q_YU-TIRbvWB7z4lwESsA,4715
|
|
41
42
|
xync_client/Bybit/order.py,sha256=H4UIb8hxFGnw1hZuSbr0yZ4qeaCOIZOMc6jEst0ycBs,1713
|
|
42
43
|
xync_client/Bybit/web_earn.py,sha256=qjqS10xlFc8r40IhDdPZ0LxA2dFEGbvBGXdsrUUJCMo,3019
|
|
43
44
|
xync_client/Bybit/web_p2p.py,sha256=sAXzK03t6jwDnz4rrvP2IzI0KxfKa7C_5GuzH1HwLuA,11768
|
|
44
45
|
xync_client/Bybit/ws.py,sha256=OQjZHo_MiAH1dlOs3c-aUZBKyqToNTmH560udh6RYDE,1431
|
|
45
|
-
xync_client/Bybit/etype/ad.py,sha256=
|
|
46
|
-
xync_client/Bybit/etype/cred.py,sha256=
|
|
46
|
+
xync_client/Bybit/etype/ad.py,sha256=pCD0I9SL4Paaegs85fHGtvZ-7Cm917AQXYel0k1MbY0,8065
|
|
47
|
+
xync_client/Bybit/etype/cred.py,sha256=qDQUsMqLV4XtXKYWZV4f805kLDf7AdcPYscHRA-IMBo,1386
|
|
47
48
|
xync_client/Bybit/etype/order.py,sha256=668UJdCNEnvnalmbtTQ9Tw50arJtgpV_09GHu0iyaRU,8870
|
|
48
49
|
xync_client/Gate/ex.py,sha256=QbhB3u7TWnvVGD-AknB2nay6KZjEXQ-1JT9UacX4sWI,3735
|
|
49
50
|
xync_client/Gate/premarket.py,sha256=IW-CgkmNJePJR2j_NRfULNKTePMX35XlhldqdiO76zY,2138
|
|
@@ -96,7 +97,7 @@ xync_client/TgWallet/order.py,sha256=BOmBx5WWfJv0-_-A8DcR-Xd8utqO_VTmSqSegm0cteQ
|
|
|
96
97
|
xync_client/TgWallet/pyd.py,sha256=Ys3E8b3RLuyQ26frWT0F0BorkNxVpxnd18tY4Gp9dik,5636
|
|
97
98
|
xync_client/TgWallet/pyro.py,sha256=2K7QWdo48k4MbbgQt90gdz_HiPck69Njm4xaMjIVgoo,1440
|
|
98
99
|
xync_client/TgWallet/web.py,sha256=kDcv9SKKQPe91mw1qJBpbuyKYCAmZdfdHJylHumLBVU,1608
|
|
99
|
-
xync_client-0.0.
|
|
100
|
-
xync_client-0.0.
|
|
101
|
-
xync_client-0.0.
|
|
102
|
-
xync_client-0.0.
|
|
100
|
+
xync_client-0.0.148.dist-info/METADATA,sha256=v2HPGq4q5LhjcUCezyrU7_37urzKs6je3wy5XSWmTKM,1149
|
|
101
|
+
xync_client-0.0.148.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
102
|
+
xync_client-0.0.148.dist-info/top_level.txt,sha256=bmYEVIIrD3v7yFwH-X15pEfRvzhuAdfsAZ2igvNI4O8,12
|
|
103
|
+
xync_client-0.0.148.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|