--- 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 [price] [routing] --send # 예산 입력 (qty 자리 '-' + --budget <원>) python3 -m orders.handler propose - MARKET --budget [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). 취소 후 신규 발주하세요.`