Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ DATABASE_USER=appuser
DATABASE_PASSWORD=appuser123
DATABASE_NAME=stock_data_cache
# DATABASE_URL이 있으면 위 개별 설정보다 우선한다.
# 운영 TLS: ssl_ca=/app/tls/oracle-mysql-ca.pem, ssl_check_hostname=false
DATABASE_URL=mysql+pymysql://appuser:appuser123@mysql:3306/stock_data_cache?charset=utf8mb4

# mysql 컨테이너 초기화용. 위 DATABASE_USER/DATABASE_PASSWORD와 일치시켜야 한다.
Expand Down
2 changes: 2 additions & 0 deletions .env.production.example
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,6 @@ SECRET_KEY=replace-on-server
NAVER_CLIENT_ID=
NAVER_CLIENT_SECRET=
# DATABASE_URL, when present, overrides the individual DATABASE_* fields.
# Managed MySQL: charset=utf8mb4&ssl_ca=/app/tls/oracle-mysql-ca.pem&ssl_check_hostname=false
DATABASE_URL=
# VITE_* and frontend proxy settings are not backend runtime credentials.
18 changes: 9 additions & 9 deletions .github/workflows/cicd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
runs-on: ubuntu-24.04-arm
timeout-minutes: 40
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Test in Docker
Expand All @@ -42,16 +42,16 @@ jobs:
outputs:
image: ghcr.io/kyj0503/backtest@${{ steps.build.outputs.digest }}
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3
- uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3
- uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1
- uses: docker/login-action@81c6f93d4bc991e510b342a00b419b3bb4efc5c6 # v4.7.0
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6
- uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7.4.0
id: build
with:
context: ./backtest_be_fast
Expand Down Expand Up @@ -80,17 +80,17 @@ jobs:
packages: read
id-token: write
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Connect to OCI through Tailscale
uses: tailscale/github-action@d1b6cd204f8dceda5b3eaad7f1f767be390056cd # v4
uses: tailscale/github-action@99a8333667fff04fdd6cffb46dfd409f8329a7bf # v4.3.0
with:
oauth-client-id: ${{ vars.TS_CLIENT_ID }}
audience: ${{ vars.TS_AUDIENCE }}
tags: tag:jandi-ci
version: '1.104.1'
sha256sum: 108d1d96ecf410d305571e173516f27038f870919a9b89c914a167c6d33a4528
version: '1.104.2'
sha256sum: 512f613ed7d04fc7282f7dc5bd76aa105aae28168fbb9357eb749ebcbc29f0ae
args: --accept-dns=false
retry: '1'
ping: ${{ vars.OCI_HOST }}
Expand Down
2 changes: 2 additions & 0 deletions HISTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -395,3 +395,5 @@ docker compose -f compose.dev.yaml exec -T backtest-fe npm run build
- 문서 클린: 버전 표기 전면 최신(구버전 문자열 0건), Docker 온보딩 견실, CLAUDE.md 제약 검증표 Zustand 1건 제외 전부 VERIFIED(기준선 141/113 정적 재계산 일치).
- 이번 세션 추가 검증: lint 경고 정확히 3개 실측(컨테이너), `pool_config.py` 40+80 확인, `TICKER_POPULARITY_TOTAL.labels(ticker=…)` 확인, jsdom/patch-package 미사용 확인, 현금 이름 필드 검증 순서 버그 확인(Codex 옳음 — 이전 감사의 "현금 심볼 검증 면제" 서술은 필드 순서 때문에 실제로는 무효), `nginx.prod.conf` trailing slash·SPA fallback 확인.
- Codex 항목 중 미재현 잔여: BE 커버리지 42%(측정치 인용), 요청 한도 부재의 실부하 영향(P2-16), 캐시 DataFrame 공유 변경(P3-22), UI 숨김 옵션 payload 잔류(P2-13).

- 2026-10-24: Python 3.15 / backtesting.py 0.6.6 업그레이드. 기존 단일 수수료 적용과 마지막 거래 청산을 유지. MySQL CA 검증용 마운트 및 URL 로그 비밀번호 마스킹 수정. 단위 504개와 기존 golden E2E 통과.
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

## 구성

