Skip to content

Latest commit

 

History

History
494 lines (339 loc) · 20.3 KB

File metadata and controls

494 lines (339 loc) · 20.3 KB

주식 시뮬레이터

브라우저에서 동작하는 정적 웹 기반 주식 시뮬레이터입니다. 실제 증권사 WTS 화면처럼 감시종목, 시세, 호가, 체결, 차트, 주문 티켓, 자산 현황을 한 화면에 배치했고, 국내장과 미국장을 번갈아 운영하는 게임형 거래 경험을 제공합니다.

  • 배포 URL: https://developlee20.github.io/
  • 현재 기준 엔트리 포인트: index.html
  • 현재 기준 런타임: js/main.js에서 ES module 방식으로 각 기능 모듈을 로드
  • 프레임워크/빌드 도구: 없음. 순수 HTML/CSS/Vanilla JavaScript

이 문서의 목적

이 README는 새로운 개발자가 프로젝트에 바로 합류할 수 있도록 아래 내용을 빠르게 이해하게 만드는 온보딩 문서입니다.

  • 이 프로젝트가 무엇인지
  • 실제로 어떤 기능이 구현되어 있는지
  • 어떤 파일이 현재 사용 중인지
  • 상태 저장과 시뮬레이션이 어떻게 돌아가는지
  • 어디를 수정하면 어떤 기능에 영향이 가는지

중요: 이 문서는 index.html + js/main.js 경로를 기준으로 작성했습니다. index_old.html, index.html.backup, js/ai-trader.js, js/ai-trader-manager.js는 현재 메인 런타임에서 사용되지 않는 레거시/보조 자산입니다.

프로젝트 한눈에 보기

항목 내용
제품 성격 게임형 주식 거래 시뮬레이터
실행 환경 브라우저
배포 방식 GitHub Pages
기술 스택 HTML, CSS, Vanilla JS, Canvas, localStorage
핵심 도메인 종목 생성, 시장 개폐, 주문장, 가상 트레이더, 현물/레버리지/숏, 환전, 차트
데이터 저장 브라우저 localStorage
서버 연동 없음
테스트 인프라 없음

빠른 시작

1. 로컬 실행

이 프로젝트는 빌드가 필요 없는 정적 사이트입니다. 다만 type="module"을 사용하므로 브라우저에서 파일을 더블클릭해 여는 방식보다 간단한 정적 서버로 실행하는 편이 안전합니다.

예시:

# Python이 있을 때
python -m http.server 4173

또는

# Node가 있을 때
npx serve .

그 다음 브라우저에서 명령이 출력한 로컬 주소로 접속하면 됩니다. 예를 들어 Python 예시는 http://localhost:4173입니다.

2. 디버깅 시 꼭 알아둘 점

  • 상태는 localStorage에 저장됩니다.
  • 브라우저를 새로고침해도 자산, 종목, 가상 트레이더 상태가 유지됩니다.
  • 데이터 모델을 바꾼 뒤 이상 동작이 보이면 브라우저 스토리지를 비우거나 화면 우측 상단의 인생 리셋 버튼으로 초기화해야 합니다.

현재 제품이 제공하는 사용자 경험

사용자는 다음 흐름으로 플레이합니다.

  1. 국내/미국 감시종목 중 하나를 선택합니다.
  2. 선택 종목의 현재가, 직전 변동률, 시가총액, 스프레드, 최근 체결량, 내 보유 상태를 확인합니다.
  3. 호가창과 최근 체결을 보면서 현물 주문 또는 레버리지/숏 포지션에 진입합니다.
  4. 원화/달러를 환전해 시장별 예수금을 조정합니다.
  5. 보유 현물, 레버리지 포지션, 숏 포지션의 손익을 실시간으로 확인합니다.
  6. 가상 트레이더 랭킹을 보며 시장 상황을 간접적으로 파악합니다.
  7. 게임 오버 UI와 재시작 흐름은 존재하지만, 현재 코드에서는 자동 게임 오버 판정이 메인 루프에 연결되어 있지 않습니다.

핵심 기능 명세

1. 시장 운영 규칙

이 프로젝트의 시장 시간은 실제 한국/미국 증시 시간과 연결되어 있지 않습니다. 사용자의 브라우저 현재 시간을 기반으로 작동하는 게임 규칙입니다.

규칙 구현 방식
시간 기준 브라우저의 new Date()
국내장 개장 현재 분(minute)이 10분 주기 중 0~4분일 때
미국장 개장 현재 분(minute)이 10분 주기 중 5~9분일 때
상태 체크 주기 1초마다
시세 시뮬레이션 주기 시장이 열려 있는 동안 2초마다
환율 변동 주기 5초마다

