Initial commit: OpenClaw 워크스페이스 버전관리 시작

설정·스크립트·스킬·문서·큐레이션 메모리 추적.
시크릿(credentials/identity)·런타임 상태(state/logs/sessions/sqlite)·
백업(clobbered/bak)·dream 캐시는 .gitignore로 제외.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
hyowons
2026-06-04 15:10:57 +09:00
commit 549545bde6
199 changed files with 49671 additions and 0 deletions
@@ -0,0 +1,332 @@
---
name: order-trading
description: 관리자님이 명시적으로 매수/매도를 지시하거나 활성 카드의 수량/가격을 수정할 때만 사용. "삼성 5주 75000원에 사줘", "카카오 10주 시장가 매수", "ISA에서 005930 3주 팔아줘", "삼성전자 100만원어치 매수해줘" 같은 신규 매매 지시 + "수량 20주로 바꿔줘", "가격 76000원으로", "예산 200만원으로" 같은 카드 수정 지시. 0 입력("0주로", "취소") → 카드 취소. **키움에 이미 접수된 미체결 주문**의 정정/취소("미체결 취소해줘", "ord_no 123 가격 76000으로 정정")도 이 스킬에서 처리. 워치리스트 도달이나 시장 이벤트, 자체 추론으로는 절대 트리거하지 않음. PIN 답장(4~8자리)이나 매매 제어(/orders_off 등)는 order-controls 스킬을 사용.
---
# order-trading
관리자님 매매 지시 → 페이로드 추출 → `orders.handler.propose_and_send` 호출 → 카드+PIN 분리 발송. 결정권은 PIN echo 에 있다.
## 절대 원칙
1. **자율 매매 금지** — 워치리스트 도달·뉴스·시장 이벤트로는 절대 트리거 X. 관리자님 명시적 지시("사줘"/"팔아줘"/"매수"/"매도")가 있을 때만.
2. **추측·할루시네이션 금지** — 종목명·수량·가격·계좌가 모호하면 카드 발행하지 말고 되묻는다.
3. **사이드카 ON 상태면 시도 X**`state/orders_disabled` 존재 시 거부 메시지가 반환됨. 그 메시지 그대로 관리자님께 전달.
4. **카드 발행 후 확인문은 한 줄만**`--send`가 카드+PIN을 텔레그램에 직접 보내므로 장황한 안내(`PIN 회신 대기`, `120초 만료` 등)는 보내지 않는다. 성공 시 레이는 `[계좌명] [종목명] [매수/매도] 카드 발급완료` 형식의 짧은 한국어 한 줄만 보낸다. 예: `가희 일반 LG전자 매수 카드 발급완료`. 단, 거부·오류·모호해서 되묻는 경우는 그대로 안내한다.
## 페이로드 추출 규칙
자연어에서 다음을 식별:
- **account** — "본인 일반"·"일반"·"ISA"·"가희 일반"·"가희_일반"·"가희 ISA"·"가희_ISA". **명시 없으면 무조건 되묻기**. 자동 default 없음. "가희" 만 있고 "일반/ISA" 미명시여도 되묻기. 4계좌 모호함 방지가 default 자동 가정보다 우선.
- **side** — "사", "매수", "buy" → `BUY` / "팔", "매도", "sell" → `SELL`.
- **symbol** — 6자리 종목코드면 그대로. 종목명("삼성", "삼성전자", "카카오")이면 `kiwoom_client.resolve_stock_code` CLI 로 코드 변환.
- **symbol_name** — 한글 종목명. resolve 결과 또는 사용자 입력.
- **qty** — 숫자(주). "5주"·"5". **budget 과 동시 지정 불가**.
- **budget** — 원 단위 금액. "100만원어치"·"50만원"·"1,000,000원어치"·"30만원으로". qty 대신 입력. 매수/매도 둘 다 가능.
- 환산: 키움 호가창 기준 1주 가격으로 floor 나눗셈. 매수 = 매도1호가, 매도 = 매수1호가.
- **BUY +1 정책**: 1주 가격(매도1호가) ≤ 300,000원 이고 floor 잔액이 0보다 크면 1주 더 매수. 예산을 살짝 초과해서 14주처럼. 30만원 초과 고가주는 floor 유지. SELL 은 항상 floor (보유수량 초과 매도 방지).
- 정수 주식만. 카드에는 잔액(또는 bumped 시 초과액)·매수가능금액(BUY)·보유수량(SELL) 표시.
- 예산이 1주 가격보다 작으면 `BUDGET_TOO_SMALL` 거부.
- 키움에는 무조건 시장가(`trde_tp=3`)로 발주. 환산 가격은 우리 내부 계산용일 뿐 키움에 안 감.
- 자연어 단위 정리: "100만원" → 1000000, "50만" → 500000, "1.5억" → 150000000. 천단위 콤마 제거.
- **"전액매수" / "예수금 다 써서" / "예수금 전부로" / "전부 매수"** → `kiwoom_client.get_balance(account_label)` 결과의 **`ord_alow_amt`(주문가능금액)** 를 `--budget` 으로 사용. `ord_alow_amt`가 0/누락이면 `d2_entra` 폴백. **`entr`은 절대 사용 X** (미정산 매도대금 포함되어 부풀려진 값). `d2_entra`를 빼는 계산(`entr - d2_entra` 등) **절대 금지** — D+2는 "이틀 후 도착"이 아니라 "결제 사이클 끝났을 때의 진짜 잔액"이며 그 자체가 가용 (auto-memory `reference_kiwoom_deposit_basis.md` 참조).
- **order_type** — 가격 명시되면 `LIMIT`. "시장가" 명시 → `MARKET`. "지금 바로", "즉시", "빨리", "당장" 명시 → `AGGRESSIVE_LIMIT`. **budget 입력은 자동 `MARKET`** (예산 기반은 시장가만 지원). 그 외 가격 미명시면 되묻기.
- **price** — `LIMIT` 일 때만. 천단위 콤마/원 표기 정리("75,000원" → 75000).
- **routing_force** — 보통 명시 X. 사용자가 "넥스트레이드로", "NXT로" → `NX`. "정규장으로", "KRX로" → `KRX`. 명시 없으면 SOR 자동.
추출 결과를 dict 로 정리한 후 CLI 한 번 호출.
## 진입점 (CLI)
```bash
cd ~/.openclaw/agents/stock/workspace/scripts
# 수량 입력
python3 -m orders.handler propose <account> <side> <symbol> <symbol_name> <qty> <order_type> [price] [routing] --send
# 예산 입력 (qty 자리 '-' + --budget <원>)
python3 -m orders.handler propose <account> <side> <symbol> <symbol_name> - MARKET --budget <won> [routing] --send
# 활성 카드 수정 — qty/price/order_type/budget/routing 중 하나 이상
python3 -m orders.handler amend [--qty N] [--price W] [--order-type LIMIT|MARKET|AGGRESSIVE_LIMIT] [--budget W] [--routing AL|NX|KRX] --send
# 활성 카드 취소
python3 -m orders.handler cancel --send
# 키움에 접수된 미체결 주문 조회
python3 -m orders.handler open-orders [--account A]
# 키움에 접수된 미체결 주문 취소 (PIN 없음, 안전 방향)
python3 -m orders.handler cancel-order [--ord-no N] [--account A] --live --send
# 키움에 접수된 미체결 주문 정정 (PIN 없음)
python3 -m orders.handler modify-order [--ord-no N] [--account A] [--qty Q] [--price P] --live --send
```
`--send` 플래그가 카드 + PIN 두 메시지를 텔레그램으로 자동 분리 발송한다. skill 응답은 발행 결과 요약만.
**`--live`**: 미체결 정정/취소(`cancel-order`/`modify-order`)는 안전을 위해 기본 dry-run. 실주문은 `--live` 명시 필요.
## 예시
**예 1 — 본인 일반 매수 지정가**
- 입력: "삼성 5주 75000원에 사줘"
- 명령:
```bash
python3 -m orders.handler propose 일반 BUY 005930 삼성전자 5 LIMIT 75000 --send
```
- 응답: `[계좌명] [종목명] [매수/매도] 카드 발급완료`
**예 2 — 가희 ISA 매도 시장가 (NXT 시간대 외)**
- 입력: "가희 ISA에서 카카오 10주 시장가로 팔아줘"
- 명령:
```bash
python3 -m orders.handler propose 가희_ISA SELL 035720 카카오 10 MARKET --send
```
- 응답: `[계좌명] [종목명] [매수/매도] 카드 발급완료`
**예 3 — 공격적 지정가**
- 입력: "삼성 3주 지금 바로 사줘"
- 명령:
```bash
python3 -m orders.handler propose 일반 BUY 005930 삼성전자 3 AGGRESSIVE_LIMIT --send
```
- 응답: `[계좌명] [종목명] [매수/매도] 카드 발급완료`
**예 3-2 — 예산 기반 시장가 매수**
- 입력: "삼성전자 100만원어치 매수해줘" (계좌는 직전 컨텍스트 또는 되묻기로 확정 가정)
- 명령 (일반 계좌일 때):
```bash
python3 -m orders.handler propose 일반 BUY 005930 삼성전자 - MARKET --budget 1000000 --send
```
- 응답: `[계좌명] [종목명] [매수/매도] 카드 발급완료` 카드 자체에 예산 환산 정보가 표시됨.
**예 3-3 — 예산이 1주 가격보다 작음**
- 입력: "LG전자 5만원어치 사줘" (1주 ≈ 85,000원)
- 명령:
```bash
python3 -m orders.handler propose 일반 BUY 066570 LG전자 - MARKET --budget 50000 --send
```
- 응답 (카드 발행 X, 거부): "⛔ 거부 [BUDGET_TOO_SMALL]: 1주 가격(85,000원, 매도1호가)이 예산(50,000원)보다 큽니다"
**예 3-4 — 예산 기반 매도**
- 입력: "ISA에서 카카오 50만원어치 팔아줘"
- 명령:
```bash
python3 -m orders.handler propose ISA SELL 035720 카카오 - MARKET --budget 500000 --send
```
- 응답: `[계좌명] [종목명] [매수/매도] 카드 발급완료` 카드 자체에 예산 환산 정보가 표시됨.
**예 4 — 계좌 미명시 → 넘버링 되묻기**
- 입력: "삼성전자 1주 시장가로 사줘"
- 응답 (카드 발행 X):
```
어느 계좌에서 매수할까요?
1) 일반
2) ISA
3) 가희_일반
4) 가희_ISA
```
- 사용자가 숫자(`1`~`4`) 또는 라벨(`일반`/`ISA`/`가희_일반`/`가희_ISA`) 회신 시 직전 매매 의도와 합쳐 propose CLI 호출.
**예 5 — "가희" 만 있고 일반/ISA 미명시 → 넘버링 되묻기**
- 입력: "가희로 카카오 10주 사줘"
- 응답 (카드 발행 X):
```
가희 어느 계좌인가요?
1) 가희_일반
2) 가희_ISA
```
- 숫자(`1`~`2`) 또는 라벨 회신 시 propose 호출.
**예 6 — 모호한 수량/가격은 되묻기**
- 입력: "삼성 좀 사줘"
- 응답: "어느 계좌, 몇 주(또는 얼마어치), 가격(또는 시장가)을 알려주세요. 예: `일반에서 삼성 5주 75000원에 매수` 또는 `일반에서 삼성 100만원어치 매수`."
## 카드 수정 / 취소 (활성 카드 있을 때)
활성 카드(미만료·미소비)가 있을 때 관리자님이 일부 항목만 바꾸길 원하면 `amend` 호출.
- **수정 가능 필드**: `qty`, `price`, `order_type`, `budget`, `routing_force`, `symbol`(+`symbol_name`), `account`. 한 번에 여러 개 가능.
- **수정 불가**: `side` (매수↔매도) — 의미가 정반대라 새 카드로 발행. "방향 바꿔" 류 입력은 "방향(매수/매도)은 amend 불가합니다. /orders_cancel 후 새로 발행해 주세요." 안내.
- **종목 변경 시 symbol_name 동반 필수**: 자연어 종목명 → `kiwoom_client.resolve_stock_code` 로 6자리 코드 변환 후 코드+이름 둘 다 명시.
- **계좌 변경 시 PIN 길이 자동 재조정**: 본인↔가희 전환 시 새 PIN 은 본인 4자리/가희 8자리로 발급.
- **PIN 전체 재발급**: amend 성공 시 새 PIN 다시 발송 + 만료 120초 리셋. card_id 는 유지.
- **0 입력 = 취소**: `qty`/`price`/`budget` 중 하나라도 0 이면 cancel 위임. 0 + 다른 amend 값 동시 입력은 `AMBIGUOUS_AMEND` 거부.
- **가드 재실행**: 잔고·보유·가격대·시간대·NXT·상태 12개 가드 전부 재검증. 통과 시에만 갱신.
### 자연어 트리거 예시
| 입력 | CLI |
|---|---|
| "수량 20주로 바꿔줘" / "20주로" | `amend --qty 20 --send` |
| "가격 76000원으로" | `amend --price 76000 --send` |
| "20주 76000원으로" | `amend --qty 20 --price 76000 --send` |
| "시장가로 바꿔" | `amend --order-type MARKET --send` |
| "예산 200만원으로" | `amend --budget 2000000 --send` |
| "지금 바로로 바꿔" | `amend --order-type AGGRESSIVE_LIMIT --send` |
| "ISA로 바꿔" / "가희 일반으로" | `amend --account ISA --send` (계좌만) |
| "카카오로 바꿔" / "035720으로" | `amend --symbol 035720 --symbol-name 카카오 --send` |
| "ISA에서 카카오로" | `amend --account ISA --symbol 035720 --symbol-name 카카오 --send` |
| "0주로" / "취소" / "그만" | `amend --qty 0 --send` (또는 `cancel --send`) |
활성 카드가 없으면 `NO_ACTIVE_CARD` 거부 응답을 그대로 echo.
**예 7 — 수량 수정**
- 입력: "수량 20주로 바꿔줘"
- 명령:
```bash
python3 -m orders.handler amend --qty 20 --send
```
- 응답: `[계좌명] [종목명] [매수/매도] 카드 발급완료`
**예 8 — 가격 + 주문방식 동시 수정**
- 입력: "85000원 지정가로"
- 명령:
```bash
python3 -m orders.handler amend --order-type LIMIT --price 85000 --send
```
**예 9 — 0 입력으로 취소**
- 입력: "0주로" / "취소해줘" / "그만"
- 명령:
```bash
python3 -m orders.handler amend --qty 0 --send
# 또는
python3 -m orders.handler cancel --send
```
**예 10 — 계좌만 변경 (본인↔가희 전환 시 PIN 길이 자동)**
- 입력: "가희 ISA로 바꿔" (활성 카드 본인 일반)
- 명령:
```bash
python3 -m orders.handler amend --account 가희_ISA --send
```
- 응답: PIN 8자리 영숫자로 재발급된 카드 발행.
**예 11 — 종목 변경 (코드+이름 동반 필수)**
- 입력: "카카오로 바꿔서 매수" (활성 카드 005930 삼성전자)
- 처리: `kiwoom_client.resolve_stock_code 카카오` → `035720`
- 명령:
```bash
python3 -m orders.handler amend --symbol 035720 --symbol-name 카카오 --send
```
**예 12 — 종목·계좌 동시 변경**
- 입력: "ISA에서 카카오 10주로 바꿔"
- 명령:
```bash
python3 -m orders.handler amend --account ISA --symbol 035720 --symbol-name 카카오 --qty 10 --send
```
**예 13 — 방향 변경 시도 → 거부 안내**
- 입력: "매도로 바꿔" (활성 카드는 BUY)
- 응답: "방향(매수/매도)은 amend 불가합니다. `/orders_cancel` 후 새로 발행해 주세요."
## 되묻기 응답 처리 규칙
직전 턴에서 레이가 넘버링 옵션을 제시하고, 사용자가 짧은 숫자/라벨로만 회신한 경우:
- 1자리 숫자 (`1`~`4`) — 계좌 선택. 직전 매매 의도(종목·수량·가격·side)와 합쳐 propose 호출.
- 라벨 직접 입력(`일반`, `ISA`, `가희_일반`, `가희_ISA`) — 동일하게 처리.
- **PIN 과 혼동 주의** — PIN 은 본인 4자리 / 가희 8자리. 1~2자리 숫자는 PIN 이 아니다. 활성 카드가 있더라도 1~2자리 숫자는 계좌 선택으로 해석.
- 직전 턴이 매매 되묻기가 아니면 숫자만 회신은 무시하고 일반 응답.
**예 5 — 사이드카 ON**
- 입력: "삼성 5주 75000원에 사줘"
- 명령 결과: `{"ok": false, "message": "🚫 매매 비활성화 상태..."}`
- 응답: 그대로 echo. "🚫 매매 비활성화 상태입니다. /orders_on 으로 재개하세요."
## 거부되는 요청 (이 스킬 호출하지 말 것)
- "워치리스트 도달했으니 자동 매수해" → 자율 매매 금지. 카드 발행 X.
- "내일 자동으로 사줘" → 예약 매매 X. 시점에 관리자님이 직접 명령.
- "이거 살까 말까?" → 의견 질문이지 매매 지시 아님. 카드 발행 X.
## 가드 (자동 적용 — 이 스킬 호출 후 거부 메시지가 오면 그대로 echo)
`orders.handler.propose_trade` 가 다음을 자동 검증:
- 계좌 화이트리스트
- 거래시간 + NXT 매트릭스 (NXT 시간대에 NXT 미지원 종목 → 거부)
- NXT 시간대·KRX 단일가 시장가 → 거부
- ±30% 상한가/하한가
- 거래정지·VI·휴장일
- 잔고/보유수량 사전조회
- 거래 간 60초 / 같은 종목 3분 딜레이 (실주문 접수 이후만 카운트 — PIN 오타·취소·키움 거절은 면제)
- 같은 종목 3분 — 키움 진실 소스(kt00007) 추가 검증. 우리 ledger 모르는 매매(NETWORK 사각·사용자 키움앱 매매)도 차단. 키움 조회 실패 시 보수적 차단
- 사이드카 ON / LLM 환경변수 차단
- **예산 환산** (budget 입력 시 qty 결정 직전): `BUDGET_INVALID` (0/음수), `NO_ORDERBOOK` (호가창 조회 실패), `BUDGET_TOO_SMALL` (1주 가격 > 예산)
거부 시 `⛔ 거부 [코드]: 메시지` 형태. 그대로 관리자님께 전달.
## 키움 접수 후 미체결 주문 정정·취소 (kt10002 / kt10003)
`amend` / `cancel` 은 **활성 카드(PIN 미입력 단계)** 만 처리한다. PIN 통과 후 키움에 접수된 미체결 주문은 별도 진입점이 처리한다 — `cancel-order` / `modify-order`.
### 절대 원칙
- **PIN 게이트 없음** — 취소는 안전 방향이라 즉시 실행. 정정도 동일 정책 (관리자님 결정).
- **자율 트리거 금지** — 워치리스트·시장 이벤트로 자동 취소/정정 발화 X.
- **`--live` 없으면 dry-run** — 안전 기본값. 실주문은 `--live` 명시 필요.
- **시장가 미체결 정정 불가** — 키움 명세상 가격 0 불가. 취소 후 신규 발주로 안내.
### 자연어 트리거
| 입력 | CLI |
|---|---|
| "미체결 보여줘" / "취소할 주문 뭐 있어" | `open-orders --send` (혹은 결과를 텍스트로 echo) |
| "미체결 취소" (1건만 있을 때) | `cancel-order --live --send` |
| "ord_no 12345 취소" | `cancel-order --ord-no 12345 --live --send` |
| "ISA 미체결 다 취소" (ISA에 1건) | `cancel-order --account ISA --live --send` |
| "ord_no 12345 가격 76000으로 정정" | `modify-order --ord-no 12345 --price 76000 --live --send` |
| "ord_no 12345 수량 3주로 정정" | `modify-order --ord-no 12345 --qty 3 --live --send` |
### 자동 대상 추출
- `ord_no` 명시 — 그대로 사용. account 도 같이 주면 빠르고, 안 줘도 4계좌에서 매칭.
- `ord_no` 미명시 + 미체결 1건 — 자동 선택.
- `ord_no` 미명시 + 미체결 2건 이상 — `AMBIGUOUS` 거부 + 리스트 echo. 관리자님께 ord_no 선택 요청.
- 미체결 0건 — `NO_OPEN_ORDERS` 거부.
### 응답 형식
- 취소 성공: `✅ 취소 접수 · 원주문 ord_no=X → 취소 ord_no=Y · [계좌] 종목 매수/매도 N주 (KRX)`
- 이후 broker 확정 시 후속 알림: `✅ 매수/매도 취소 확인: 종목 N주 취소됨\n원주문: X / 취소주문: Y` (fill_watcher 데몬이 ka10075 폴링)
- 30분 미확인 시: `⏱️ 매수/매도 취소 30분째 미확인 ...`
- 정정 성공: `✏️ 정정 접수 · 원주문 ord_no=X → 정정 ord_no=Y · [계좌] 종목 매수/매도 5주 @ 75,000원 → 3주 @ 76,000원 (KRX)`
- dry-run: `🧪 [DRY-RUN] ...`
- 키움 거절: `⛔ 취소/정정 실패 [BROKER_REJECT] · ord_no=X ...\n사유: ...`
### 예시
**예 A — 미체결 1건 즉시 취소**
- 입력: "미체결 취소해줘"
- 명령:
```bash
python3 -m orders.handler cancel-order --live --send
```
- 응답: `✅ 취소 접수 ...` (단일 한 줄)
**예 B — ord_no 명시 정정 (가격만)**
- 입력: "0001234 가격 76000으로 정정"
- 명령:
```bash
python3 -m orders.handler modify-order --ord-no 0001234 --price 76000 --live --send
```
**예 C — 다수 미체결 → 모호**
- 입력: "미체결 취소"
- 명령:
```bash
python3 -m orders.handler cancel-order --live --send
```
- 응답:
```
대상 ord_no 를 명시해 주세요:
ord_no=0001234 · [일반] 삼성전자 (005930) 매수 5주 @ 75,000원 · 미체결 5주 ...
ord_no=0005678 · [ISA] 카카오 (035720) 매도 10주 @ 50,000원 · 미체결 10주 ...
```
- 관리자님이 ord_no 회신 시 그 값으로 재호출.
**예 D — 시장가 정정 시도 → 거부 안내**
- 입력: "시장가 미체결 가격 76000으로 정정" (원주문이 시장가)
- 응답: `⛔ 시장가 미체결은 정정 불가 (가격 0). 취소 후 신규 발주하세요.`