- `backtest_be_fast/`: Python 3.11, FastAPI, SQLAlchemy, pandas, backtesting.py 0.3.3.
- `backtest_be_fast/`: Python 3.15, FastAPI, SQLAlchemy, pandas, backtesting.py 0.6.6.
- `database/`: MySQL 스키마와 마이그레이션 운영 안내.
- `compose.dev.yaml`: 로컬 백엔드·MySQL 개발 환경.
- `compose.dev-prod.yaml`: 실제 런타임 이미지의 로컬 워커 모드 검증.
Expand Down Expand Up @@ -34,7 +34,7 @@ docker build --target runtime ./backtest_be_fast
```

테스트는 Docker에서 실행합니다. 스키마 정합성 검사는 임시 MySQL만 사용하며 운영 DB를 변경하지 않습니다.
CI 취약점 검사는 기존 bokeh 예외 근거를 유지합니다. 신규 차단 취약점을 무시하지 않습니다.
CI 취약점 검사는 알려진 취약점을 예외 없이 차단합니다.

## GitHub Actions 배포

Expand All @@ -55,6 +55,8 @@ CI 취약점 검사는 기존 bokeh 예외 근거를 유지합니다. 신규 차
## DB 운영

앱 배포에서 운영 DB 마이그레이션을 자동 실행하지 않습니다.
OCI 관리형 MySQL 26.10을 사용하며, 전용 CA를 검증하는 TLS 연결을 사용합니다.
CA 파일은 `/opt/backtest/production/tls/oracle-mysql-ca.pem`에 두고 컨테이너의 `/app/tls`에 읽기 전용으로 마운트합니다.
스키마 변경은 `database/schema.sql`과 `backtest_be_fast/alembic/`를 함께 갱신하고 검증합니다.
기존 DB의 baseline, Alembic 적용, MySQL 버전 변경은 [database/README.md](database/README.md)를 따릅니다.

Expand Down
14 changes: 7 additions & 7 deletions backtest_be_fast/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
# syntax=docker/dockerfile:1
# Python 3.11 슬림 이미지 사용
# Python 3.15 슬림 이미지 사용
#
# 3-스테이지 구성:
# builder - 컴파일 툴체인(gcc/g++/cargo/rustc 등) + venv 생성 + requirements.txt만 설치.
# 최종 이미지에는 포함되지 않는다.
# test - builder에서 갈라져 나와 requirements-test.txt를 추가 설치하고 pytest를 실행.
# 배포 전 테스트 단계 전용 (아래 설명 참고).
# runtime - 클린 python:3.11-slim 위에 /opt/venv만 복사. 컴파일러도, 테스트
# runtime - 클린 python:3.15-slim-trixie 위에 /opt/venv만 복사. 컴파일러도, 테스트
# 의존성도 없다. non-root 사용자로 실행된다.
FROM python:3.11-slim AS builder
FROM python:3.15-slim-trixie AS builder

WORKDIR /app

Expand Down Expand Up @@ -58,7 +58,7 @@ COPY tests ./tests
COPY scripts ./scripts
COPY pytest.ini ./

RUN pytest tests/unit -q
RUN pytest tests/unit -q && pytest tests/e2e -m e2e -q

# audit: 의존성 취약점 감사 (CI 전용)
#
Expand All @@ -69,7 +69,7 @@ RUN pytest tests/unit -q
# 바인드 마운트(-v)가 아니라 빌드 컨텍스트로 파일을 받는 이유: Jenkins가
# 컨테이너 안에서 호스트 Docker 데몬에 붙어 sibling 컨테이너를 띄우는 구조라
# -v의 소스 경로가 호스트 기준으로 해석되어 파일을 찾지 못한다.
FROM python:3.11-slim AS audit
FROM python:3.15-slim-trixie AS audit

WORKDIR /app
COPY requirements.lock.txt .
Expand All @@ -86,8 +86,8 @@ RUN pip install -q pip-audit && \
# runtime: 실제 배포 이미지 (마지막 스테이지 = 기본 빌드 타깃)
#
# builder의 컴파일러/헤더, test의 pytest 등 테스트 의존성은 여기 포함되지 않는다.
# 클린한 python:3.11-slim 위에 venv(/opt/venv)만 복사하고 non-root 사용자로 실행한다.
FROM python:3.11-slim AS runtime
# 클린한 python:3.15-slim-trixie 위에 venv(/opt/venv)만 복사하고 non-root 사용자로 실행한다.
FROM python:3.15-slim-trixie AS runtime

WORKDIR /app

Expand Down
2 changes: 1 addition & 1 deletion backtest_be_fast/Dockerfile.dev
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Development Dockerfile for Backend
FROM python:3.11-slim
FROM python:3.15-slim-trixie

WORKDIR /app

Expand Down
5 changes: 4 additions & 1 deletion backtest_be_fast/app/services/backtest_engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,10 @@ async def run_backtest(self, request: BacktestRequest) -> BacktestResult:
data,
strategy_class,
cash=request.initial_cash,
commission=request.commission,
# 기존 진입 시 1회 수수료 모델 유지
spread=request.commission,
commission=0.0,
finalize_trades=True,
)
self.logger.debug("백테스트 객체 생성 완료")

Expand Down
7 changes: 3 additions & 4 deletions backtest_be_fast/app/services/database/database_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,11 +125,10 @@ def get_url(self) -> str:
def get_masked_url(self) -> str:
"""로깅용 마스킹된 URL을 반환합니다 (비밀번호 숨김)."""
try:
if self.database_password and isinstance(self.database_url, str):
return self.database_url.replace(self.database_password, "***")
from sqlalchemy.engine import make_url
return make_url(self.database_url).render_as_string(hide_password=True)
except Exception:
pass
return self.database_url
return "<invalid database URL>"

def get_components(self) -> dict:
"""연결 설정 요소를 딕셔너리로 반환합니다."""
Expand Down
93 changes: 69 additions & 24 deletions backtest_be_fast/requirements-test.lock.txt
Original file line number Diff line number Diff line change
@@ -1,25 +1,70 @@
# P2-22: requirements-test.txt의 완전 고정 락. requirements.lock.txt 위에
# "얹어서" 설치되는 것을 전제로 하며 (Dockerfile의 test 스테이지는 builder를
# FROM하고, Dockerfile.dev는 requirements.lock.txt 설치 후 이 파일을 설치),
# 그래서 requirements.lock.txt에 이미 있는 패키지는 여기 다시 나열하지 않는다.
#
# 생성 방법은 requirements.lock.txt와 동일 (같은 pip freeze 출력에서, 어떤
# 설치된 패키지도 필요로 하지 않는/오직 pytest 계열만 필요로 하는 패키지만
# `pip show`의 Required-by로 골라냈다). httpx/httpcore/sniffio는 FastAPI
# TestClient 등 테스트에서만 쓰이고 애플리케이션 코드 경로에는 전혀
# 등장하지 않는다 (requirements.lock.txt의 anyio/starlette 쪽에는 sniffio가
# 필요하지 않음을 pip show로 확인했다).

coverage==7.15.2
factory-boy==3.3.0
Faker==22.6.0
freezegun==1.4.0
alembic==1.20.1
annotated-doc==0.0.5
annotated-types==0.8.0
anyio==4.15.1
backtesting==0.6.6
beautifulsoup4==4.15.0
bokeh==3.10.1
cachetools==7.2.4
certifi==2026.10.23
cffi==2.1.1
charset-normalizer==3.5.2
click==8.5.0
coverage==7.17.0
cryptography==50.0.2
curl_cffi==0.16.3
factory_boy==3.3.3
Faker==40.43.0
fastapi==0.162.0
freezegun==1.5.5
h11==0.16.0
httpcore==1.0.9
httpx==0.26.0
iniconfig==2.3.0
pluggy==1.6.0
pytest==8.4.2
pytest-asyncio==0.24.0
pytest-cov==4.1.0
pytest-mock==3.12.0
sniffio==1.3.1
httpx==0.28.1
idna==3.20
iniconfig==2.3.1
Jinja2==3.1.6
lxml==6.1.3
Mako==1.4.3
MarkupSafe==3.0.4
multitasking==0.0.13
narwhals==2.27.1
numpy==2.5.4
opentelemetry-api==1.45.1
packaging==26.3
pandas==3.1.0
peewee==4.5.3
pillow==13.0.0
platformdirs==4.13.5
pluggy==1.7.0
prometheus-fastapi-instrumentator==8.1.0
prometheus_client==0.26.0
protobuf==7.36.2
pycparser==3.12
pydantic==2.14.1
pydantic-settings==2.16.1
pydantic_core==2.50.1
Pygments==2.21.0
PyMySQL==1.2.3
pytest==9.1.1
pytest-asyncio==1.4.0
pytest-cov==7.1.0
pytest-mock==3.17.0
python-dateutil==3.0.0
python-dotenv==1.2.4
python-multipart==0.0.32
pytz==2026.5
PyYAML==6.0.3
requests==2.34.2
six==1.17.0
soupsieve==3.0.4
SQLAlchemy==2.1.6
starlette==1.9.0
tornado==6.6
typing-inspection==0.4.4
typing_extensions==4.16.0
tzdata==2026.5
urllib3==2.8.0
uvicorn==0.54.0
websockets==17.3
xyzservices==2026.9.1
yfinance==1.7.0
14 changes: 7 additions & 7 deletions backtest_be_fast/requirements-test.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@
# requirements-test.lock.txt(고정본, requirements.lock.txt 위에 얹어 설치)를
# 설치한다. 재생성 방법은 requirements.lock.txt 상단 주석 참고.
pytest>=8.2.0
pytest-asyncio==0.24.0
pytest-cov==4.1.0
pytest-mock==3.12.0
httpx==0.26.0
faker==22.6.0
factory-boy==3.3.0
freezegun==1.4.0
pytest-asyncio==1.4.0
pytest-cov==7.1.0
pytest-mock==3.17.0
httpx==0.28.1
faker==40.43.0
factory-boy==3.3.3
freezegun==1.5.5
Loading