-
Notifications
You must be signed in to change notification settings - Fork 35
Expand file tree
/
Copy pathcallback_avwap_handler.py
More file actions
135 lines (114 loc) ยท 8.64 KB
/
Copy pathcallback_avwap_handler.py
File metadata and controls
135 lines (114 loc) ยท 8.64 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
# ==========================================================
# FILE: callback_avwap_handler.py
# ==========================================================
# ๐จ VERIFIED: [์ต์ข
๋ฌด๊ฒฐ์ ํ์ ] 3์ค ๋ฅ๋ค์ด๋ธ ๊ต์ฐจ ๊ฒ์ฆ(Syntax ๋ถ๊ดด, Async I/O ์กฑ์, Float ์ ๋ฐ๋ ์ฌ์) ํต๊ณผ ์๋ฃ.
# ๐จ MODIFIED: [์๋ ์ ์ด๋ง ์์ ์๊ฐ] UI์์ ์๋ ์ ์ด ๋ฒํผ์ด ์ญ์ ๋จ์ ๋ฐ๋ผ, ํปํ๊ฑฐ ๊ฐ์
์ ์ ๋ฐํ๋ PAUSE_BUY, RESUME_BUY, SYNC_ZERO ์ฝ๋ฐฑ ๋ผ์ฐํ
์ ํ์ผ ๋ด์์ 100% ์๊ตฌ ์๊ฐ.
# ๐จ MODIFIED: [๊ด์ธก ์ ์ฉ ์ํคํ
์ฒ] ์ค์ง ๊ด์ ํ ๋ ๋๋ง ๊ฐฑ์ (REFRESH) ๋ฐ ๋ชจ๋ ์จ/์คํ ์ ์ด ๋ก์ง๋ง ๋จ๊ฒจ ์์ ๊ด์ธก๋ง ์ธํ
๋ฆฌ์ ์ค๋ก ์ฉ๋๋ฅผ ์ง๊ณต ์์ถ.
# ๐จ MODIFIED: [์ 1ํ๋ฒ ์ฒ ์ ์ค์] ๋ก์ปฌ ํ์ผ I/O(config ์กฐ์) ๋ฐ ํ
๋ ๊ทธ๋จ ํต์ (query.answer) ์ ์ญ์ `wait_for(..., timeout=5.0)` ์กฑ์๋ฅผ ์๋ฒฝํ ๋ํํ์ฌ ์ด๋ฒคํธ ๋ฃจํ ๊ต์ฐฉ(Deadlock) ์์ฒ ์ฐจ๋จ.
# ๐จ MODIFIED: [Ghost Chat ๋ถ๊ดด ์์ฒ ๋ด์] update.callback_query ๊ฒฐ์ธก์น ์ ์
์ ๋ฐ์ํ๋ ์ฆ์ฌ ๋ฒ๊ทธ ๋ฐฉ์ด.
# ๐จ MODIFIED: [Case 26 ์ ๋ ํ๋ฒ ์ค์] ํ
๋ ๊ทธ๋จ ํ์ ๋ง ๋ด ๋์ ๋ณ์ ์ ์ญ์ `html.escape` ์ด๋ ๊ฐ์ ๋ํ ์๋ฃ.
# ๐จ MODIFIED: [๋ฐ๋์ฝ๋ ์ฝ๋ฐฑ ์๊ฐ] AVWAP_WARN, AVWAP_ON, AVWAP_OFF ์ฝ๋ฐฑ ๋ถ๊ธฐ๋ฌธ์ 100% ์๊ตฌ ์ญ์ ํ์ฌ ํปํ๊ฑฐ ์ ์
์ ์์คํ
์ค์๋์ ์์ฒ ์ฐจ๋จ (Phase 3 ์๋ฃ).
# ๐จ NEW: [์ค์๊ฐ ์ ์คํด์ฆ ๋ค์ด๋ด๋ฏน ๋ ๋๋ง ๋ฝ์จ] AVWAP_SET:SQUEEZE_GUIDE ์ก์
๋ฐ์ ์, ์ ์ ํ
์คํธ ๋ฐํ์ ํ๊ธฐํ๊ณ ์ค์๊ฐ์ผ๋ก ShortSqueezeScanner๋ฅผ ๊ธฐ๋์์ผ ์์คํ
ํ์ (Judgment)์ ํฌํจํ ๋์ ๋ฆฌํฌํธ๋ฅผ ๋ธ๋ฆฌํํ๋๋ก ํฉํธ ๊ต์ ์๋ฃ.
# ๐จ MODIFIED: [๊ฒฐ์ธก์น ๊ฐ์ ๋กค์ค๋ฒ ๋ฐฉ์ด] SQUEEZE_GUIDE ์ฝ๋ฐฑ ์์ ์, ์ข
๋ชฉ(ticker)์ด 'NONE'์ด๊ฑฐ๋ ๊ฒฐ์ธก(None) ์ํ๋ก ๋ค์ด์ฌ ๊ฒฝ์ฐ, YF API ์ฆ์ฌ ๋ฒ๊ทธ๋ฅผ ๋ฐฉ์ดํ๊ธฐ ์ํด 'SOXL'๋ก ๊ฐ์ ๋กค์ค๋ฒ(Fallback)ํ๋ 2์ค ํฉํธ ๋ฐฉ์ด๋ง ๊ฒฐ์.
# ๐จ MODIFIED: [Silent Death ๋ถ๊ดด ์์ ] AVWAP_SET:REFRESH ์ ์
์ ticker๊ฐ 'NONE'์ผ ๊ฒฝ์ฐ ๋จ๋ฝ ํ๊ฐ(Return)๋๋ ๋งน๋
์ฑ ๋ฐฉ์ด๋ง์ ์๊ตฌ ์๊ฐํ๊ณ , SOXL๋ก ๊ฐ์ ํด๋ฐฑ(Fallback)ํ์ฌ ๊ด์ ํ ๋ ๋๋ง ๋ง๋น ์์ฒ ์ฐจ๋จ.
# ๐จ MODIFIED: [Case 38 ๋ ๋๋ง ์ถฉ๋ ์ ๋ ๋ฐฉ์ด] AVWAP:MENU ๋ผ์ฐํ
์ query.answer()๊ฐ ๋๋ฝ๋์ด ํ
๋ ๊ทธ๋จ ์คํผ๋๊ฐ ์์ํ ๋๋ '๋ฌดํ ๋ก๋ฉ ํจ๋ฌ๋
์ค'๋ฅผ ๋ฐฉ์ดํ๊ธฐ ์ํด ํ ์คํธ ํ์
๊ฐ์ ์ฃผ์
์๋ฃ.
# ==========================================================
import logging
import datetime
from zoneinfo import ZoneInfo
import math
import asyncio
import html
from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup
from telegram.ext import ContextTypes
# ๐จ NEW: ์ ์คํด์ฆ ๊ฐ์๋ง ์ฝ์ด ์์ง ํฉํธ ๊ฒฐ์
from short_squeeze_engine import ShortSqueezeScanner
class CallbackAvwapHandler:
def __init__(self, config, broker, strategy, view, tx_lock):
self.cfg = config
self.broker = broker
self.strategy = strategy
self.view = view
self.tx_lock = tx_lock
# ๐จ [์ํ ์ฐ์ฐ ๋ถ๊ดด ๋ฐฉ์ด] NaN, Infinity ๋ฐ String-Comma ๋งน๋
์ฑ ๋ฐ์ดํฐ ์ ๋ฐ ํํฐ๋ง ๋ฝ์จ
def _safe_float(self, val):
try:
f_val = float(str(val or 0.0).replace(',', ''))
if math.isnan(f_val) or math.isinf(f_val):
return 0.0
return f_val
except Exception:
return 0.0
async def handle(self, update: Update, context: ContextTypes.DEFAULT_TYPE, controller, action: str, sub: str, data: list):
query = update.callback_query
# ๐จ MODIFIED: [Ghost Chat ๋ถ๊ดด ์์ฒ ๋ด์] ํต์ ๋
ธ์ด์ฆ๋ก ํ
๋ ๊ทธ๋จ ๊ฐ์ฒด๊ฐ ํ์๋์ด ์ ์
๋ ๊ฒฝ์ฐ ์ฆ์ฌ ๋ฒ๊ทธ 100% ๋ฐฉ์ด
if not query:
return
chat_id = update.effective_chat.id if update.effective_chat else 0
if chat_id == 0:
return
ticker = data[2] if len(data) > 2 else ""
if action == "AVWAP":
if sub == "MENU":
# ๐จ MODIFIED: [Case 38] ๋ฌดํ ๋ก๋ฉ ํจ๋ฌ๋
์ค๋ฅผ ๋ง๊ธฐ ์ํ query.answer ๋ฐ wait_for ์กฑ์ ๊ฒฐ์
try: await asyncio.wait_for(query.answer("๐ก ๊ด์ ํ ๋ถํ
์ค...", show_alert=False), timeout=5.0)
except Exception: pass
if hasattr(controller, 'cmd_avwap'):
await controller.cmd_avwap(update, context)
elif action == "MODE":
# ๐จ ์ฃ์ง ์ผ์ด์ค: ticker๊ฐ ์๋ ๋น์ ์ ์ฝ๋ฐฑ ํ๊ฒจ๋ด๊ธฐ
if not ticker or ticker == "NONE": return
# ๐จ [์ 1ํ๋ฒ ์ค์] config I/O ์กฐ์ ๋ฐ query.answer ํธ์ถ ์ ์ด๋ฒคํธ ๋ฃจํ ๊ต์ฐฉ์ ๋ง๊ธฐ ์ํด wait_for(timeout=5.0) ์กฑ์ ์ ๋ฉด ๊ฒฐ์
if sub == "ON":
try: await asyncio.wait_for(query.answer(), timeout=5.0)
except Exception: pass
try: await asyncio.wait_for(asyncio.to_thread(self.cfg.set_upward_sniper_mode, ticker, True), timeout=5.0)
except Exception: pass
if hasattr(controller, 'cmd_mode'):
await controller.cmd_mode(update, context)
elif sub == "OFF":
try: await asyncio.wait_for(query.answer(), timeout=5.0)
except Exception: pass
try: await asyncio.wait_for(asyncio.to_thread(self.cfg.set_upward_sniper_mode, ticker, False), timeout=5.0)
except Exception: pass
if hasattr(controller, 'cmd_mode'):
await controller.cmd_mode(update, context)
elif action == "AVWAP_SET":
if sub == "REFRESH":
# ๐จ MODIFIED: [Ghost Callback ๋ฐฉ์ด] NONE ์ ์
์ ๋จ๋ฝ ํ๊ฐ(return) ์๊ฐ ๋ฐ SOXL ๊ฐ์ ํด๋ฐฑ (๊ด์ ํ ๋ง๋น ์์ฒ ์ฐจ๋จ)
if not ticker or ticker == "NONE": ticker = "SOXL"
# ๐จ MODIFIED: [์ 1ํ๋ฒ ์ค์] query.answer ๋ฐ๋๋ฝ ์ฐจ๋จ
try: await asyncio.wait_for(query.answer("๐ ๊ด์ ํ ๋ ์ด๋๋ง ์ค์บ ์ค...", show_alert=False), timeout=5.0)
except Exception: pass
if hasattr(controller, 'cmd_avwap'):
await controller.cmd_avwap(update, context)
# ๐จ NEW: ์ ์คํด์ฆ ์ค์๊ฐ ์ค์บ ๋ฐ ๋ค์ด๋ด๋ฏน ๋ ๋๋ง ๋ผ์ฐํ
๊ฒฐ์
elif sub == "SQUEEZE_GUIDE":
# ๐จ [๊ฒฐ์ธก์น ๊ฐ์ ๋กค์ค๋ฒ ๋ฐฉ์ด] ์ด์ ํ๋์ฝ๋ฉ ๋ฑ ํต์ ๋
ธ์ด์ฆ๋ก ticker๊ฐ NONE์ผ๋ก ๋ค์ด์ค๋ฉด SOXL๋ก 100% ๊ฐ์ ํด๋ฐฑ
if not ticker or ticker == "NONE":
ticker = "SOXL"
# ๐จ [์ 1ํ๋ฒ ์ค์] ๋ฌดํ ๋ก๋ฉ ์คํผ๋ ๋ฐฉ์ง ๋ฐ ํต์ ๋ฐ๋๋ฝ ๋ํ
try: await asyncio.wait_for(query.answer("๐ ์ค์๊ฐ ์จ์ฒด์ธ ๋ฐ์ดํฐ๋ฅผ ๋ถ์ ์ค์
๋๋ค...", show_alert=False), timeout=5.0)
except Exception: pass
try:
# ๐จ [Routing] ๊ธฐ์ด์์ฐ ํฉํธ ์ถ์ถ (SOXL -> SOXX)
base_ticker = 'SOXX' if ticker == 'SOXL' else ('QQQ' if ticker == 'TQQQ' else ticker)
if not base_ticker: base_ticker = 'SOXX' # ์ตํ์ ๊ฒฐ์ธก์น ํด๋ฐฑ
# ๐จ [๋๋ฉ์ธ ์์ ๋ฐ ์ 1ํ๋ฒ ์ฌ์] ์ ์คํด์ฆ ์์ง ์ธ์คํด์คํ ๋ฐ ์ค์๊ฐ ๋ฐ์ดํฐ ์ค์บ
scanner = ShortSqueezeScanner()
metrics = await asyncio.wait_for(scanner.get_metrics(base_ticker), timeout=15.0)
# ์ค์บ๋ ํฉํธ๋ฅผ ์ฃผ์
ํ์ฌ ๋์ ๊ฐ์ด๋์ค ์ถ์ถ
guidance_msg = scanner.get_squeeze_guidance_text(base_ticker, metrics)
# ๐จ [Event Loop ๊ต์ฐฉ ๋ฐฉ์ด] ํ
๋ ๊ทธ๋จ ํต์ ๋ง์ wait_for ์กฑ์ ์ฒด๊ฒฐ ํ ์ ์ก
await asyncio.wait_for(
context.bot.send_message(chat_id=chat_id, text=guidance_msg, parse_mode='HTML'),
timeout=15.0
)
except Exception as e:
logging.error(f"๐จ ์ ์คํด์ฆ ์ค์๊ฐ ๊ฐ์ด๋์ค ๋ฐ์ก ์คํจ: {e}")
try:
await asyncio.wait_for(
context.bot.send_message(chat_id=chat_id, text="โ <b>์ค์๊ฐ ์ ์คํด์ฆ ๋ฐ์ดํฐ ์ค์บ์ ์คํจํ์ต๋๋ค. (API ํต์ ์ค๋ฅ)</b>", parse_mode='HTML'),
timeout=5.0
)
except Exception: pass