| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | |
| 7 | 8 | 9 | 10 | 11 | 12 | 13 |
| 14 | 15 | 16 | 17 | 18 | 19 | 20 |
| 21 | 22 | 23 | 24 | 25 | 26 | 27 |
| 28 | 29 | 30 |
- 코틀린
- thread
- 상속
- 회고
- Til
- JVM
- Wil
- Security
- Kotlin
- Java
- 데드락
- 객체지향
- Python
- springboot
- TCP
- MySQL
- OOP
- netty
- Spring
- Rust
- 트랜잭션
- 2026-04
- 프록시
- react
- go
- 자바
- 스프링
- Spring Boot
- hikaricp
- 도커
- Today
- Total
hyuko
주린이를 위한 AI 주식 시뮬레이션 게임을 만들어봤다 본문
사이드 프로젝트로 실제 한국 주식 데이터 + AI 코칭이 붙은 모의투자 게임을 만든 이야기.
왜 만들었나
주식에 관심은 있는데 진짜 돈 넣기는 무서운 사람이 주변에 많았다. 증권사 모의투자는 계좌 개설부터 해야 하고, 실시간이라 결과 보려면 며칠을 기다려야 한다.
"과거 데이터로 시간을 넘기면서 연습할 수 있으면 어떨까?"에서 시작했다.
그리고 차트 보면 RSI니 MACD니 용어가 쏟아지는데, 이걸 주린이 눈높이에서 설명해주는 AI 코치가 있으면 학습 효과가 클 거라고 생각했다.
기술 스택
| 영역 | 기술 |
|---|---|
| Frontend | Next.js 15 (App Router), TanStack Query, Recharts, Tailwind CSS |
| Backend (게임 로직) | Spring Boot 3 + Kotlin, JPA, PostgreSQL |
| Backend (AI/수집) | FastAPI + Python, pandas-ta, Gemini 2.5 Flash |
| 데이터 소스 | FinanceDataReader (일봉), DART OpenAPI (공시), 네이버 검색 API (뉴스) |
| 인프라 | GCP VM (e2-standard-4), Docker Compose, Nginx + Let's Encrypt |
| IaC / CI/CD | Terraform, GitHub Actions (Workload Identity Federation) |
아키텍처
인터넷 (HTTPS)
↓
[Nginx :443] ← Let's Encrypt (Certbot 자동갱신)
↓
├── / → Next.js :3000 (SSR + SSG)
├── /api/v1/* → Spring Boot :8080 (Kotlin, 게임 로직)
└── /ai/* → FastAPI :8000 (Python, AI 코칭 + 데이터 수집)
↓
├── FinanceDataReader → 일봉 OHLCV
├── DART OpenAPI → 공시
├── 네이버 검색 API → 뉴스
└── Gemini 2.5 Flash → AI 코칭
↓
[PostgreSQL] [Redis]
3M+ 일봉 캐시
570K+ 공시





핵심 기능
시뮬레이션 모드
5년 전(2021-06)부터 시작해서 "다음 날" 버튼으로 하루씩 시간을 넘긴다. 미래 데이터는 볼 수 없고, 그 시점의 차트 + 지표 + 공시만 보고 매수/매도 판단을 해야 한다.
시뮬레이션 날짜 기준으로 종가 체결이 이루어지고, 포트폴리오 평가액도 해당 날짜 종가 기준으로 계산된다. 5일/1달 단위 점프도 가능해서 장기 투자 전략도 테스트할 수 있다.
AI 코칭 (Gemini 2.5 Flash)
종목의 기술적 지표(RSI, MACD, 볼린저 밴드, SMA)와 최근 DART 공시를 종합해서 Gemini API에 보내면, JSON 구조화된 코칭 응답이 온다.
{
"action": "BUY",
"confidence": 0.72,
"reasons": [
"RSI 28.5로 과매도 구간 진입",
"MACD 히스토그램 수축 중 — 하락 모멘텀 약화",
"2025-04-18 대량보유상황보고서 공시 — 기관 매수 신호"
],
"learnMore": { "indicator": "RSI", "explanation": "..." },
"riskWarning": "..."
}
핵심은 reasons에 구체적 숫자와 이벤트가 포함되도록 시스템 프롬프트를 설계한 것. "좋아 보입니다" 같은 애매한 답변이 아니라 "RSI가 28.5이고 공시가 이랬으니" 하는 근거를 보여줘야 교육적 가치가 있다.
response_mime_type: "application/json" + response_schema로 응답 구조를 강제하고, LLM이 실패하면 rule-based fallback으로 자동 전환된다. 리스크 경고 문구는 서버 사이드에서 강제 주입해서 프롬프트 인젝션으로도 빠지지 않게 했다.
데이터 수집 파이프라인
KRX 전 종목(2,879개)에 대해:
- 일봉: FinanceDataReader로 5년치 수집 → pandas-ta로 RSI/MACD/BB/SMA 계산 → PostgreSQL upsert
- 공시: DART OpenAPI로 5년치 공시 목록 수집 (corp_code 매핑 포함)
- 뉴스: 네이버 검색 API로 종목명 기반 뉴스 수집
전체 약 300만 일봉 + 57만 공시 레코드. 초기 대량 수집 시 GCP가 암호화폐 채굴로 오인해서 VM이 정지되는 사건이 있었다(...). pandas-ta의 CPU 집약적 계산이 원인이었는데, 종목 간 sleep과 배치 쿨다운을 추가해서 해결.
아키텍처 결정들
왜 Spring + FastAPI 이중 백엔드?
게임 로직(매매, 포트폴리오, 유저)은 트랜잭션과 타입 안전성이 중요해서 Kotlin + JPA. 데이터 수집과 AI 코칭은 pandas/Gemini 생태계가 Python이라 FastAPI. Spring이 FastAPI를 내부 HTTP로 호출하는 구조.
왜 Cloud SQL 안 쓰고 컨테이너 PostgreSQL?
비용. Cloud SQL db-f1-micro가 월 $10~15인데, 컨테이너 PostgreSQL은 VM 비용에 포함. 무료 크레딧 프로젝트라 비용 최소화가 우선이었다. 대신 매일 pg_dump → GCS 백업으로 데이터 유실 방지.
시뮬레이션 날짜 기반 매매
User.simulationDate 필드 하나로 전체 게임 상태를 제어한다. 매매 시 해당 날짜 종가로 체결, 포트폴리오 평가도 해당 날짜 기준. "다음 날" API 호출 시 주말(토/일) 건너뛰기 로직 포함.
포트폴리오 히스토리 차트는 거래 내역을 날짜별로 리플레이하면서 각 날짜의 종가로 평가액을 재계산하는 방식. TreeMap으로 가장 가까운 거래일 가격을 O(log n)에 조회.
인프라
Terraform으로 GCP VM, 고정 IP, Cloud DNS, Artifact Registry, IAM(Workload Identity Federation) 프로비저닝. GitHub Actions에서 main push 시 Docker 이미지 빌드 → Artifact Registry 푸시 → SSH로 VM에서 docker compose pull && up -d.
SA 키 파일 없이 WIF로 인증하는 게 보안상 깔끔했다.
[GitHub Actions] → WIF → [GCP Service Account] → Artifact Registry + SSH
Let's Encrypt 인증서는 certbot 컨테이너가 12시간마다 자동 갱신.
SEO
사이드 프로젝트를 만들고 아무도 안 쓰면 의미가 없어서, SEO에 신경 썼다.
- Next.js
generateMetadata로 종목별 동적 타이틀 ("삼성전자(005930) KOSPI 주식 시뮬레이션") opengraph-image.tsx로 종목별 OG 이미지 자동 생성 (카톡 공유 시 미리보기)sitemap.ts에서 2,879개 종목 페이지 + 가이드 6개 동적 생성- JSON-LD 구조화 데이터 (FinancialProduct 스키마)
- 가이드 콘텐츠: RSI/MACD/볼린저밴드/공시 해석 등 검색 유입용 글 6개
Google Search Console + 네이버 서치어드바이저 등록 + GA4 연동까지.
삽질 기록
- Next.js
NEXT_PUBLIC_*빌드 타임 이슈: Docker 이미지 빌드 시 환경변수가 주입되어야 하는데 런타임으로만 전달해서 프론트가localhost:8080을 호출하던 버그. Dockerfile ARG로 해결. - nginx DNS 캐시: Docker Compose에서 backend 컨테이너 재시작 시 IP가 바뀌는데 nginx가 캐시된 옛 IP로 접속해 502 발생.
resolver 127.0.0.11 valid=10s로 Docker 내장 DNS 사용 + deploy 후 nginx restart 자동화. - GCP 채굴 오탐: 2,879종목 × 5년 일봉을 한꺼번에 수집하면서 pandas-ta 지표 계산까지 돌리니 CPU가 치솟아서 GCP가 암호화폐 채굴로 판단. 이의신청 후 복구됐고, 종목 간 sleep + 배치 쿨다운 추가.
- pandas-ta Strategy API 제거:
pandas-ta0.4 버전에서ta.Strategy클래스가 제거됨. 개별df.ta.rsi(),df.ta.macd()호출로 대체. - Gemini 2.0 Flash → 2.5 Flash: 2026년 4월부터 Gemini 2.0 Flash가 무료 티어에서 제외. 모델명 변경 +
max_output_tokens확대(JSON 응답 중간 잘림 방지).