Spaces:
Sleeping
Sleeping
Update autotrack.py
Browse files- autotrack.py +78 -5
autotrack.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
# autotrack.py — Unlimited milestones + auto-delete on drawdown + video ≥3×
|
| 2 |
import os
|
| 3 |
import re
|
| 4 |
import time
|
|
@@ -36,7 +36,7 @@ BACKFILL_BUFFER_MINUTES = int(os.environ.get("BACKFILL_BUFFER_MINUTES", "3"))
|
|
| 36 |
# Marker antirecursive: ditambahkan ke semua pengumuman bot (milestone)
|
| 37 |
BOT_MARKER = "【MT-AUTOTRACK】"
|
| 38 |
|
| 39 |
-
# Lokasi DB milik botsignal (untuk ambil msg_id pesan awal)
|
| 40 |
BOTSIGNAL_DB = os.environ.get("BOTSIGNAL_DB", "/tmp/botsignal.db")
|
| 41 |
|
| 42 |
# NEW: Auto-delete bila drawdown >= threshold (rasio relatif terhadap entry)
|
|
@@ -145,6 +145,59 @@ def lookup_origin_msg_id(keyword: str) -> Optional[int]:
|
|
| 145 |
print(f"[TRACK][DB] lookup error: {e}")
|
| 146 |
return None
|
| 147 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 148 |
# =========================
|
| 149 |
# Tracker core (UNLIMITED)
|
| 150 |
# =========================
|
|
@@ -154,7 +207,7 @@ class TrackItem:
|
|
| 154 |
basis: str = "mcap" # "mcap" or "price"
|
| 155 |
entry_price: Optional[float] = None
|
| 156 |
entry_mcap: Optional[float] = None
|
| 157 |
-
symbol_hint: Optional[
|
| 158 |
source_link: Optional[str] = None
|
| 159 |
poll_secs: int = 20
|
| 160 |
next_milestone: float = field(default_factory=lambda: REPLY_FROM_MULTIPLE)
|
|
@@ -266,6 +319,13 @@ class PriceTracker:
|
|
| 266 |
await self.client.delete_messages(self.announce_chat, msg_id)
|
| 267 |
print(f"[TRACK] Deleted origin message for {ca}")
|
| 268 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 269 |
if DELETE_THREAD_REPLIES:
|
| 270 |
try:
|
| 271 |
async for m in self.client.iter_messages(self.announce_chat, limit=100, reply_to=msg_id):
|
|
@@ -327,9 +387,11 @@ class PriceTracker:
|
|
| 327 |
text = self._milestone_text(item, item.next_milestone, ratio, cur_price, cur_mcap)
|
| 328 |
key = ca_key_for_db(item.ca)
|
| 329 |
reply_to_id = lookup_origin_msg_id(key) if key else None
|
|
|
|
|
|
|
| 330 |
if item.next_milestone >= VIDEO_MIN_MULTIPLE and os.path.isfile(VIDEO_PATH):
|
| 331 |
try:
|
| 332 |
-
await self.client.send_file(
|
| 333 |
self.announce_chat,
|
| 334 |
VIDEO_PATH,
|
| 335 |
caption=f"{text}\n\n{BOT_MARKER}",
|
|
@@ -339,12 +401,22 @@ class PriceTracker:
|
|
| 339 |
except Exception as e:
|
| 340 |
print(f"[TRACK] gagal kirim video: {e}")
|
| 341 |
else:
|
| 342 |
-
await self.client.send_message(
|
| 343 |
self.announce_chat,
|
| 344 |
f"{text}\n\n{BOT_MARKER}",
|
| 345 |
reply_to=reply_to_id if reply_to_id else None,
|
| 346 |
link_preview=False
|
| 347 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 348 |
item.next_milestone = self._next_target_after(item.next_milestone)
|
| 349 |
|
| 350 |
await asyncio.sleep(item.poll_secs)
|
|
@@ -435,6 +507,7 @@ def setup_autotrack(shared_client: TelegramClient, announce_chat: str | None = N
|
|
| 435 |
global client, tracker, startup_time_utc
|
| 436 |
client = shared_client
|
| 437 |
ensure_db()
|
|
|
|
| 438 |
ac = announce_chat or TARGET_CHAT
|
| 439 |
tracker = PriceTracker(client, announce_chat=ac)
|
| 440 |
startup_time_utc = datetime.now(timezone.utc)
|
|
|
|
| 1 |
+
# autotrack.py — Unlimited milestones + auto-delete on drawdown + video ≥3× + daily summary hooks
|
| 2 |
import os
|
| 3 |
import re
|
| 4 |
import time
|
|
|
|
| 36 |
# Marker antirecursive: ditambahkan ke semua pengumuman bot (milestone)
|
| 37 |
BOT_MARKER = "【MT-AUTOTRACK】"
|
| 38 |
|
| 39 |
+
# Lokasi DB milik botsignal (untuk ambil msg_id pesan awal + summary hooks)
|
| 40 |
BOTSIGNAL_DB = os.environ.get("BOTSIGNAL_DB", "/tmp/botsignal.db")
|
| 41 |
|
| 42 |
# NEW: Auto-delete bila drawdown >= threshold (rasio relatif terhadap entry)
|
|
|
|
| 145 |
print(f"[TRACK][DB] lookup error: {e}")
|
| 146 |
return None
|
| 147 |
|
| 148 |
+
# =========================
|
| 149 |
+
# SUMMARY HOOKS (harian)
|
| 150 |
+
# =========================
|
| 151 |
+
def _summary_db():
|
| 152 |
+
conn = sqlite3.connect(BOTSIGNAL_DB)
|
| 153 |
+
conn.execute("PRAGMA journal_mode=WAL;")
|
| 154 |
+
return conn
|
| 155 |
+
|
| 156 |
+
def summary_init_tables():
|
| 157 |
+
try:
|
| 158 |
+
conn = _summary_db()
|
| 159 |
+
conn.executescript("""
|
| 160 |
+
CREATE TABLE IF NOT EXISTS summary_milestones (
|
| 161 |
+
keyword TEXT NOT NULL,
|
| 162 |
+
reply_msg_id INTEGER,
|
| 163 |
+
multiple REAL,
|
| 164 |
+
replied_at INTEGER NOT NULL
|
| 165 |
+
);
|
| 166 |
+
CREATE TABLE IF NOT EXISTS summary_outcomes (
|
| 167 |
+
keyword TEXT PRIMARY KEY,
|
| 168 |
+
is_deleted INTEGER DEFAULT 0
|
| 169 |
+
);
|
| 170 |
+
""")
|
| 171 |
+
conn.commit()
|
| 172 |
+
conn.close()
|
| 173 |
+
except Exception as e:
|
| 174 |
+
print(f"[SUMMARY] init tables failed: {e}")
|
| 175 |
+
|
| 176 |
+
def _summary_log_milestone(keyword: str, reply_msg_id: Optional[int], multiple: float, ts_utc: int):
|
| 177 |
+
try:
|
| 178 |
+
conn = _summary_db()
|
| 179 |
+
conn.execute(
|
| 180 |
+
"INSERT INTO summary_milestones(keyword, reply_msg_id, multiple, replied_at) VALUES(?,?,?,?)",
|
| 181 |
+
(keyword, reply_msg_id, float(multiple), int(ts_utc))
|
| 182 |
+
)
|
| 183 |
+
conn.commit()
|
| 184 |
+
conn.close()
|
| 185 |
+
except Exception as e:
|
| 186 |
+
print(f"[SUMMARY] log milestone failed: {e}")
|
| 187 |
+
|
| 188 |
+
def _summary_mark_deleted(keyword: str):
|
| 189 |
+
try:
|
| 190 |
+
conn = _summary_db()
|
| 191 |
+
conn.execute(
|
| 192 |
+
"INSERT INTO summary_outcomes(keyword, is_deleted) VALUES(?,1) "
|
| 193 |
+
"ON CONFLICT(keyword) DO UPDATE SET is_deleted=1",
|
| 194 |
+
(keyword,)
|
| 195 |
+
)
|
| 196 |
+
conn.commit()
|
| 197 |
+
conn.close()
|
| 198 |
+
except Exception as e:
|
| 199 |
+
print(f"[SUMMARY] mark deleted failed: {e}")
|
| 200 |
+
|
| 201 |
# =========================
|
| 202 |
# Tracker core (UNLIMITED)
|
| 203 |
# =========================
|
|
|
|
| 207 |
basis: str = "mcap" # "mcap" or "price"
|
| 208 |
entry_price: Optional[float] = None
|
| 209 |
entry_mcap: Optional[float] = None
|
| 210 |
+
symbol_hint: Optional[float] = None
|
| 211 |
source_link: Optional[str] = None
|
| 212 |
poll_secs: int = 20
|
| 213 |
next_milestone: float = field(default_factory=lambda: REPLY_FROM_MULTIPLE)
|
|
|
|
| 319 |
await self.client.delete_messages(self.announce_chat, msg_id)
|
| 320 |
print(f"[TRACK] Deleted origin message for {ca}")
|
| 321 |
|
| 322 |
+
# === Summary: mark lose
|
| 323 |
+
try:
|
| 324 |
+
if key:
|
| 325 |
+
_summary_mark_deleted(key)
|
| 326 |
+
except Exception as e:
|
| 327 |
+
print(f"[SUMMARY] mark deleted hook err: {e}")
|
| 328 |
+
|
| 329 |
if DELETE_THREAD_REPLIES:
|
| 330 |
try:
|
| 331 |
async for m in self.client.iter_messages(self.announce_chat, limit=100, reply_to=msg_id):
|
|
|
|
| 387 |
text = self._milestone_text(item, item.next_milestone, ratio, cur_price, cur_mcap)
|
| 388 |
key = ca_key_for_db(item.ca)
|
| 389 |
reply_to_id = lookup_origin_msg_id(key) if key else None
|
| 390 |
+
|
| 391 |
+
sent_msg = None
|
| 392 |
if item.next_milestone >= VIDEO_MIN_MULTIPLE and os.path.isfile(VIDEO_PATH):
|
| 393 |
try:
|
| 394 |
+
sent_msg = await self.client.send_file(
|
| 395 |
self.announce_chat,
|
| 396 |
VIDEO_PATH,
|
| 397 |
caption=f"{text}\n\n{BOT_MARKER}",
|
|
|
|
| 401 |
except Exception as e:
|
| 402 |
print(f"[TRACK] gagal kirim video: {e}")
|
| 403 |
else:
|
| 404 |
+
sent_msg = await self.client.send_message(
|
| 405 |
self.announce_chat,
|
| 406 |
f"{text}\n\n{BOT_MARKER}",
|
| 407 |
reply_to=reply_to_id if reply_to_id else None,
|
| 408 |
link_preview=False
|
| 409 |
)
|
| 410 |
+
|
| 411 |
+
# === Summary: log milestone (win signal)
|
| 412 |
+
try:
|
| 413 |
+
if key:
|
| 414 |
+
ts_utc = int(time.time())
|
| 415 |
+
rep_id = getattr(sent_msg, "id", None) if sent_msg else None
|
| 416 |
+
_summary_log_milestone(key, rep_id, item.next_milestone, ts_utc)
|
| 417 |
+
except Exception as e:
|
| 418 |
+
print(f"[SUMMARY] milestone hook err: {e}")
|
| 419 |
+
|
| 420 |
item.next_milestone = self._next_target_after(item.next_milestone)
|
| 421 |
|
| 422 |
await asyncio.sleep(item.poll_secs)
|
|
|
|
| 507 |
global client, tracker, startup_time_utc
|
| 508 |
client = shared_client
|
| 509 |
ensure_db()
|
| 510 |
+
summary_init_tables() # <<< summary tables
|
| 511 |
ac = announce_chat or TARGET_CHAT
|
| 512 |
tracker = PriceTracker(client, announce_chat=ac)
|
| 513 |
startup_time_utc = datetime.now(timezone.utc)
|