fed3526b20
설정·스크립트·스킬·문서·큐레이션 메모리 추적. 시크릿(credentials/identity)·런타임 상태(state/logs/sessions/sqlite)· 백업(clobbered/bak)·dream 캐시는 .gitignore로 제외. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
11 KiB
11 KiB
orders 모듈 — 매매 절차 원칙으로 전환
2026-05-06 코디 작업. 기존 "매매 절대 원칙(주문 함수 절대 부재)"을 풀고, 사람의 PIN echo를 마지막 게이트로 두는 매매 절차 원칙으로 정책 전환.
핵심 원칙 (변경 없음)
- 레이는 매매를 결정하지 않는다. 결정권은 항상 관리자님 PIN echo.
- 자율 매매 금지 — 워치리스트 도달·시장 이벤트·뉴스로 자동 트리거 X. 명시적 매매 지시("사줘"/"팔아줘"/"매수"/"매도")가 있을 때만.
- 레이의 역할은 페이로드 추출 — 자연어 → 종목코드·계좌·수량·가격을 dict로 정리해서
orders.handler.propose_and_send진입점에 전달. - 카드+PIN 분리 발송 — 카드 메시지 + PIN 메시지 두 개를 텔레그램에 분리 발송. PIN 메시지는 단독(메타 X).
- PIN 변형 금지 — 관리자님 PIN을 그대로 echo. 변형·생성·시도 절대 X.
- 사이드카 ON 상태에선 거부 —
state/orders_disabled파일 존재 시 모든 진입점 차단.
패키지 구성
위치: agents/stock/workspace/scripts/orders/
| 모듈 | 역할 |
|---|---|
__init__.py |
매매 절차 원칙 docstring (정책 명문화) |
limits.json |
모든 한도·딜레이·시간대·PIN·라우팅 단일 진실 공급원 |
sidecar.py |
kill switch (state/orders_disabled 파일 검사) |
pin.py |
PIN 발급·검증·만료. 파일 기반(state/active_card.json + flock) — cross-process 활성 카드 공유 |
ledger.py |
append-only 주문 로그 (state/order_log.jsonl) + 멱등성 해시 |
guards.py |
12종 검증 (계좌·시간대·NXT·딜레이·±30%·잔고·시장가). 순수 함수 |
datasource.py |
키움 → market_data dict 어댑터 |
kiwoom_order.py |
kt10000(매수)/kt10001(매도) 호출. dry-run default |
card.py |
텔레그램 카드 포맷터 — 매수/매도·계좌·종목·가격 4개 bold 하이라이트 |
handler.py |
흐름 + CLI + send_telegram 통합 (propose_and_send / submit_with_pin_and_send) |
expiry_watcher.py |
launchd 만료 정리 진입점 (등록은 안 함) |
tests/test_guards.py |
단위테스트 81건 |
한도 / 정책 (관리자님 결정)
- 매매 허용 계좌: 본인 4계좌 (가희 포함)
- 1회 / 1일 / 잔고% 한도: 모두 없음
- 종목: 자유 (코스피·코스닥·ETF·ETN)
- 시장가: 허용. 단 NXT 시간대(08:00–09:00, 15:30–20:00)와 KRX 단일가(15:20–15:30)에선 거부 (지정가만 가능)
- 자연어: "시장가" → MARKET, "지금 바로/즉시/빨리/당장" → 최우선호가+1틱 지정가
- 가격 가드: 거래소 ±30% 상한가/하한가 초과 지정가 거부
- 거래시간: 08:00–20:00 (NXT 포함). 시간대×NXT eligible 매트릭스
- 라우팅 default: SOR (
_AL). "넥스트레이드/NXT" →_NX, "정규장/KRX" → KRX - 거래 간 딜레이: 60초 (마지막 카드 종결 시점부터)
- 동일 종목 딜레이: 600초 (10분, 마지막 체결 시점부터)
- PIN: 본인 4자리 숫자, 가희 8자리 영숫자(혼동글자 0/O/o/1/l/I 제외 55자)
- PIN 만료: 60초, 1회용, 1회 시도 (틀리면 카드 무효 + 재발행 필요)
- 카드+PIN 분리 발송: 메시지 A(카드, Markdown) + 메시지 B(PIN 단독, plain)
- 계좌 default 없음 — 미명시 시 넘버링 되묻기 (1) 일반 2) ISA 3) 가희_일반 4) 가희_ISA)
호출 진입점
CLI (수동 검증·디버깅)
cd ~/.openclaw/agents/stock/workspace/scripts
python3 -m orders.sidecar status
python3 -m orders.sidecar enable # 매매 풀기
python3 -m orders.sidecar disable # 잠그기
python3 -m orders.handler propose <account> BUY <symbol> <name> <qty> LIMIT <price> [routing] [--send]
python3 -m orders.handler pin <PIN> [--live] [--send]
python3 -m orders.handler cancel
python3 -m orders.handler status
python3 -m orders.handler cmd /orders_off
python3 -m orders.handler cmd /orders_on
skill (텔레그램 자연어)
skills/order-trading/SKILL.md— 자연어 매수/매도 → propose_and_sendskills/order-controls/SKILL.md— PIN echo + /orders_off /orders_on /cancel /orders_status
첫 검증 대기
2026-05-06 코디 작업 종료 시점 상태:
- 사이드카 default ON (
state/orders_disabled파일 존재) — 모든 매매 진입점 거부 - launchd plist
ai.openclaw.stock.order-expiry.plist작성됐지만 등록 안 함 (10초 데몬 비용 비대칭). 만료 알림은 다음 매매 액션 시점에 sweep으로 사후 처리
관리자님 첫 검증 절차 (예정):
- KRX 정규장 시간 확보
- 사이드카 OFF:
python3 -m orders.sidecar enable - 텔레그램에 자연어 매수 (권장: 본인 일반에서 KODEX 200 ETF 1주 시장가 — 변동성 ↓ 호가 ↑)
- 카드 검토 → PIN echo → 체결 알림 확인
- 잘 되면 한도 자유롭게 사용
검증 후 보강 예정
| 항목 | 현재 | 보강 트리거 |
|---|---|---|
✅ 2026-05-07 PDF 명세 기반 정확화 (sel_fpr_bid 1호가 + sel_{N}th_pre_bid 2~10호가) |
완료 | |
_nxt_eligible |
✅ 2026-05-07 ka10099 nxtEnable 캐시 활용. lookup_stock_meta(code) 신규. 캐시 미스/구 스키마 → 보수적 True. 4264 종목 갱신 후 NXT 가드 정확화 (3634/4264이 미상장). 회귀 테스트 5건(test_nxt_eligible.py) |
완료 |
| 거래정지(halt) 플래그 | False stub | REST 단독 플래그 없음 (실시간 websocket 1h 만 존재). 사후 broker reject가 안전판. ka10099 state에 "거래정지" 키워드 매핑은 후속 보강 안건 |
| 체결(filled) 폴링 | 미구현 (접수까지만 ledger) | 별도 체결조회 TR 추가 (kt00007 활용 가능) |
ord_seq_no/rt_cd fallback |
✅ 2026-05-07 명세에 없는 키 제거. response_summary도 return_code+return_msg 정확화. 회귀 테스트 6건(test_kiwoom_order.py) 신규 |
완료 |
✅ 2026-05-07 등급별 차등 정책 적용. guards.evaluate_stock_state 신규. 거부: orderWarning ∈ {2 정리매매, 4 투자위험} OR state에 '거래정지'·'정리매매'. 경고: orderWarning ∈ {1 ETF주의, 3 단기과열, 5 투자경과} OR state에 '관리종목'. 회귀 14건(test_stock_state.py). 캐시 기준 거부 대상 약 110+종목, 경고 대상 약 175종목 |
완료 | |
| 자연어 파싱 정확도 | LLM 1차 검증 후 | 자주 틀리는 패턴 발견 시 SKILL.md 보강 |
✅ 2026-05-07 _call_paginated(label, tr_id, body, list_field) 헬퍼 신규. get_positions(kt00018)·get_trade_journal(ka10170)·get_order_executions(kt00007) 페이징 처리. 응답 헤더 cont-yn=Y + next-key 자동 후속 호출, list 누적, max_pages=50 safety cap. 현재 종목 30개 환경에선 cont-yn=N으로 1페이지에 끝나지만 미래 안전판. 회귀 8건(test_pagination.py) |
완료 |
키움 TR 검증 결과
2026-05-07 PDF 공식 명세 전수 대조 완료 (~/.openclaw/docs/키움 REST API 문서.pdf — 카탈로그는 ~/.openclaw/docs/README.md).
| TR | endpoint | 상태 |
|---|---|---|
| kt10000 (매수) | /api/dostk/ordr |
✅ body 9필드 일치 (2026-05-06 검증) |
| kt10001 (매도) | /api/dostk/ordr |
✅ body 9필드 일치, 첫 실매도 통과 (2026-05-07 KODEX 은선물 1주) |
| kt00001 (예수금) | /api/dostk/acnt |
✅ |
| kt00004 (계좌평가) | /api/dostk/acnt |
✅ |
| kt00007 (체결내역) | /api/dostk/acnt |
✅ |
| kt00018 (잔고내역) | /api/dostk/acnt |
✅ |
| ka10001 (주식기본정보) | /api/dostk/stkinfo |
✅ + 2026-05-07 상한가/하한가 필드 수정 (upl_pric/lst_pric) |
| ka10004 (호가) | /api/dostk/mrkcond |
✅ 2026-05-07 endpoint·필드 모두 수정 |
| ka10099 (종목리스트) | /api/dostk/stkinfo |
✅ |
| ka10170 (당일매매일지) | /api/dostk/acnt |
✅ (sel_avg_pric 키움 자체 오타 정확 매핑) |
| ka10075 (미체결) | /api/dostk/acnt |
✅ 2026-05-13 신규. 활성 미체결만, 정정/취소 대상 추출용 (kiwoom_client.get_open_orders) |
| kt10002 (정정) | /api/dostk/ordr |
✅ 2026-05-13 신규. body 6필드 (dmst_stex_tp, orig_ord_no, stk_cd, mdfy_qty, mdfy_uv, mdfy_cond_uv) |
| kt10003 (취소) | /api/dostk/ordr |
✅ 2026-05-13 신규. body 4필드 (dmst_stex_tp, orig_ord_no, stk_cd, cncl_qty). cncl_qty='0' 잔량 전부 취소 |
kt10000/kt10001 body 명세: dmst_stex_tp(SOR/NXT/KRX), stk_cd(종목코드), ord_qty(str), ord_uv(가격 또는 빈 문자열), trde_tp('0' 보통 / '3' 시장가), cond_uv(미사용)
2026-05-07 budget(예산) 기반 매매 추가
자연어 "삼성전자 100만원어치 매수" 처리. 키움 API는 금액 입력 안 받아서 우리가 환산.
- 환산 규칙: 매수=매도1호가, 매도=매수1호가, floor 나눗셈, 슬리피지 마진 0%
- 소수점 매매 안 함 (정수 주식만, 잔액은 카드에 표시)
- 거부:
BUDGET_TOO_SMALL(1주 가격 > 예산),BUDGET_INVALID(0/음수),NO_ORDERBOOK(호가 조회 실패) - CLI:
python3 -m orders.handler propose <account> BUY <symbol> <name> - MARKET --budget <won> --send(qty 자리는-) - 키움 발주: 환산된 정수 qty + 시장가(
trde_tp=3)로 그대로 진행. 환산용 가격은 우리 내부 계산용일 뿐 키움에 안 감. - 회귀 테스트:
tests/test_guards.py9건 +tests/test_handler_budget.py8건 추가, 총 111건 통과
skills/order-trading/SKILL.md 에 budget 페이로드 추출 규칙·예시 4개 등록.
- 응답
ord_no: 주문번호,return_code: 0 성공
2026-05-13 키움 접수 후 미체결 정정/취소 추가
신규 매매 카드(amend/cancel)는 PIN 미입력 단계 활성 카드만 처리. PIN 통과 후 키움에 접수된 미체결 주문은 별도 진입점.
- 신규 TR: ka10075(미체결 조회), kt10002(정정), kt10003(취소)
- kiwoom_client.py (read-only):
get_open_orders(label),get_open_orders_all()— 활성 미체결 행 정규화 반환 - kiwoom_order.py:
cancel_order(),modify_order()— submit() 패턴 그대로. dry-run default, ledger 기록 - handler.py:
cancel_open_order(),modify_open_order()— auto-resolve(ord_no 미명시 + 1건이면 자동, 다수면 AMBIGUOUS 거부 + 리스트) - PIN 게이트 없음 — 관리자님 결정 (취소는 안전 방향). 단
--live명시 없으면 dry-run - 신규 ledger 이벤트:
cancel_submitted,cancel_rejected,modify_submitted,modify_rejected - CLI 진입점:
python3 -m orders.handler open-orders [--account A] python3 -m orders.handler cancel-order [--ord-no N] [--account A] --live --send python3 -m orders.handler modify-order [--ord-no N] [--account A] [--qty Q] [--price P] --live --send - 시장가 미체결 정정 불가 — 키움 mdfy_uv 0 불가. 취소 후 신규 발주 안내
- routing_suffix 보존 — 원주문 ka10075 응답의 stex_tp(0/1/2) →
_AL/``/_NX매핑해서 정정/취소 호출에 그대로 전달 (거래소 일치 필수) - 검증: dry-run 10건 회귀 + 실 키움 ka10075 호출 확인 (2026-05-13)