-
Notifications
You must be signed in to change notification settings - Fork 36
Expand file tree
/
Copy pathcallback_queue_handler.py
More file actions
159 lines (140 loc) ยท 8.63 KB
/
Copy pathcallback_queue_handler.py
File metadata and controls
159 lines (140 loc) ยท 8.63 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
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
# ==========================================================
# FILE: callback_queue_handler.py
# ==========================================================
# ๐จ VERIFIED: [์ต์ข
๋ฌด๊ฒฐ์ ํ์ ] 5๋ ํ๋ฒ ๋ฐ 46๋ ์ฃ์ง ์ผ์ด์ค ์๋ฒฝ ๊ฒฐ์ ๊ต์ฐจ ๊ฒ์ฆ ์๋ฃ
# ๐จ MODIFIED: [V-REV LIFO ์ง์ธต ์ ์ด ์ ๋ด ๋๋ฉ์ธ] ํ ์ฅ๋ถ ์กฐ์ ๋ก์ง ๋ถ๋ฆฌ
# ๐จ MODIFIED: [Case 08, 14, 25, 26 ์ ๋ ํ๋ฒ ์ค์] ๋๊ธฐ์ ํ์ผ ์ค์บ(os.path.exists) ๋ฐฐ์ ๋ฐ html.escape ์ด๋ ์ ์ญ ๊ฒฐ์ ์๋ฃ
# ๐จ MODIFIED: [2์ฐจ ์ค์ธ ํจ๋ฌ๋
์ค ๋ถ๊ดด ์์ฒ ์ฐจ๋จ] ์ง์ธต ์ญ์ (DEL_Q) ์์๋ ์งํ ์ค์ธ ์ฌ๋ผ์ด์ค ์ง์์๋ฅผ os.remove()๋ก ์ญ์ ํ์ง ์๊ณ hijacked=True ์ํ์ ๋น ์ง์์๋ก ๋ฎ์ด์จ์ VWAP ์ค์ผ์ค๋ฌ์ False Alarm ์๋ฌ๋ฅผ 100% ์๊ตฌ ์๊ฐ ์๋ฃ.
# ==========================================================
import logging
import asyncio
import html
import os
import glob
import tempfile # ๐จ NEW: ์์์ ์ฐ๊ธฐ์ฉ ๋ชจ๋ ๊ฒฐ์
import json
import datetime
from zoneinfo import ZoneInfo
from telegram import Update
from telegram.ext import ContextTypes
from global_throttle import GlobalThrottle
class CallbackQueueHandler:
def __init__(self, config, queue_ledger, sync_engine, view):
self.cfg = config
self.queue_ledger = queue_ledger
self.sync_engine = sync_engine
self.view = view
async def handle(self, update: Update, context: ContextTypes.DEFAULT_TYPE, controller, action: str, sub: str, data: list):
query = update.callback_query
chat_id = update.effective_chat.id
if action == "QUEUE":
try:
await asyncio.wait_for(query.answer(), timeout=5.0)
except Exception:
pass
if sub == "VIEW":
ticker = data[2] if len(data) > 2 else ""
if getattr(self, 'queue_ledger', None) and ticker:
q_data = await asyncio.wait_for(asyncio.to_thread(self.queue_ledger.get_queue, ticker), timeout=10.0) or []
else:
q_data = []
msg, markup = self.view.get_queue_management_menu(ticker, q_data)
try:
await asyncio.wait_for(query.edit_message_text(msg, reply_markup=markup, parse_mode='HTML'), timeout=10.0)
except Exception:
pass
elif action == "DEL_REQ":
try:
await asyncio.wait_for(query.answer(), timeout=5.0)
except Exception:
pass
ticker = sub
target_date = ":".join(data[2:])
if getattr(self, 'queue_ledger', None):
q_data = await asyncio.wait_for(asyncio.to_thread(self.queue_ledger.get_queue, ticker), timeout=10.0) or []
else:
q_data = []
qty, price = 0, 0.0
for item in q_data:
if isinstance(item, dict) and item.get('date') == target_date:
qty = int(float(str(item.get('qty') or 0).replace(',', '')))
price = float(str(item.get('price') or 0.0).replace(',', ''))
break
msg, markup = self.view.get_queue_action_confirm_menu(ticker, target_date, qty, price)
try:
await asyncio.wait_for(query.edit_message_text(msg, reply_markup=markup, parse_mode='HTML'), timeout=10.0)
except Exception:
pass
elif action in ["DEL_Q", "EDIT_Q"]:
ticker = sub
target_date = ":".join(data[2:])
try:
if action == "DEL_Q":
if getattr(self, 'queue_ledger', None):
await asyncio.wait_for(asyncio.to_thread(self.queue_ledger.delete_lot, ticker, target_date), timeout=10.0)
try:
await asyncio.wait_for(query.answer("โ
์ง์ธต ์ญ์ ์๋ฃ. KIS ์์ฅ๊ณผ ๋๊ธฐํํฉ๋๋ค.", show_alert=False), timeout=5.0)
except Exception:
pass
# ๐จ MODIFIED: [์ด์ค ํ๊ฒฉ ๋ฐฉ์ด] ํ ์ญ์ ์ ์ค๋
์ท ๋ฐ ๋ชจ๋ ์ฌ๋ผ์ด์ค/์ ํํฐ์ฅ ์ง์์ ์์์ ์๊ตฌ ์๊ฐ
def _nuke_snapshot_and_state_del():
for f in glob.glob(f"data/daily_snapshot_*_{ticker}.json"):
with GlobalThrottle.get_file_lock(f):
try: os.remove(f)
except OSError: pass
for f in glob.glob(f"data/vwap_state_*_{ticker}.json"):
with GlobalThrottle.get_file_lock(f):
try: os.remove(f)
except OSError: pass
# ๐จ MODIFIED: 2์ฐจ ์ค์ธ ํจ๋ฌ๋
์ค ์ฐจ๋จ (๋ฌผ๋ฆฌ์ ์ญ์ ๋์ ๋น ์ง์์ ๋ฐ์ )
est_now = datetime.datetime.now(ZoneInfo('America/New_York'))
today_str = est_now.strftime('%Y-%m-%d')
empty_state = {"date": today_str, "hijacked": True, "orders": []}
for f_path in [f"data/vrev_slice_state_{ticker}.json", f"data/vrev_aftermarket_state_{ticker}.json"]:
with GlobalThrottle.get_file_lock(f_path):
dir_name = os.path.dirname(f_path) or '.'
try: os.makedirs(dir_name, exist_ok=True)
except OSError: pass
try:
fd, tmp_path = tempfile.mkstemp(dir=dir_name, text=True)
with os.fdopen(fd, 'w', encoding='utf-8') as f_out:
json.dump(empty_state, f_out, ensure_ascii=False, indent=4)
f_out.flush()
os.fsync(f_out.fileno())
os.replace(tmp_path, f_path)
except Exception: pass
await asyncio.wait_for(asyncio.to_thread(_nuke_snapshot_and_state_del), timeout=10.0)
if ticker not in self.sync_engine.sync_locks:
self.sync_engine.sync_locks[ticker] = asyncio.Lock()
if not self.sync_engine.sync_locks[ticker].locked():
await self.sync_engine.process_auto_sync(ticker, chat_id, context, silent_ledger=True)
final_q = await asyncio.wait_for(asyncio.to_thread(self.queue_ledger.get_queue, ticker), timeout=10.0) if getattr(self, 'queue_ledger', None) else []
final_q = final_q or []
msg, markup = self.view.get_queue_management_menu(ticker, final_q)
try:
await asyncio.wait_for(query.edit_message_text(msg, reply_markup=markup, parse_mode='HTML'), timeout=10.0)
except Exception:
pass
elif action == "EDIT_Q":
try:
await asyncio.wait_for(query.answer("โ๏ธ ์์ ๋ชจ๋ ์ง์
", show_alert=False), timeout=5.0)
except Exception:
pass
short_date = html.escape(str(target_date)[:10]) if len(str(target_date)) >= 10 else html.escape(str(target_date))
safe_ticker = html.escape(str(ticker))
controller.user_states[chat_id] = f"EDITQ_{ticker}_{target_date}"
prompt = f"โ๏ธ <b>[{safe_ticker} ์ง์ธต ์์ ๋ชจ๋]</b>\n"
prompt += f"์ ํํ์ <b>[{short_date}]</b> ์ง์ธต์ ์ฌ์ค์ ํฉ๋๋ค.\n\n"
prompt += "์๋ก์ด <b>[์๋]</b>๊ณผ <b>[ํ๋จ๊ฐ]</b>๋ฅผ ๋์ด์ฐ๊ธฐ๋ก ์
๋ ฅํ์ธ์.\n"
prompt += "(์: <code>229 52.16</code>)\n\n"
prompt += "<i>(์
๋ ฅ์ ์ทจ์ํ๋ ค๋ฉด ์ซ์ ์ด์ธ์ ๋ฌธ์๋ฅผ ๋ณด๋ด์ฃผ์ธ์)</i>"
try:
await asyncio.wait_for(query.edit_message_text(prompt, parse_mode='HTML'), timeout=10.0)
except Exception:
pass
except Exception as e:
safe_err = html.escape(str(e))
try:
await asyncio.wait_for(query.answer(f"โ ์ฒ๋ฆฌ ์ค ์๋ฌ ๋ฐ์: {safe_err}", show_alert=True), timeout=5.0)
except Exception:
pass