From 3c5f1752e6b1138bc633d18ac9d5ea711d7fa11c Mon Sep 17 00:00:00 2001 From: agentson Date: Mon, 23 Feb 2026 10:30:47 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20DB=20WAL=20=EB=AA=A8=EB=93=9C=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9,=20.env.example=20=EC=A0=95=EB=A6=AC=20(#210?= =?UTF-8?q?,=20#213,=20#216)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - #210: init_db()에 WAL 저널 모드 적용 (파일 DB에만, :memory: 제외) - 대시보드(READ)와 거래루프(WRITE) 동시 접근 시 SQLite 락 오류 방지 - busy_timeout=5000ms 설정 - #213: RATE_LIMIT_RPS 기본값 2.0으로 통일 (.env.example이 5.0으로 잘못 표기됨) - #216: .env.example 중요 변수 추가 및 정리 - KIS_BASE_URL 모의/실전 URL 주석 명시 (포트 29443 수정 포함) - MODE, TRADE_MODE, ENABLED_MARKETS, PAPER_OVERSEAS_CASH 추가 - GEMINI_MODEL 업데이트 (gemini-pro → gemini-2.0-flash-exp) - DASHBOARD 설정 섹션 추가 테스트 2개 추가 (WAL 파일 DB 적용, 메모리 DB 미적용 검증) Co-Authored-By: Claude Sonnet 4.6 --- .env.example | 64 +++++++++++++++++++++++++++++++++++++++++------- src/db.py | 5 ++++ tests/test_db.py | 37 ++++++++++++++++++++++++++++ 3 files changed, 97 insertions(+), 9 deletions(-) diff --git a/.env.example b/.env.example index ac91c53..218d0b6 100644 --- a/.env.example +++ b/.env.example @@ -1,36 +1,82 @@ +# ============================================================ +# The Ouroboros — Environment Configuration +# ============================================================ +# Copy this file to .env and fill in your values. +# Lines starting with # are comments. + +# ============================================================ # Korea Investment Securities API +# ============================================================ KIS_APP_KEY=your_app_key_here KIS_APP_SECRET=your_app_secret_here KIS_ACCOUNT_NO=12345678-01 -KIS_BASE_URL=https://openapivts.koreainvestment.com:9443 +# Paper trading (VTS): https://openapivts.koreainvestment.com:29443 +# Live trading: https://openapi.koreainvestment.com:9443 +KIS_BASE_URL=https://openapivts.koreainvestment.com:29443 + +# ============================================================ +# Trading Mode +# ============================================================ +# paper = 모의투자 (safe for testing), live = 실전투자 (real money) +MODE=paper + +# daily = batch per session, realtime = per-stock continuous scan +TRADE_MODE=daily + +# Comma-separated market codes: KR, US, JP, HK, CN, VN +ENABLED_MARKETS=KR,US + +# Simulated USD cash for paper (VTS) overseas trading. +# VTS overseas balance API often returns 0; this value is used as fallback. +# Set to 0 to disable fallback (not used in live mode). +PAPER_OVERSEAS_CASH=50000.0 + +# ============================================================ # Google Gemini +# ============================================================ GEMINI_API_KEY=your_gemini_api_key_here -GEMINI_MODEL=gemini-pro +# Recommended: gemini-2.0-flash-exp or gemini-1.5-pro +GEMINI_MODEL=gemini-2.0-flash-exp +# ============================================================ # Risk Management +# ============================================================ CIRCUIT_BREAKER_PCT=-3.0 FAT_FINGER_PCT=30.0 CONFIDENCE_THRESHOLD=80 +# ============================================================ # Database +# ============================================================ DB_PATH=data/trade_logs.db -# Rate Limiting (requests per second for KIS API) -# Reduced to 5.0 to avoid "초당 거래건수 초과" errors (EGW00201) -RATE_LIMIT_RPS=5.0 +# ============================================================ +# Rate Limiting +# ============================================================ +# KIS API real limit is ~2 RPS. Keep at 2.0 for maximum safety. +# Increasing this risks EGW00201 "초당 거래건수 초과" errors. +RATE_LIMIT_RPS=2.0 -# Trading Mode (paper / live) -MODE=paper - -# External Data APIs (optional — for enhanced decision-making) +# ============================================================ +# External Data APIs (optional) +# ============================================================ # NEWS_API_KEY=your_news_api_key_here # NEWS_API_PROVIDER=alphavantage # MARKET_DATA_API_KEY=your_market_data_key_here +# ============================================================ # Telegram Notifications (optional) +# ============================================================ # Get bot token from @BotFather on Telegram # Get chat ID from @userinfobot or your chat # TELEGRAM_BOT_TOKEN=1234567890:ABCdefGHIjklMNOpqrsTUVwxyz # TELEGRAM_CHAT_ID=123456789 # TELEGRAM_ENABLED=true + +# ============================================================ +# Dashboard (optional) +# ============================================================ +# DASHBOARD_ENABLED=false +# DASHBOARD_HOST=127.0.0.1 +# DASHBOARD_PORT=8080 diff --git a/src/db.py b/src/db.py index 60dae11..5b23a38 100644 --- a/src/db.py +++ b/src/db.py @@ -14,6 +14,11 @@ def init_db(db_path: str) -> sqlite3.Connection: if db_path != ":memory:": Path(db_path).parent.mkdir(parents=True, exist_ok=True) conn = sqlite3.connect(db_path) + # Enable WAL mode for concurrent read/write (dashboard + trading loop). + # WAL does not apply to in-memory databases. + if db_path != ":memory:": + conn.execute("PRAGMA journal_mode=WAL") + conn.execute("PRAGMA busy_timeout=5000") conn.execute( """ CREATE TABLE IF NOT EXISTS trades ( diff --git a/tests/test_db.py b/tests/test_db.py index fe956eb..af2fcf8 100644 --- a/tests/test_db.py +++ b/tests/test_db.py @@ -1,5 +1,8 @@ """Tests for database helper functions.""" +import tempfile +import os + from src.db import get_open_position, init_db, log_trade @@ -58,3 +61,37 @@ def test_get_open_position_returns_none_when_latest_is_sell() -> None: def test_get_open_position_returns_none_when_no_trades() -> None: conn = init_db(":memory:") assert get_open_position(conn, "AAPL", "US_NASDAQ") is None + + +# --------------------------------------------------------------------------- +# WAL mode tests (issue #210) +# --------------------------------------------------------------------------- + + +def test_wal_mode_applied_to_file_db() -> None: + """File-based DB must use WAL journal mode for dashboard concurrent reads.""" + with tempfile.NamedTemporaryFile(suffix=".db", delete=False) as f: + db_path = f.name + try: + conn = init_db(db_path) + cursor = conn.execute("PRAGMA journal_mode") + mode = cursor.fetchone()[0] + assert mode == "wal", f"Expected WAL mode, got {mode}" + conn.close() + finally: + os.unlink(db_path) + # Clean up WAL auxiliary files if they exist + for ext in ("-wal", "-shm"): + path = db_path + ext + if os.path.exists(path): + os.unlink(path) + + +def test_wal_mode_not_applied_to_memory_db() -> None: + """:memory: DB must not apply WAL (SQLite does not support WAL for in-memory).""" + conn = init_db(":memory:") + cursor = conn.execute("PRAGMA journal_mode") + mode = cursor.fetchone()[0] + # In-memory DBs default to 'memory' journal mode + assert mode != "wal", "WAL should not be set on in-memory database" + conn.close() -- 2.49.1