해석하면 다음과 같습니다.

  • 매 10분 사이클에서 앞 5분은 국내장, 뒤 5분은 미국장이 열립니다.
  • 두 시장은 번갈아 열리며, 각각 개장/폐장 상태를 별도로 유지합니다.
  • 헤더에는 현재 개장 여부와 함께 이번 5분 변동 또는 직전 5분 변동이 표시됩니다.

2. 종목 생성 규칙

초기 종목

  • 게임 시작 시 국내 10종목, 미국 10종목을 생성합니다.
  • 종목명은 코드에 하드코딩된 가상 이름 목록에서 중복 없이 선택됩니다.

가격/수량 초기화 규칙

항목 국내 주식 미국 주식
초기 가격 범위 1,000원 ~ 100,000원 약 $10 ~ $59
호가 단위 100원 $0.01
총 발행 주식 수 1,000주 ~ 10,000주 1,000주 ~ 10,000주

추가 상장

  • 국내 종목은 15분마다 신규 종목 생성 시도를 합니다.
  • 미국 종목은 20분마다 신규 종목 생성 시도를 합니다.
  • 단, 이름 목록이 소진되면 더 이상 생성되지 않습니다.

현재 코드 기준 상장 가능 이름 수:

  • 국내: 10개
  • 미국: 11개

즉, 기본 시작에서 국내는 사실상 이미 최대치이고, 미국은 추가로 1종목 더 상장될 여지가 있습니다.

3. 가격 이력과 차트

  • 모든 종목은 priceHistory 배열을 가집니다.
  • 차트는 Canvas로 직접 그립니다.
  • 소형 차트는 본문 중앙 패널에 노출됩니다.
  • 상세 차트는 모달에서 별도로 렌더링됩니다.
  • 가격 이력은 최대 100개 포인트까지만 유지합니다.
  • 차트 색상 팔레트는 다크 모드 여부에 따라 바뀝니다.

차트는 최근 체결가 흐름을 보여주는 성격이며, 별도 라이브러리를 사용하지 않습니다.

4. 주문장과 체결 시스템

이 프로젝트의 핵심은 단순 랜덤 가격 변동이 아니라, 가상 트레이더가 만든 주문장을 바탕으로 체결이 발생한다는 점입니다.

구조

  • 주문장은 orderBook[marketId][stockId] 구조를 사용합니다.
  • 체결 내역은 tradeHistory[marketId][stockId]에 저장합니다.
  • 주문 정렬 원칙은 가격 우선 -> 시간 우선입니다.

현재 런타임에서의 실제 동작

  • 시장이 열려 있는 동안 2초마다 한 번씩 새 틱을 계산합니다.
  • 매 틱마다 해당 시장의 주문장을 비우고 다시 만듭니다.
  • 각 종목마다 가상 트레이더 주문을 새로 생성합니다.
  • 생성된 주문끼리 먼저 매칭합니다.
  • 그 결과로 최신 가격, 체결량, 체결 횟수, 최근 체결 기록이 갱신됩니다.

중요: 현재 구현은 "지속적으로 쌓이는 영속 주문장"보다는 "틱 단위로 재구성되는 스냅샷성 주문장"에 가깝습니다. 즉, 가상 트레이더의 미체결 지정가 주문이 여러 틱에 걸쳐 유지되는 구조가 아닙니다.

플레이어 주문 방식

  • 현재 UI에서 플레이어는 시장가 방식만 사용합니다.
  • 플레이어 매수는 최우선 매도호가부터 순차 체결됩니다.
  • 플레이어 매도는 최우선 매수호가부터 순차 체결됩니다.
  • 호가 수량이 부족하면 부분 체결이 가능합니다.
  • 체결 가능한 수량이 전혀 없으면 실패 메시지를 표시합니다.

화면에 보이는 주문 데이터

  • 호가창은 가격대별로 수량을 합산해 보여줍니다.
  • 선택 종목 기준 매도/매수 각 6호가를 표시합니다.
  • 최근 체결은 최대 8건을 표시합니다.
  • 플레이어가 참여한 체결은 최근 체결 목록에서 강조 표시됩니다.

5. 가상 트레이더 시뮬레이션

현재 메인 런타임은 js/market-simulator.js를 사용합니다. 이름은 "시장 시뮬레이터"지만 실제 역할은 아래와 같습니다.

  • 100명의 가상 트레이더 생성
  • 트레이더별 전략/관심시장/초기 예수금 부여
  • 종목별 초기 재고 배분
  • 틱마다 지정가 주문 생성
  • 주문 체결 후 자산과 랭킹 갱신

