-
Notifications
You must be signed in to change notification settings - Fork 31
Expand file tree
/
Copy pathorder_executor.py
More file actions
131 lines (109 loc) ยท 7.94 KB
/
Copy pathorder_executor.py
File metadata and controls
131 lines (109 loc) ยท 7.94 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
# ==========================================================
# FILE: order_executor.py
# ==========================================================
# ๐จ NEW: [์ฃผ๋ฌธ ์งํ ๋๋ฉ์ธ ๋ถ๋ฆฌ] ์ค์ผ์ค๋ฌ ๋ด๋ถ์ ํผ์ฌํ๋ KIS ํต์ ๋ฐ ์๋ฌ ํจ๋ค๋ง ๋ก์ง์ ์ ๋ดํ๋ ์ ๊ท ๋ชจ๋ ๊ตฌ์ถ
# ๐จ VERIFIED: [์์ท ๋ฅ๋ค์ด๋ธ ๊ต์ฐจ ๊ฒ์ฆ ์๋ฃ] 5๋ ํ๋ฒ ๋ฐ ์ ๋ ๊ท์น ์ ์ฉ ๋ฌด๊ฒฐ์ ํจ์น ์๋ฃ (Zero-Defect ํ์ )
# ๐จ MODIFIED: [Cascade Failure ์ ๋ ๋ฐฉ์ด] ํน์ ๋ซ ์ฅ์ ์คํจ ์ raise๋ก ์ธํด ์ ์ฒด ๋ฃจํ๊ฐ ์ฆ์ฌํ๋ ์น๋ช
์ ๋งน์ ์ ํ๊ธฐํ๊ณ , ๋
๋ฆฝ์ ์คํจ ์ฒ๋ฆฌ(rt_cd: 999)๋ก ๋ฃจํ ์๋ช
์ฃผ๊ธฐ 100% ์ฌ์.
# ๐จ MODIFIED: [Case 32 ์ ๋ ๋ฐฉ์ด๋ง ๊ฒฐ์] KIS ํต์ ๋ฐ ๋ก์ปฌ ์์์ ์ฐ๊ธฐ ์คํจ ์ 3๋จ ์ง์ ๋ฐฑ์คํ(Exponential Backoff) 100% ์๋์์น ๋ํํ์ฌ ๋ฌด์ค๋จ ํ๋ณต ๋ฃจํด ์ฌ์.
# ๐จ MODIFIED: [TimeoutError ๋ก๊ทธ ์ฆ๋ฐ ์์ ] asyncio.TimeoutError ๋ฐ์ ์ str(e)๊ฐ ๋น ๋ฌธ์์ด์ ๋ฐํํ์ฌ ์๋ฌ ๋ด์ญ์ด ์ฆ๋ฐํ๋ ํ์ด์ฌ ๊ณ ์ ๋งน์ ์ ๋ช
์์ ํ์ด๋ก๋ ์ฃผ์
์ผ๋ก ์์ฒ ์ฐจ๋จ.
# ๐จ MODIFIED: [Case 05 ๋ฐํ์ ๋ถ๊ดด ๋ฐฉ์ด] Float ์บ์คํ
ํ ์๋(qty)์ด 0์ฃผ ์ดํ๋ก ํ๊ฐ๋ ๊ฒฝ์ฐ API ํต์ ์ ์ ๋ฉด ๋ฐ์ดํจ์คํ์ฌ KIS ์๋ฒ Reject ์์ฒ ์ฐจ๋จ.
# ๐จ VERIFIED: [1์ฐจ ํ๊ฒฉ ์๋ฃ] KIS ํต์ ๋ฐ ๋ก์ปฌ I/O(to_thread) ์ ์ญ์ asyncio.wait_for ์๋๋ฐ์ค 100% ๋ํ ์๋ฃ
# ๐จ VERIFIED: [2์ฐจ ํ๊ฒฉ ์๋ฃ] successful_orders_cache ์ฐธ์กฐ ์ฃผ์
(DI)์ผ๋ก ์ค์ผ์ค๋ฌ ์ฌ์๋ ์ ์ค๋ณต ๋งค๋งค(Double Tap) ์๋ฒฝ ์ฐจ๋จ
# ๐จ VERIFIED: [3์ฐจ ํ๊ฒฉ ์๋ฃ] NaN/Infinity ๋งน๋
์ฑ ๋ฐ์ดํฐ ๋ฐ Float ์๋ ํต์ ์๋ฌ๋ฅผ ์ฐจ๋จํ๋ _safe_float ๋ฐฉ์ด๋ง ๊ฒฐ์
# ๐จ MODIFIED: [์ 1ํ๋ฒ] TPS 20 ์บกํ ๋ฐฉ์ด๋ง(await asyncio.sleep(0.06)) ๋ฃจํ ๋ด๋ถ ์ ์ง ๋ฐฐ์น ๋ฐ ๋ฐฑ์คํ ์ตํฉ ์๋ฃ
# ==========================================================
import asyncio
import logging
import html
import math
from state_io_manager import save_slice_state_sync, save_aftermarket_state_sync
def _safe_float(val):
""" ๐จ [์ํ ์ฐ์ฐ ๋ถ๊ดด ๋ฐฉ์ด] NaN, Infinity ๋ฐ String-Comma ๋งน๋
์ฑ ๋ฐ์ดํฐ ์ ๋ฐ ํํฐ๋ง """
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 execute_order_list(broker, ticker, orders_list, successful_orders_cache, is_market_active_now, today_str, is_capital_locked=False, order_category="1์ฐจ ํ์"):
"""
๐จ [ํต์ฌ ์๋ฌด] ์ค์ผ์ค๋ฌ๋ก๋ถํฐ ์์๋ฐ์ ๋ซ(Target Orders)์ TPS ๊ท์น, 3๋จ ์ง์ ๋ฐฑ์คํ, ๋ฉฑ๋ฑ์ฑ์ ์ฌ์ํ๋ฉฐ KIS์ ์ฅ์ ํฉ๋๋ค.
"""
msgs = ""
all_success = True
loop_fail_reason = ""
# ๐จ [Type Safety ๋ฝ์จ] ๋ฆฌ์คํธ๊ฐ ์๋ ์ค์ผ ๊ฐ์ฒด ์ ์
์ ์ํ ๋ถ๊ดด ๋ฐฉ์ง
if not isinstance(orders_list, list):
return False, "๐จ <b>์ค๋
์ท ์ค์ผ: ์ฃผ๋ฌธ ๋ฆฌ์คํธ ๊ฒฐ์ธก</b>\n", "์ฃผ๋ฌธ ๋ฆฌ์คํธ ํ์
์๋ฌ"
for o in orders_list:
try:
if not isinstance(o, dict): continue
# ๐จ [Type Boundary] ํ์ด๋ก๋ ๊ฐ์ ์บ์คํ
๋ฝ์จ
o_type = str(o.get('type', 'LOC'))
o_side = str(o.get('side', 'BUY'))
o_qty = int(_safe_float(o.get('qty')))
o_price = _safe_float(o.get('price'))
# ๐จ [Case 05] 0์ฃผ ์ค์ธ ๊ฒฉ๋ฐ ๋ฐ KIS ์๋ฒ Reject ์์ฒ ์ฐจ๋จ
if o_qty <= 0:
msgs += f"โ ๏ธ {order_category}: ์๋ 0์ฃผ ์ฐ์ถ๋ก ํ๊ฒฉ ๋ฐ์ดํจ์ค (์์ ๊ฒฉ๋ฆฌ)\n"
continue
# ๐จ [Case 26] ํ
๋ ๊ทธ๋จ ํ์ ๋ถ๊ดด(Silent Death) ๋ฐฉ์ด์ฉ html.escape ๋ํ
o_desc = html.escape(str(o.get('desc', '์ฃผ๋ฌธ')))
# ๐จ [Case 19 ์ค๋ณต ๋งค๋งค ๋ฐฉ์ด] ๊ธฐ์ฅ์ ๋ ๋ซ์ ํต์ ์ ์ ๋ฉด ๋ฐ์ดํจ์ค(Bypass)
order_key = f"{ticker}_{o_desc}"
if order_key in successful_orders_cache:
msgs += f"โ {order_category}: {o_desc} {o_qty}์ฃผ (${o_price}): โ
(๊ธฐ์ฅ์ ๋ณด์กด)\n"
continue
res = {}
# ๐จ [Case 32 & 33 ์ ๋ ๊ท์น] 3๋จ ์ง์ ๋ฐฑ์คํ ๋ฐ TPS ์บกํ ๊ฐ์ ๋ํ
for attempt in range(3):
try:
# ๐จ [์ 1ํ๋ฒ] TPS 20 ์ ํ ์ฌ์์ฉ ์ง์ฐ ์ฃผ์
await asyncio.sleep(0.06)
# ๐จ [Case 39, 40 ์๋ณธ ์ ๊น ๋ฐฉ์ด] ์ ํํฐ์ฅ ์ง์ฐ ์ด๊ด ๋ถ๊ธฐ ํฉํธ ๋ฝ์จ
if is_capital_locked:
slice_info = {"ticker": ticker, "side": o_side, "total_qty": o_qty, "filled_qty": 0, "target_price": o_price, "desc": o_desc, "status": "PENDING"}
await asyncio.wait_for(asyncio.to_thread(save_aftermarket_state_sync, ticker, today_str, slice_info), timeout=10.0)
res = {'rt_cd': '0', 'msg1': '์ ํํฐ์ฅ ์ง์ฐ ์ด๊ด ์๋ฃ', 'odno': f'AFTERMARKET_{id(o)}'}
# ๐จ [V-REV 1๋ถ ์ฌ๋ผ์ด์ฑ ์ธ๊ณ] KIS ์๊ณ ๋ฆฌ์ฆ ์๊ฐ ๋ฐ ์์ฒด ์์ง ๋ก์ปฌ ํ์ผ ์ธ๊ณ
elif o_type == 'VWAP':
slice_info = {"ticker": ticker, "side": o_side, "total_qty": o_qty, "filled_qty": 0, "target_price": o_price, "desc": o_desc, "status": "PENDING"}
await asyncio.wait_for(asyncio.to_thread(save_slice_state_sync, ticker, today_str, slice_info), timeout=10.0)
res = {'rt_cd': '0', 'msg1': '๋ก์ปฌ ์์ฒด VWAP ์์ง ์์ ์๋ฃ', 'odno': f'LOCAL_VWAP_{id(o)}'}
# ๐จ [์ ๊ท์ฅ ์ค์ ํ๊ฒฉ]
elif is_market_active_now:
res = await asyncio.wait_for(asyncio.to_thread(broker.send_order, ticker, o_side, o_qty, o_price, o_type), timeout=15.0)
# ๐จ [ํ๋ฆฌ์ฅ/์ฅ๋ง๊ฐ ์์ฝ ๋ซ ์ฅ์ ]
else:
res = await asyncio.wait_for(asyncio.to_thread(broker.send_reservation_order, ticker, o_side, o_qty, o_price, o_type), timeout=15.0)
# ํต์ ๋ฐ ์ฒ๋ฆฌ ์ฑ๊ณต ์ ๋ฃจํ ํ์ถ
break
except asyncio.TimeoutError:
if attempt < 2:
await asyncio.sleep(1.0 * (2 ** attempt))
else:
# ๐จ [TimeoutError ๋ก๊ทธ ์ฆ๋ฐ ์์ ] ๊ฐ์ง ํ์ด๋ก๋ ์ฃผ์
์ผ๋ก ์์ธ ๋์์ฑ๊ธฐ
res = {'rt_cd': '999', 'msg1': 'API ํต์ ํ์์์ (10~15์ด ์ด๊ณผ)'}
except Exception as e:
if attempt < 2:
await asyncio.sleep(1.0 * (2 ** attempt))
else:
res = {'rt_cd': '999', 'msg1': f'ํต์ /I/O ์ค๋ฅ: {str(e)}'}
safe_res = res if isinstance(res, dict) else {}
is_success = safe_res.get('rt_cd') == '0'
err_msg = html.escape(str(safe_res.get('msg1') or '์ค๋ฅ/์๊ธํจ์ค'))
if is_success:
successful_orders_cache.add(order_key)
else:
all_success = False
loop_fail_reason = f"[{ticker}] {order_category} ๊ฑฐ์ : {err_msg}"
status_icon = 'โ
' if is_success else f'โ({err_msg})'
msgs += f"โ {order_category}: {o_desc} {o_qty}์ฃผ (${o_price}): {status_icon}\n"
# ๐จ API ์ฒด๊ฒฐ/๊ฑฐ์ ํํญํ(Rate Limit) ๋ฐฉ์ง์ฉ ๋ฏธ์ธ ์กฐ์
await asyncio.sleep(0.2)
except Exception as e:
all_success = False
loop_fail_reason = f"[{ticker}] {order_category} ์น๋ช
์ ์ค๋ฅ"
logging.error(f"๐จ [{ticker}] execute_order_list ๊ฐ๋ณ ๋ซ ์ฒ๋ฆฌ ์ค๋ฅ: {e}")
msgs += f"โ {order_category} ์์คํ
์ค๋ฅ: {html.escape(str(e))}\n"
return all_success, msgs, loop_fail_reason