모의투자 관리 기능 추가

This commit is contained in:
Macbook
2026-05-31 03:47:07 +09:00
parent 9d7dddfa57
commit 16d0e2c226
78 changed files with 4688 additions and 972 deletions
@@ -0,0 +1,466 @@
# 모의투자 투자금·종목별 관리 설계
상용 주식/HTS 프로그램(키움 영웅문 모의, NH/미래 HTS 모의, TradingView Paper 등)에서 통상 제공하는 **계좌·투자금·종목별 배분·수익률·거래내역** 구조를 기준으로, goldenChart **현재 구현(as-is)****목표(to-be)** 를 정리한 설계서입니다.
---
## 1. 목표
| 목표 | 설명 |
|------|------|
| **투자금 단일 원천** | 모의 계좌의 현금·총자산·초기자본·실현/평가 손익을 DB에서 일관되게 관리 |
| **종목별 투자 한도** | 종목마다 최대 투자금(또는 비율)을 두고 매수 시 한도 초과 방지 |
| **투자금 설정 vs 관리 분리** | 설정(규칙·초기화) 화면과 운영(잔고·보유·체결) 화면 역할 분리 |
| **종목별 매수/매도** | 수동 주문·전략 자동매매가 동일 계좌·동일 한도 규칙을 따름 |
| **가상매매 연동** | 가상투자 대시보드·모의투자 메뉴가 **동일 `gc_paper_*` 계좌** 를 참조 |
---
## 2. 현재 구현 (As-Is)
### 2.1 DB (V18)
```
gc_app_settings ← 전역: 초기자본, 수수료, 슬리피지, 자동매매 ON/OFF, 매수 예산 %
gc_paper_account ← device_id당 1계좌: cash_balance, realized_pnl
├── gc_paper_position ← 종목별: quantity, avg_price (한도·배분 없음)
└── gc_paper_trade ← 체결 이력: BUY/SELL, fee, cash_after
```
### 2.2 API
| 메서드 | 경로 | 용도 |
|--------|------|------|
| GET/POST | `/api/paper/summary` | 계좌 요약·보유·평가손익(markPrices 선택) |
| GET | `/api/paper/trades` | 체결 목록 |
| POST | `/api/paper/orders` | 즉시 체결 주문 |
| POST | `/api/paper/reset` | 계좌 초기화 |
### 2.3 화면
| 화면 | 위치 | 역할 |
|------|------|------|
| **가상매매 설정** | 설정 → 가상매매 | 초기자본·수수료·자동매매 (전역) |
| **모의투자 설정 탭** | 모의투자 좌측 | 위와 동일 항목 + 계좌 초기화 |
| **모의투자 대시보드** | 메뉴 모의투자 | KPI·보유·체결·차트·수동 매수/매도 |
| **가상투자** | 메뉴 가상투자 | 전략 모니터링 + `placePaperOrder` 자동/수동 |
### 2.4 부족한 점 (Gap)
- **종목별 투자금 한도** 없음 → 전역 `paper_auto_trade_budget_pct` 만 존재
- **현금 변동 원장**(입금/출금/수수료/초기화) 미분리 → `cash_after` 만 체결에 기록
- **일별 자산 스냅샷** 없음 → 수익률 차트는 프론트가 체결만 재구성
- **미체결 주문** 없음 → 모두 즉시 체결
- **종목별 실현손익·회전율** 집계 테이블 없음
- **투자금 설정** 이 설정 앱 + 모의투자에 이중 노출
---
## 3. 상용 모의투자 벤치마크
| 영역 | 통상 기능 | goldenChart 매핑 |
|------|-----------|------------------|
| 계좌 개설 | 초기 예탁금 설정 | `paper_initial_capital` + `gc_paper_account` |
| 계좌 초기화 | 잔고·보유·내역 리셋 | `POST /paper/reset` (유지·이력 테이블 추가 권장) |
| 예수금/주문가능 | 현금, D+0 가용 | `cash_balance` `reserved_cash` |
| 종목별 매수한도 | 종목당 상한 또는 비중 상한 | **신규** `gc_paper_symbol_allocation` |
| 보유종목 | 수량·평단·평가·손익률·비중 | `gc_paper_position` + summary API 확장 |
| 체결내역 | 일자·종목·매수/매도·가격·수량·수수료 | `gc_paper_trade` (필터·페이징 추가) |
| 자산추이 | 일별 총자산 그래프 | **신규** `gc_paper_daily_snapshot` |
| 원장/자금변동 | 입출금·수수료·배당·이자 | **신규** `gc_paper_cash_ledger` |
| 수동 주문 | 지정가/시장가·수량·금액 | `TradeOrderPanel` + `/paper/orders` |
| 자동매매 | 전략 시그널 연동 | STOMP 시그널 → `placePaperOrder` |
---
## 4. DB 설계 (To-Be)
### 4.1 ER 개요
```mermaid
erDiagram
gc_app_settings ||--o{ gc_paper_account : "defaults"
gc_paper_account ||--o{ gc_paper_position : holds
gc_paper_account ||--o{ gc_paper_trade : fills
gc_paper_account ||--o{ gc_paper_symbol_allocation : limits
gc_paper_account ||--o{ gc_paper_cash_ledger : cash_flow
gc_paper_account ||--o{ gc_paper_daily_snapshot : eod
gc_paper_account ||--o{ gc_paper_reset_log : history
gc_paper_symbol_allocation }o--|| gc_strategy : optional
```
### 4.2 테이블 상세
#### (기존 확장) `gc_paper_account`
| 컬럼 | 타입 | 설명 |
|------|------|------|
| `id` | BIGINT PK | |
| `user_id` / `device_id` | | 기존 |
| `cash_balance` | DECIMAL(20,2) | 예수금 |
| `reserved_cash` | DECIMAL(20,2) **NEW** | 미체결·예약 금액 합 (Phase 2) |
| `realized_pnl` | DECIMAL(20,2) | 누적 실현손익 |
| `initial_capital_snapshot` | DECIMAL(20,2) **NEW** | 마지막 초기화 시점 기준 초기자본 |
| `reset_count` | INT **NEW** | 초기화 횟수 |
| `last_reset_at` | DATETIME **NEW** | |
| `status` | VARCHAR(20) **NEW** | `ACTIVE` \| `FROZEN` |
| `created_at` / `updated_at` | | |
**파생 지표(API 계산, 컬럼 비저장 권장)**
- `orderable_cash` = `cash_balance` `reserved_cash`
- `stock_eval_amount` = Σ(보유 수량 × markPrice)
- `total_asset` = `cash_balance` + `stock_eval_amount`
- `unrealized_pnl` = `stock_eval_amount` Σ(수량 × 평단)
- `total_return_pct` = (`total_asset` `initial_capital_snapshot`) / `initial_capital_snapshot` × 100
#### (신규) `gc_paper_symbol_allocation` — 종목별 투자금 설정
상용 HTS의 **종목별 매수 한도 / 관심종목 투자비중** 에 해당.
| 컬럼 | 타입 | 설명 |
|------|------|------|
| `id` | BIGINT PK | |
| `account_id` | BIGINT FK | |
| `symbol` | VARCHAR(50) | KRW-BTC 등 |
| `korean_name` | VARCHAR(100) | |
| `max_invest_krw` | DECIMAL(20,2) | 종목 최대 투자금(상한) |
| `budget_pct` | DECIMAL(8,4) NULL | 총자산 대비 % (max와 택1 또는 min 적용) |
| `strategy_id` | BIGINT NULL | 연결 전략(가상투자 카드와 동기) |
| `is_active` | TINYINT(1) | 0이면 신규 매수 차단 |
| `pinned` | TINYINT(1) | 관심 고정 |
| `sort_order` | INT | UI 정렬 |
| `created_at` / `updated_at` | | |
**UK**: `(account_id, symbol)`
**파생(체결·보유 기준 계산)**
| 필드 | 계산 |
|------|------|
| `invested_krw` | 보유 평가금 또는 `quantity × avg_price` |
| `available_buy_krw` | `max_invest_krw` `invested_krw` (음수면 0) |
| `symbol_return_pct` | (평가 원가) / 원가 × 100 |
| `weight_pct` | `eval_amount` / `total_asset` × 100 |
#### (기존 유지) `gc_paper_position`
| 컬럼 | 비고 |
|------|------|
| `quantity`, `avg_price` | 평단가 가중평균 유지 (기존 로직) |
| (선택) `first_buy_at` | 최초 매수 시각 — 통계용 |
#### (기존 확장) `gc_paper_trade`
| 컬럼 | 타입 | 설명 |
|------|------|------|
| 기존 컬럼 | | |
| `realized_pnl_delta` | DECIMAL(20,2) NULL **NEW** | 매도 시 해당 체결 실현손익 |
| `position_qty_after` | DECIMAL(30,12) NULL **NEW** | 체결 후 잔량 |
| `note` | VARCHAR(200) NULL **NEW** | 메모 |
**인덱스 추가**: `(account_id, symbol, created_at)`, `(account_id, created_at DESC)`
#### (신규) `gc_paper_cash_ledger` — 자금 변동 원장
| 컬럼 | 타입 | 설명 |
|------|------|------|
| `id` | BIGINT PK | |
| `account_id` | BIGINT FK | |
| `entry_type` | VARCHAR(30) | 아래 enum |
| `amount` | DECIMAL(20,2) | +입금 / −출금 |
| `cash_before` | DECIMAL(20,2) | |
| `cash_after` | DECIMAL(20,2) | |
| `ref_trade_id` | BIGINT NULL | 체결 연결 |
| `symbol` | VARCHAR(50) NULL | |
| `created_at` | DATETIME | |
**`entry_type` enum**
- `INITIAL` — 계좌 생성·초기화 시 초기 예탁
- `BUY_SETTLE` — 매수 결제
- `SELL_SETTLE` — 매도 입금
- `FEE` — 수수료 (체결에 포함 시 생략 가능)
- `ADJUST` — 관리자/시스템 조정
- `RESET` — 초기화 전 스냅샷 역산용
#### (신규) `gc_paper_daily_snapshot` — 일별 자산 (수익률 차트)
| 컬럼 | 타입 | 설명 |
|------|------|------|
| `id` | BIGINT PK | |
| `account_id` | BIGINT FK | |
| `snapshot_date` | DATE | KST 기준 |
| `cash_balance` | DECIMAL(20,2) | |
| `stock_eval_amount` | DECIMAL(20,2) | EOD 시세 기준 |
| `total_asset` | DECIMAL(20,2) | |
| `daily_pnl` | DECIMAL(20,2) | 전일 대비 |
| `daily_return_pct` | DECIMAL(10,4) | |
| `cumulative_return_pct` | DECIMAL(10,4) | 초기자본 대비 |
**UK**: `(account_id, snapshot_date)` — 스케줄러: 매일 00:05 KST 또는 마지막 틱 후
#### (신규) `gc_paper_reset_log` — 초기화 이력
| 컬럼 | 설명 |
|------|------|
| `account_id`, `reset_at`, `initial_capital`, `cash_before`, `positions_json`, `reason` | 감사·복구 |
#### (Phase 2, 선택) `gc_paper_order` — 미체결 주문
지정가·예약 매수 등 확장 시. 현재는 **즉시 체결** 유지하고 Phase 2로 미룸.
### 4.3 전역 설정 (`gc_app_settings` — 기존 유지)
| 컬럼 | 용도 |
|------|------|
| `paper_trading_enabled` | 모의투자 마스터 스위치 |
| `paper_initial_capital` | 초기화·신규 계좌 기본 예탁금 |
| `paper_fee_rate_pct` / `paper_slippage_pct` / `paper_min_order_krw` | |
| `paper_auto_trade_enabled` / `paper_auto_trade_budget_pct` | **전역** 자동매수 시 가용현금 % |
**규칙 우선순위 (매수 가능 금액)**
```
min(
orderable_cash,
orderable_cash × paper_auto_trade_budget_pct / 100, -- 자동매매 시
symbol.available_buy_krw -- 종목 한도
)
```
### 4.4 마이그레이션 제안
| 파일 | 내용 |
|------|------|
| `V51__paper_investment_management.sql` | account 확장, symbol_allocation, cash_ledger, daily_snapshot, reset_log, trade 인덱스 |
| 데이터 백필 | 기존 account → `initial_capital_snapshot` = app.initial_capital; allocation은 보유 종목만 max=초기자본으로 seed |
---
## 5. 화면 설계
### 5.1 메뉴·정보 구조
```
설정
└── [모의투자 설정] ← 투자금·규칙·초기화 (관리자/최초 설정 성격)
모의투자 (Paper Trading) ← 투자금 관리·운영 허브
├── [투자금 관리] (기본) ← 계좌 요약·보유·종목별 수익
├── [거래/주문] ← 매수·매도 패널 (기존 우측)
├── [거래내역] ← 체결·필터
└── [성과/분석] ← MDD·샤프·자산추이 (기존 차트 강화)
가상투자 (Virtual) ← 전략 모니터 (체결은 동일 paper API)
```
**역할 분리 원칙**
| 화면 | 사용자 질문 |
|------|-------------|
| **투자금 설정** | “얼마로 시작하고, 수수료·자동매매·종목당 한도는?” |
| **투자금 관리** | “지금 얼마 벌었고, 종목별로 얼마 묶여 있나?” |
| **거래/주문** | “이 종목 사고/팔려면?” |
### 5.2 투자금 설정 화면 (`PaperInvestmentSettingsPage` 또는 설정 탭 통합)
**레이아웃 (1열 폼 + 위험 작업 하단)**
```
┌─────────────────────────────────────────┐
│ 모의투자 설정 │
├─────────────────────────────────────────┤
│ [ON] 모의투자 사용 │
│ 초기 자본 (KRW) [ 10,000,000 ] │
│ 수수료 (%) [ 0.05 ] │
│ 슬리피지 (%) [ 0.00 ] │
│ 최소 주문 (KRW) [ 5,000 ] │
├─────────────────────────────────────────┤
│ 자동매매 │
│ [ON] 전략 시그널 자동 체결 │
│ 매수 예산 (가용현금 %) [ 95 ] │
├─────────────────────────────────────────┤
│ 기본 종목 한도 (신규 종목 추가 시) │
│ ○ 총자산의 [ 20 ] % ○ 고정 [ 2,000,000]│
├─────────────────────────────────────────┤
│ [ 계좌 초기화 ] (confirm + reset_log) │
└─────────────────────────────────────────┘
```
- **저장**: `gc_app_settings` + (선택) 계좌 `initial_capital_snapshot` 동기
- **초기화**: `POST /paper/reset` + `gc_paper_reset_log` + ledger `RESET`
### 5.3 투자금 관리 화면 (`PaperInvestmentManage` — 모의투자 메인 상단/탭)
**상용 HTS “계좌평가 / 잔고” 패널에 해당**
```
┌──────────────────────────────────────────────────────────────┐
│ 총자산 ₩12,345,678 │ 수익률 +12.3% │ 평가손익 +234,567 │
│ 예수금 ₩3,000,000 │ 실현손익 +50,000 │ 주문가능 ₩2,850,000 │
└──────────────────────────────────────────────────────────────┘
┌─ 보유 종목 (종목별 투자금·수익률) ─────────────────────────────┐
│ 종목 │ 비중 │ 투자한도 │ 투입금 │ 평가 │ 수익률 │
│ BTC │ 35% │ 5,000,000 │ 4.2M │ 4.5M │ +7.1% │
│ ETH │ 20% │ 3,000,000 │ 2.8M │ 2.5M │ -10.7% │
│ [+ 종목 추가] 한도·전략 연결 │
└──────────────────────────────────────────────────────────────┘
```
**행 동작**
- 행 클릭 → 차트/주문 패널 종목 전환
- 한도 편집 → `PATCH /paper/allocations/{symbol}`
- 비활성 토글 → 해당 종목 신규 매수 차단
### 5.4 모의투자 대시보드 (기존 `PaperTradingPage` 개편)
```
┌──────────┬─────────────────────────────┬──────────────┐
│ 좌측 │ 중앙 │ 우측 │
│ · 전략 │ [투자금 관리] [성과차트] │ 매수/매도 │
│ · 설정 │ 보유·KPI │ 호가/체결 │
└──────────┴─────────────────────────────┴──────────────┘
```
- 좌측 **설정** → §5.2 로 링크 또는 임베드
- 중앙 **투자금 관리** → §5.3
- 우측 **TradeOrderPanel** — 종목별 `available_buy_krw` 표시
### 5.5 거래내역 화면
| 컬럼 | 설명 |
|------|------|
| 체결시각 | |
| 종목 | |
| 구분 | 매수(빨강) / 매도(파랑) — `--gc-trade-buy/sell` |
| 가격·수량·금액 | |
| 수수료 | |
| 실현손익 | 매도 행만 |
| 출처 | MANUAL / STRATEGY |
| 체결 후 예수금 | `cash_after` |
**필터**: 기간, 종목, side, source · **페이징** API
### 5.6 가상투자와의 동기화
| 가상투자 UI | DB 매핑 |
|-------------|---------|
| 투자대상 목록 | `gc_paper_symbol_allocation` (symbol, strategy_id, pinned) |
| 카드 매수/매도 | `/paper/orders` + 한도 검증 |
| 자동매매 | `paper_auto_trade_*` + 종목 `max_invest_krw` |
**권장**: 가상투자 `targets` 저장 시 백엔드 `PUT /paper/allocations/bulk` 로 동기화 (localStorage는 캐시만).
---
## 6. 기능 설계
### 6.1 API (신규·확장)
| 메서드 | 경로 | 설명 |
|--------|------|------|
| GET | `/paper/summary` | 기존 + `orderableCash`, `initialCapitalSnapshot`, `allocations[]` |
| GET | `/paper/allocations` | 종목별 한도·투입·가용·수익률 |
| PUT | `/paper/allocations` | bulk upsert (가상투자 동기) |
| PATCH | `/paper/allocations/{symbol}` | 단일 종목 한도 수정 |
| GET | `/paper/trades` | `?symbol=&side=&from=&to=&page=` |
| GET | `/paper/ledger` | 자금 원장 |
| GET | `/paper/snapshots/daily` | 자산 추이 |
| POST | `/paper/orders` | **한도·orderable 검증** 추가 |
| POST | `/paper/reset` | reset_log + ledger + allocation 정책에 따라 재생성 |
### 6.2 매수 검증 플로우
```mermaid
sequenceDiagram
participant UI as 주문 UI
participant API as PaperTradingService
participant DB as gc_paper_*
UI->>API: POST /paper/orders BUY
API->>DB: load account + allocation + position
API->>API: needKrw = price×qty + fee
alt needKrw > orderable_cash
API-->>UI: 400 가용현금 부족
else needKrw > available_buy_krw
API-->>UI: 400 종목 투자한도 초과
else
API->>DB: trade + position + ledger
API-->>UI: PaperTradeDto
end
```
### 6.3 매도 검증
- 보유 `quantity` ≥ 매도 수량
- 전량 매도 시 `realized_pnl_delta` 계산 → `account.realized_pnl` 누적
- (선택) 매도 후에도 allocation 행 유지, `invested_krw`=0
### 6.4 일별 스냅샷 배치
- 대상: `paper_trading_enabled` 이고 당일 체결/시세 변동 있는 계좌
- 입력: EOD mark prices (Ta4jStorage 또는 REST)
- UPSERT `gc_paper_daily_snapshot`
### 6.5 종목별 수익률 정의 (화면 표기 통일)
| 지표 | 정의 |
|------|------|
| **종목 평가수익률** | (mark×qty avg×qty) / (avg×qty) × 100 |
| **종목 실현손익** | 해당 종목 매도 체결의 `realized_pnl_delta` 합 |
| **계좌 총수익률** | (total_asset initial_capital_snapshot) / initial × 100 |
| **당일 수익률** | daily_snapshot.daily_return_pct |
---
## 7. 서비스·모듈 구조 (백엔드)
```
PaperTradingController
PaperTradingService ← 체결·잔고 (기존)
PaperAllocationService ← 종목별 한도 CRUD·검증
PaperLedgerService ← 원장 기록
PaperSnapshotService ← 일별 스냅샷·조회
PaperAnalyticsService ← MDD/샤프 (체결·스냅샷 기반, 기존 paperMetrics 이관 가능)
```
---
## 8. 구현 단계 (로드맵)
| 단계 | 범위 | 산출물 |
|------|------|--------|
| **P0** | DB V51 + allocation CRUD + 매수 한도 검증 | 마이그레이션, API, 단위 테스트 |
| **P1** | 투자금 관리 UI (보유+한도+KPI) | PaperTradingPage 탭, bulk sync 가상투자 |
| **P2** | cash_ledger + 거래내역 필터/페이징 | 원장 화면 |
| **P3** | daily_snapshot + 자산추이 차트 | 스케줄러 |
| **P4** | (선택) 미체결 주문 | gc_paper_order |
---
## 9. goldenChart 적용 시 유의사항
1. **단일 계좌**: `device_id` 기준 1계좌 유지 (상용 모의도 대부분 1:1).
2. **평가 시세**: summary는 `POST /paper/summary` + markPrices 패턴 유지.
3. **색상**: 매수 `#ef5350`, 매도 `#4dabf7` (`--gc-trade-buy/sell`).
4. **전략 평가와 분리**: Ta4j 시그널은 `gc_paper_*` 체결만 변경; 전략 DSL 평가는 기존 백엔드 단일 경로 유지.
5. **설정 이중화 해소**: `SettingsPage` 가상매매 = 전역 기본값, 모의투자 좌측 설정 = 동일 API 바인딩 또는 “설정으로 이동” 링크만.
---
## 10. 요약
| 계층 | 설계 요지 |
|------|-----------|
| **DB** | 계좌 + **종목별 allocation** + **cash 원장** + **일별 스냅샷** + 체결 확장 |
| **투자금 설정** | 전역 규칙·초기자본·초기화 (app_settings + reset_log) |
| **투자금 관리** | 총자산·예수금·종목별 한도/투입/수익률·비중 |
| **매수/매도** | 가용현금 ∧ 종목한도 ∧ (자동매매 %) 검증 후 체결 |
| **거래내역** | paper_trade + ledger, 필터·페이징 |
현재 V18 스키마는 **MVP 체결·보유** 에 적합하고, 상용 수준의 **종목별 투자금·원장·일별 성과** 는 §4.4 `V51` 마이그레이션과 §8 P0~P3 순으로 확장하는 것을 권장합니다.