트레이더 수와 전략 구성

전략 인원 설명
market_maker 12명 양방향 호가를 제시해 유동성 공급
momentum 24명 최근 흐름 추종
value 24명 이동평균 대비 저평가/고평가 반응
contrarian 20명 최근 흐름과 반대로 반응
swing 20명 더 큰 폭의 방향성 주문

트레이더 자금

  • 기준 자산은 1인당 1,000,000 KRW 상당입니다.
  • 국내 전용, 미국 전용, 양시장 관심 트레이더에 따라 KRW/USD 비중이 다르게 배분됩니다.

틱당 주문 생성 개요

  • 각 종목마다 마켓메이커 일부와 일반 트레이더 일부가 샘플링됩니다.
  • 마켓메이커는 매수/매도 양쪽 주문을 동시에 제시합니다.
  • 일반 트레이더는 모멘텀, 평균회귀, 역추세 신호를 바탕으로 한 방향 주문을 냅니다.
  • 주문 가격은 시장별 호가 단위에 맞춰 반올림됩니다.

랭킹

  • 랭킹은 트레이더 총 자산을 KRW로 환산해 계산합니다.
  • 수익률은 각 트레이더의 초기 자산 대비 현재 자산 기준입니다.
  • 내부 구현은 최대 18명까지 반환할 수 있지만, 현재 UI는 상위 12명만 보여줍니다.

6. 플레이어 자산/포트폴리오

시작 자금

  • 원화 예수금: ₩1,000,000
  • 달러 예수금: $0.00

보유 자산 종류

  • 현물 보유 종목
  • 레버리지 롱 포지션
  • 숏 포지션
  • 원화 예수금
  • 달러 예수금

총 자산 계산 방식

총 자산은 항상 원화 기준으로 계산합니다.

  • 원화 예수금은 그대로 더합니다.
  • 국내 현물/포지션 가치는 원화로 합산합니다.
  • 미국 현물/포지션 및 달러 예수금은 현재 환율로 원화 환산 후 합산합니다.

7. 현물 주문 명세

매수

  • 선택 종목 기준 수량 입력 후 현물 매수
  • 현재 시장이 열려 있어야 함
  • 해당 시장 통화 예수금이 충분해야 함
  • 주문장 매도호가가 있어야 체결 가능
  • 평균 체결가, 부분 체결 여부를 토스트로 표시

매도

  • 선택 종목 기준 수량 입력 후 현물 매도
  • 보유 수량보다 많이 팔 수 없음
  • 주문장 매수호가가 있어야 체결 가능
  • 부분 체결 가능

편의 기능

  • 1주/10주/100주 빠른 입력 버튼
  • 최대 버튼
    • 해당 종목 보유 수량이 있으면 보유 수량을 우선 사용
    • 보유가 없으면 현재 예수금으로 살 수 있는 최대 수량을 계산

8. 레버리지 롱/숏 포지션 명세

현재 UI에서 체크박스를 켜면 현물 버튼과 별개로 레버리지/숏 진입 버튼을 사용할 수 있습니다.

레버리지 롱

  • 배율: 2x, 3x, 4x
  • 필요 자기자본: 총 매수금액 / 레버리지
  • 수수료: 자기자본의 1%
  • 차입금: 총 매수금액 - 자기자본
  • 청산가: 자기자본의 90%를 잃는 가격 기준

  • 증거금: 총 포지션 가치의 50%
  • 수수료: 증거금의 1%
  • 청산가: 증거금의 90%를 잃는 가격 기준

청산 처리

  • 시장 틱마다 자동 청산 여부를 검사합니다.
  • 청산 조건을 충족하면 포지션은 자동 제거되고, 남은 가치만 예수금으로 환원됩니다.
  • 자동 청산 결과는 토스트로 알립니다.

포지션 화면

  • 레버리지 포지션 영역에서 각 포지션의 현재 가치, 손익, 청산가를 확인 가능
  • 숏 포지션 영역에서 동일하게 확인 가능
  • 각 포지션마다 수동 청산 버튼 제공

9. 환전 기능 명세

기본 규칙

  • 시작 환율: 1 USD = 1300 KRW
  • 환율 변동 주기: 5초
  • 변동 폭: 대략 ±0.5%
  • 환전 수수료: 1%

입력값 의미

현재 입력 필드 환전 금액 (USD)는 양방향 모두 "달러 기준 수량"을 의미합니다.

  • 원화 → 달러: 입력한 달러 수량을 사기 위해 필요한 원화를 차감
  • 달러 → 원화: 입력한 달러 수량만큼 팔아서 원화를 수령

