[계획 문서 경로] - common-docs/improvement/plans/dev-plan-2026-03-06-autotrade-real-execution-budget-tax.md [요구사항 요약] - 내 예산 기준으로 실제 몇 주를 살 수 있는지 계산하고, 자동매매 설정창에서 정한 비율대로 매수 수량이 정해지게 만든다. - 매도는 현재 보유/매도가능 수량과 비교해서 가능한 수량만 나가게 한다. - 수수료/세금/실현손익까지 고려해 진짜 자동매매처럼 동작하게 만든다. [가정] - 자동매매 설정창의 `allocationPercent`는 "이번 종목/이번 주문에 실제로 쓸 비율"로 사용한다. - `allocationAmount`는 절대 상한(최대 투자금)으로 사용한다. - 수수료/세금은 계좌/환경/정책에 따라 달라질 수 있으므로, 구현 시 하드코딩보다 `설정값 + KIS 실제 체결/매매일지 값`을 함께 쓴다. - 국내주식 단주가 아닌 1주 단위 주문 기준으로 계획한다. [영향 범위] - 수정: features/autotrade/hooks/useAutotradeEngine.ts - 수정: lib/autotrade/risk.ts - 수정: features/autotrade/types/autotrade.types.ts - 수정: features/autotrade/components/AutotradeControlPanel.tsx - 수정: app/api/autotrade/signals/generate/route.ts - 수정: lib/autotrade/openai.ts - 수정: lib/autotrade/cli-provider.ts - 수정: package.json - 추가: lib/autotrade/execution-cost.ts - 추가: lib/autotrade/executable-order-quantity.ts - 추가: tests/autotrade/risk-budget.test.ts - 추가: tests/autotrade/order-guard-cost.test.ts - 추가: common-docs/improvement/plans/dev-plan-2026-03-06-autotrade-real-execution-budget-tax.md - 삭제: 없음 [현재 코드 기준 핵심 문제] - `allocationPercent`가 실주문 계산 기준이 아니라 참고 경고 수준으로만 쓰이고 있다. - 쉬운 말: 설정창에서 10%, 25%를 바꿔도 실제 자동매매 수량 계산에는 약하게만 반영된다. - 매수 수량은 `effectiveAllocationAmount`와 `maxOrderAmountRatio` 중심이라, 내 예산/비율/호가/예상 비용을 함께 계산하는 구조가 아니다. - 매도는 `보유수량/매도가능수량` 차단은 있지만, 포지션 기준 목표 청산 비율, 부분 청산, 순손익 기준 청산 조건이 없다. - 세금/수수료는 대시보드 조회/표시에는 일부 있지만, 자동매매의 진입/청산/손실 한도 계산에는 거의 반영되지 않는다. - 일일 손실 한도는 입력 금액 기준이고, 실제 체결 후 순손익(수수료/세금 포함)과 연결되지 않는다. [구현 단계] - [x] 1. 주문 가능 예산 모델 재정의 - 입력: 가용 예수금, 매수가능금액, allocationPercent, allocationAmount, 전략별 maxOrderAmountRatio - 처리: `실주문가능예산 = min(매수가능금액, allocationAmount 상한, 예수금 * allocationPercent)` 구조로 통일 - 결과: "현재 이 종목에 실제로 쓸 수 있는 예산" 1개 값으로 고정 - [x] 2. 매수 수량 계산 로직 교체 - 입력: 실주문가능예산, 현재가/주문가, 예상 수수료, 최소 안전여유금 - 처리: 비용 포함 기준으로 최대 주문 가능 수량 계산 - 결과: "내 예산 기준으로 지금 몇 주 살 수 있는지"를 로그와 UI에 함께 표시 - [x] 3. 매도 수량 계산 로직을 포지션 기준으로 확장 - 입력: 보유수량, 매도가능수량, 평균단가, 평가손익, AI 제안 수량/비율 - 처리: 없는 주식은 절대 매도 금지, 보유보다 큰 수량 금지, 부분 매도 허용 - 결과: "실제 보유 중인 수량 안에서만 매도" 보장 - [x] 4. 수수료/세금 추정 모듈 추가 - 입력: 주문금액, 매수/매도 구분, 계좌/환경 정책 - 처리: 주문 전 예상 비용 계산, 주문 후 실제 체결/매매일지로 정산값 보정 - 결과: 순손익 기준 판단 가능 - [x] 5. 자동매매 위험 관리 기준을 순손익 기준으로 보강 - 입력: 실현손익, 평가손익, 누적 수수료, 누적 세금 - 처리: 일일 손실선/청산 조건을 총손익이 아니라 순손익 기준으로 갱신 - 결과: 세금/수수료 때문에 실제 손실이 커지는 상황 반영 - [x] 6. AI 입력값도 포지션/비용 기준으로 보강 - 입력: holdingQuantity, sellableQuantity, averagePrice, estimatedFee, estimatedTax, netProfitEstimate - 처리: AI가 매도 시 "팔 수 있는지/팔면 순손익이 어떤지"를 함께 보게 함 - 결과: 보유 없는 SELL, 손익 무시 SELL/BUY 감소 - [x] 7. UI/로그 보강 - 자동매매 설정창/로그에 아래 항목 노출 - 현재 주문 가능 예산 - 현재 매수 가능 수량 - 현재 보유 수량 / 매도 가능 수량 - 예상 수수료 / 예상 세금 / 예상 순손익 - [x] 8. 체결 후 실제값 동기화 - 주문 후 잔고/활동 API 재조회 - 체결 후 보유수량, sellableQuantity, realized fee/tax, realized profit을 스토어에 반영 - 다음 주문은 이 최신값을 기준으로 계산 [사용할 MCP/Skills] - MCP: next-devtools, sequential-thinking, mcp:kis-code-assistant-mcp - Skills: dev-plan-writer, dev-mcp-implementation, dev-refactor-polish, dev-test-gate, dev-plan-completion-checker, vercel-react-best-practices [참조 문서(common-docs)] - common-docs/api-reference/kis_api_reference.md - common-docs/api-reference/kis-error-code-reference.md - common-docs/features/trade-stock-sync.md - common-docs/ui/GLOBAL_ALERT_SYSTEM.md [주석/문서 반영 계획] - 함수 주석: [목적]/[사용처]/[데이터 흐름] 유지 - 수량 계산/비용 계산 함수에는 입력 -> 처리 -> 결과 주석 추가 - 자동매매 로그에는 "왜 주문됐는지/왜 차단됐는지" 숫자 기준 노출 [리스크/회귀 포인트] - 계좌별 수수료 정책이 다르면 세금/수수료 추정이 실제와 다를 수 있다. - 매수가능금액/잔고/매매일지 API 응답 타이밍이 어긋나면 체결 직후 수량이 잠깐 다르게 보일 수 있다. - 모의투자는 실전과 세금/수수료/매매일지 지원 방식이 다를 수 있다. - 주문 전 추정 비용과 주문 후 실제 비용이 다를 수 있으므로, 최종 손익 기준은 실제 체결/매매일지 값으로 재정산해야 한다. [검증 계획] - [x] 1. `allocationPercent`, `allocationAmount`, `매수가능금액` 조합별로 매수 수량이 기대값대로 계산되는지 단위 테스트 추가 - [x] 2. 보유 없음 / 보유 1주 / 매도가능수량 부족 상황에서 SELL이 차단되는지 테스트 - [x] 3. 수수료/세금 추정 로직과 실제 activity API 정산값 연결 테스트 - [x] 4. `npm run lint` - [x] 5. `npm run build` - [x] 6. 자동매매 스모크 시나리오 - 예산 30만원, 비율 10%, 주가 16,000원일 때 매수 가능 수량 계산 확인 - 보유 5주, 매도가능 3주일 때 SELL 수량 제한 확인 - 체결 후 잔고/활동 재조회로 보유/손익이 갱신되는지 확인 - Playwright 인증 필요 구간에서는 사용자(본인)가 로그인/앱키/계좌 인증을 완료할 때까지 테스트를 대기하고, 완료 신호를 받은 뒤 다음 단계를 진행 [진행 로그] - 2026-03-06: 현재 자동매매 코드를 점검한 결과, 매도가능수량 비교는 일부 구현되어 있으나 `allocationPercent` 실주문 반영, 세금/수수료 반영, 순손익 기준 손실 관리, 체결 후 정산 반영은 미흡한 상태로 판단함. - 2026-03-06: 구현 방향을 `예산 계산 -> 주문 수량 계산 -> 보유/매도가능 수량 검증 -> 비용 추정 -> 체결 후 실제 정산` 순서로 재설계하기로 함. - 2026-03-06: `lib/autotrade/risk.ts`에서 `allocationPercent`를 실주문 예산 계산에 강제 반영하도록 변경하고, BUY/SELL 수량 계산 경로를 분리함. - 2026-03-06: `useAutotradeEngine.ts`에 비용 추정(수수료/세금), 체결 전후 활동/잔고 재조회, 누적 손실 한도 자동중지 로직을 반영함. - 2026-03-06: AI 신호 스냅샷에 `budgetContext`, `portfolioContext`, `executionCostProfile`을 추가하고 OpenAI/CLI 프롬프트 규칙에 예산/보유/비용 제약을 반영함. - 2026-03-06: 검증 결과 `npm run lint`, `npm run build` 통과. `npm run test:autotrade:smoke`는 로그인 필요(개발 우회 토큰 미적용 환경)로 실패함. - 2026-03-06: Playwright 스모크로 `/`, `/trade`(로그인 리다이렉트 확인), `/settings`(로그인 리다이렉트 확인) 화면 로드 및 콘솔 error 없음 확인. - 2026-03-06: Playwright 테스트 협업 규칙 추가 - 로그인/앱키/계좌 인증은 사용자가 직접 완료하고, 완료 전에는 테스트를 대기하도록 문서에 명시함. - 2026-03-06: `lib/autotrade/executable-order-quantity.ts` 순수 clamp 유틸을 추가하고, `useAutotradeEngine.ts`의 실제 주문수량 검증에 연결함. - 2026-03-06: 단위 테스트 추가(`tests/autotrade/risk-budget.test.ts`, `tests/autotrade/order-guard-cost.test.ts`) 후 `npm run test:autotrade:unit` 통과. - 2026-03-06: `.env.local`의 실제 `AUTOTRADE_DEV_BYPASS_TOKEN`, `AUTOTRADE_WORKER_TOKEN`으로 스모크 재실행하여 `npm run test:autotrade:smoke` 통과. - 2026-03-06: Playwriter 실브라우저 디버깅으로 `/trade` 화면에서 `내 설정 점검 -> 자동매매 시작 -> 수동 중지` 흐름 확인(세션 시작/중지 로그 정상, 브라우저 콘솔 error 없음). 장중 실시간 틱 부재로 신호요청/주문실행 로그는 미발생. - 2026-03-06: AI 스냅샷의 `estimatedBuyableQuantity` 계산을 실제 주문 함수(`resolveOrderQuantity`)와 동일하게 통일해, 비율 예산으로 0주가 나와도 전체 예산 1주 가능 시 `1주`가 전달되도록 핫픽스함. - 2026-03-06: Playwriter 네트워크 검증으로 `/api/autotrade/signals/generate` 요청 본문에 `estimatedBuyableQuantity=1`, `effectiveAllocationAmount=21631`, `effectiveOrderBudgetAmount=7570`, `currentPrice=16790`이 전달되는 것을 확인함(수량 0 전달 이슈 해소). - 2026-03-06: 검증 자금 산정 로직을 `예수금 + 매수가능금액` 동시 조회 기반으로 변경하고, 두 값이 모두 있을 때는 더 보수적인 값(min)을 사용하도록 반영함. - 2026-03-06: 자동매매 설정창의 투자비율 입력 UX를 퍼센트 프리셋 버튼 + 슬라이더 + 금액 자동입력 버튼으로 개선하고, 안전 점검 라벨을 `가용 예수금`에서 `주문 기준 자금`으로 변경함. - 2026-03-06: `setNumberField`를 필드별 범위(clamp) 보정 방식으로 바꿔 퍼센트/신뢰도 입력이 비정상 값(음수, 100% 초과, 임계값 범위 이탈)으로 저장되지 않도록 정리함. - 2026-03-06: 회귀 검증으로 `npm run test:autotrade:unit`, `npm run lint`, `npm run build` 재실행 모두 통과함.