auto: 일일 백업 2026-09-22 02:00
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,120 @@
|
||||
#!/usr/bin/env python3
|
||||
"""KRX 애프터마켓(16:00~20:00, 2026-09-14 시행) 실측 probe — **조회 전용, 일회성**.
|
||||
|
||||
세션 모델을 고치기 전에 확인해야 할 4가지를 시각대별로 스냅샷으로 남긴다.
|
||||
분석이 끝나면 이 스크립트와 plist(ai.openclaw.stock.aftermarket-probe)를 지운다.
|
||||
|
||||
확인 대상:
|
||||
1. 애프터마켓 체결가가 KRX / NXT / SOR(AL) 중 어디로 오는가 (ka10095 3거래소 동시 조회)
|
||||
2. 애프터마켓 가격제한폭이 정규장과 같은가 (`upl_pric`/`lst_pric` 시각대별 비교)
|
||||
3. KRX 정규장 미체결이 15:35 증거금 해지 후 ka10075 에서 빠지는가
|
||||
4. 애프터마켓에 호가가 뜨는가 (ka10004 bare=KRX / _AL)
|
||||
|
||||
⚠️ 주문 경로를 일절 건드리지 않는다 — orders 패키지를 import 하지 않고 kiwoom_client 만 쓴다.
|
||||
⚠️ 응답을 **원시 그대로** 저장한다. 목적이 '새 필드가 생겼는지' 확인이라 정규화하면 안 된다.
|
||||
⚠️ 한 항목이 실패해도 나머지는 계속 수집한다 (에러도 같이 기록).
|
||||
|
||||
결과: state/aftermarket_probe.jsonl — 1회차 = 1줄
|
||||
사용: python3 aftermarket_probe.py [--tag <라벨>]
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import sys
|
||||
import traceback
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from pathlib import Path
|
||||
|
||||
KST = timezone(timedelta(hours=9))
|
||||
WORKSPACE = Path('/Users/snowoyh/.openclaw/agents/stock/workspace')
|
||||
sys.path.insert(0, str(WORKSPACE / 'scripts'))
|
||||
|
||||
import kiwoom_client as kc # noqa: E402
|
||||
|
||||
OUT = WORKSPACE / 'state' / 'aftermarket_probe.jsonl'
|
||||
|
||||
# NXT 미상장 일반주 = 애프터마켓에서 새로 거래 가능해지는 쪽 (관심의 핵심)
|
||||
# NXT 상장 = 기존에도 15:30 이후 거래되던 쪽 (대조)
|
||||
# ETF = 애프터마켓 거래불가로 공지된 쪽 (대조)
|
||||
TARGETS = [
|
||||
('005935', '삼성전자우', 'nxt_미상장'),
|
||||
('064400', 'LG씨엔에스', 'nxt_미상장'),
|
||||
('381620', '제닉스로보틱스', 'nxt_미상장'),
|
||||
('025980', '아난티', 'nxt_미상장'),
|
||||
('005930', '삼성전자', 'nxt_상장'),
|
||||
('000660', 'SK하이닉스', 'nxt_상장'),
|
||||
('102110', 'TIGER 200', 'etf'),
|
||||
]
|
||||
BOOK_CODES = ['005935', '005930', '102110'] # 호가는 유형별 대표 1종목씩만
|
||||
|
||||
|
||||
def _safe(fn, *a, **kw):
|
||||
"""수집 실패를 예외로 흘리지 않는다 — 한 항목 실패가 회차 전체를 날리면 안 된다."""
|
||||
try:
|
||||
return {'ok': True, 'data': fn(*a, **kw)}
|
||||
except Exception as e:
|
||||
return {'ok': False, 'error': f'{e!r}', 'trace': traceback.format_exc(limit=3)}
|
||||
|
||||
|
||||
def _raw_ka10095(codes: list[str], suffix: str) -> dict:
|
||||
"""ka10095 원시 응답. get_watchlist_quotes 는 필드를 추려버려서 직접 부른다."""
|
||||
label = kc._default_account_label()
|
||||
param = '|'.join(f'{c}{suffix}' for c in codes)
|
||||
return kc._call(label, kc.TR_WATCHLIST_INFO, {'stk_cd': param},
|
||||
endpoint=kc.ENDPOINT_STKINFO)
|
||||
|
||||
|
||||
def _raw_ka10004(code: str, suffix: str) -> dict:
|
||||
label = kc._default_account_label()
|
||||
url = kc.base_url() + kc.ENDPOINT_MRKCOND
|
||||
resp, _ = kc._http_post_full(url, {'stk_cd': f'{code}{suffix}'},
|
||||
kc.auth_headers(label, tr_id='ka10004'))
|
||||
return resp
|
||||
|
||||
|
||||
def collect(tag: str) -> dict:
|
||||
now = datetime.now(KST)
|
||||
codes = [c for c, _, _ in TARGETS]
|
||||
rec: dict = {
|
||||
'ts': now.isoformat(),
|
||||
'tag': tag,
|
||||
'targets': [{'code': c, 'name': n, 'kind': k} for c, n, k in TARGETS],
|
||||
'quotes': {},
|
||||
'books': {},
|
||||
'open_orders': None,
|
||||
}
|
||||
|
||||
# 1·2번 — 같은 종목을 세 거래소로 동시에 물어 값이 갈리는지 본다
|
||||
for ex_name, suffix in (('KRX', ''), ('NXT', '_NX'), ('SOR', '_AL')):
|
||||
rec['quotes'][ex_name] = _safe(_raw_ka10095, codes, suffix)
|
||||
|
||||
# 4번 — 애프터마켓에 호가가 실리는지
|
||||
for code in BOOK_CODES:
|
||||
for ex_name, suffix in (('KRX', ''), ('SOR', '_AL')):
|
||||
rec['books'][f'{code}:{ex_name}'] = _safe(_raw_ka10004, code, suffix)
|
||||
|
||||
# 3번 — KRX 미체결 생존 (증거금 해지 15:35 / NXT 20:05)
|
||||
rec['open_orders'] = _safe(kc.get_open_orders_all)
|
||||
|
||||
return rec
|
||||
|
||||
|
||||
def main() -> int:
|
||||
tag = 'manual'
|
||||
if '--tag' in sys.argv:
|
||||
tag = sys.argv[sys.argv.index('--tag') + 1]
|
||||
rec = collect(tag)
|
||||
OUT.parent.mkdir(parents=True, exist_ok=True)
|
||||
with OUT.open('a', encoding='utf-8') as f:
|
||||
f.write(json.dumps(rec, ensure_ascii=False) + '\n')
|
||||
|
||||
ok_q = sum(1 for v in rec['quotes'].values() if v['ok'])
|
||||
ok_b = sum(1 for v in rec['books'].values() if v['ok'])
|
||||
oo = rec['open_orders']
|
||||
n_oo = len(oo['data']) if oo['ok'] else -1
|
||||
print(f"[{rec['ts']}] tag={tag} 시세 {ok_q}/3 · 호가 {ok_b}/{len(rec['books'])} · 미체결 {n_oo}건 → {OUT}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user