화면 표시

  • 현재 환율 표시
  • 직전 환율 대비 상승/하락/보합 배지 표시

10. UI/UX 기능 명세

메인 레이아웃

화면은 크게 세 컬럼으로 구성됩니다.

영역 역할
왼쪽 감시종목, 시장 탭, 가상 트레이더 랭킹
가운데 선택 종목 요약, 현재가, 요약 카드, 실시간 차트
오른쪽 호가/체결, 주문 티켓, 자산/포지션/환전

보조 기능

  • 다크 모드 토글
  • 폰트 크기 순환 변경
  • 인생 리셋
  • 차트 확대 모달
  • 반응형 레이아웃

반응형

  • 1180px 이하에서 1열 스택 구조로 전환
  • 900px 이하에서 카드와 그리드가 더 단순한 1열 구조로 전환
  • 640px 이하에서는 버튼과 상단 컨트롤이 모바일 레이아웃으로 재배치

11. 게임 종료와 초기화

게임 오버

  • 게임 오버 오버레이 UI와 다시 시작 버튼은 구현되어 있습니다.
  • handleGameOver() 보조 함수도 존재합니다.
  • 다만 현재 main.js의 메인 루프에서 이 함수가 실제로 호출되지 않아, 자동 게임 오버는 의도 대비 미연결 상태입니다.

다시 시작

  • 게임 오버 모달의 다시 시작은 런타임 상태를 초기화하고 새 판을 시작합니다.
  • 종목, 포지션, 시장 시뮬레이터 상태를 재구성합니다.

인생 리셋

  • 우측 상단 인생 리셋localStorage 전체를 비우고 페이지를 다시 로드합니다.
  • 저장된 게임 상태, 시장 상태, 다크 모드, 폰트 크기 설정까지 모두 삭제됩니다.

상태 저장 명세

localStorage 키

역할
stockGameState 플레이어 자산, 포지션, 시장 상태, 종목 데이터 저장
stockGameRealisticMarketState 가상 트레이더 상태 저장
darkMode 다크 모드 설정 저장
fontSizeIndex 폰트 크기 설정 저장

저장 시점

  • 페이지를 닫거나 새로고침하기 직전 beforeunload에서 저장
  • 다크 모드/폰트 크기 변경 시 즉시 저장

호환성/마이그레이션 메모

  • game-state.js에는 과거 버전 cash가 숫자였던 저장 데이터를 보정하는 로직이 들어 있습니다.
  • market-simulator.js는 별도의 상태 버전(MARKET_STATE_VERSION = 2)을 사용합니다.
  • 저장 구조를 바꾸면 해당 버전과 로드 로직을 함께 수정하는 것이 안전합니다.

파일 구조와 책임

활성 런타임 파일

파일 역할
index.html 실제 엔트리 HTML. 전체 레이아웃과 버튼/패널 마크업 담당
styles.css 전체 UI 스타일, 다크 모드, 반응형 레이아웃
js/main.js 앱 초기화, 타이머, 시장 개폐, 전역 이벤트 연결, 게임 라이프사이클
js/game-state.js 플레이어 자산, 포지션, 환율, 저장/불러오기
js/stock.js 종목 생성, 메타데이터, 현물/레버리지/숏 계산
js/market-simulator.js 가상 트레이더, 틱 시뮬레이션, 주문 생성/체결, 랭킹
js/order-book.js 주문장 자료구조, 체결 기록, 호가/체결 조회 유틸
js/ui.js DOM 렌더링, 선택 종목 패널, 포지션 목록, 랭킹, 토스트
js/chart.js Canvas 차트 및 상세 모달 렌더링
js/settings.js 다크 모드/폰트 크기 설정

레거시 또는 현재 미사용 파일

파일 상태
js/ai-trader.js 예전 AI 트레이더 구현. 현재 main.js에서 import하지 않음
js/ai-trader-manager.js 예전 AI 트레이더 풀 관리 코드. 현재 메인 런타임 미사용
index_old.html 과거 단일 HTML 기반 구현 백업
index.html.backup 과거 HTML 백업

개발자가 가장 먼저 이해해야 할 구현 포인트

1. 이 프로젝트는 서버가 없다

  • 모든 상태는 브라우저 메모리 + localStorage에만 존재합니다.
  • API 호출이나 백엔드 동기화는 없습니다.

2. HTML은 인라인 onclick 패턴을 사용한다

