Files
hyowons fed3526b20 Initial commit: OpenClaw 워크스페이스 버전관리 시작
설정·스크립트·스킬·문서·큐레이션 메모리 추적.
시크릿(credentials/identity)·런타임 상태(state/logs/sessions/sqlite)·
백업(clobbered/bak)·dream 캐시는 .gitignore로 제외.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 15:39:41 +09:00

11 KiB
Raw Permalink Blame History

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:0009:00, 15:3020:00)와 KRX 단일가(15:2015:30)에선 거부 (지정가만 가능)
  • 자연어: "시장가" → MARKET, "지금 바로/즉시/빨리/당장" → 최우선호가+1틱 지정가
  • 가격 가드: 거래소 ±30% 상한가/하한가 초과 지정가 거부
  • 거래시간: 08:0020: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_send
  • skills/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으로 사후 처리

관리자님 첫 검증 절차 (예정):

  1. KRX 정규장 시간 확보
  2. 사이드카 OFF: python3 -m orders.sidecar enable
  3. 텔레그램에 자연어 매수 (권장: 본인 일반에서 KODEX 200 ETF 1주 시장가 — 변동성 ↓ 호가 ↑)
  4. 카드 검토 → PIN echo → 체결 알림 확인
  5. 잘 되면 한도 자유롭게 사용

검증 후 보강 예정

항목 현재 보강 트리거
ka10004 호가창 응답 필드명 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_summaryreturn_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 보강
list 응답 페이지네이션 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.py 9건 + tests/test_handler_budget.py 8건 추가, 총 111건 통과

skills/order-trading/SKILL.mdbudget 페이로드 추출 규칙·예시 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)