현재 화면 버튼 다수는 index.html에서 직접 onclick="..." 형태로 연결되어 있습니다. 그래서 js/main.js 마지막에서 여러 함수를 window.someFunction = ... 형태로 노출합니다.

새 인터랙션을 추가할 때는 두 가지 중 하나를 선택해야 합니다.

  • 기존 패턴을 유지해서 window에 함수 노출
  • 또는 ui.js/main.js에서 직접 이벤트 리스너를 바인딩

둘을 섞더라도 일관성을 유지하는 것이 중요합니다.

3. 화면 렌더링은 ui.js가 중심이다

상태가 바뀌었을 때 보통 아래 조합이 호출됩니다.

  • renderStocks()
  • updatePlayerInfo()
  • refreshSelectedStockPanel()
  • updateChartIfOpen()

새 상태 필드를 추가하면 거의 항상 game-state.jsui.js를 함께 수정하게 됩니다.

4. 틱 기반 시장 재생성 구조를 이해해야 한다

현재 시장은 실시간 누적 주문장이 아니라 "매 틱마다 새로 빌드하는 가상 시장"입니다. 이 구조를 바꾸면 아래 모듈이 함께 영향을 받습니다.

  • js/main.js
  • js/market-simulator.js
  • js/order-book.js
  • js/ui.js

5. 저장 구조를 바꾸면 로드 로직도 같이 바꿔야 한다

아래 둘은 항상 세트로 봐야 합니다.

  • saveGameState() / loadGameState()
  • saveMarketSimulationState() / loadMarketSimulationState()

로컬 스토리지 구조를 깨면 이전 저장 데이터와 충돌할 수 있습니다.

기능 추가 시 추천 수정 지점

작업 유형 우선 확인할 파일
새 버튼/패널 추가 index.html, styles.css, js/ui.js, js/main.js
자산 계산 규칙 변경 js/game-state.js, js/stock.js, js/ui.js
신규 주문 타입 추가 js/order-book.js, js/market-simulator.js, js/main.js, js/ui.js
시장 운영 시간 변경 js/main.js
종목 생성 규칙 변경 js/stock.js, js/main.js
랭킹 규칙 변경 js/market-simulator.js, js/ui.js
차트 표현 변경 js/chart.js, styles.css
저장 포맷 변경 js/game-state.js, js/market-simulator.js

현재 코드 기준의 제약과 주의사항

구현상 제약

  • 자동화 테스트가 없습니다.
  • 타입 시스템이 없습니다.
  • 패키지 매니저와 빌드 단계가 없습니다.
  • 플레이어 지정가 주문/예약 주문 UI는 없습니다.
  • 게임 오버 자동 판정은 UI/함수는 있으나 현재 메인 루프와 연결되어 있지 않습니다.
  • 레거시 파일이 저장소에 남아 있어 현재 경로와 혼동할 수 있습니다.

실무적으로 주의할 점

  • 브라우저 캐시와 localStorage 때문에 "수정했는데 이상하게 예전 상태처럼 보이는" 상황이 자주 생길 수 있습니다.
  • ES module 파일명이 바뀌면 index.html과 import 경로를 함께 수정해야 합니다.
  • 종목/포지션 구조를 바꾸면 저장된 예전 데이터와 충돌할 수 있습니다.

추천 온보딩 순서

새로 합류한 개발자에게는 아래 순서를 권장합니다.

  1. index.html로 화면 전체 구성을 확인합니다.
  2. js/main.js에서 앱 초기화와 틱 루프를 읽습니다.
  3. js/game-state.jsjs/stock.js로 도메인 모델을 이해합니다.
  4. js/market-simulator.js로 주문장/체결/랭킹 흐름을 이해합니다.
  5. js/ui.jsjs/chart.js로 렌더링 구조를 확인합니다.
  6. 마지막으로 styles.css에서 레이아웃과 반응형 규칙을 봅니다.

요약

이 프로젝트는 "백엔드 없는 정적 웹 트레이딩 시뮬레이터"입니다. 현재 구현의 핵심은 다음 세 가지입니다.

  • 국내장/미국장을 번갈아 여는 시간 기반 게임 구조
  • 100명의 가상 트레이더가 만드는 틱 기반 주문장/체결 시뮬레이션
  • 현물, 레버리지, 숏, 환전, 랭킹까지 한 화면에 묶은 WTS 스타일 UI

새 기능을 추가할 때는 먼저 "이 기능이 상태 변경인지, 시뮬레이션 규칙 변경인지, 렌더링 변경인지"를 구분하면 수정 범위를 빠르게 좁힐 수 있습니다.