64
.env.example
64
.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
|
# Korea Investment Securities API
|
||||||
|
# ============================================================
|
||||||
KIS_APP_KEY=your_app_key_here
|
KIS_APP_KEY=your_app_key_here
|
||||||
KIS_APP_SECRET=your_app_secret_here
|
KIS_APP_SECRET=your_app_secret_here
|
||||||
KIS_ACCOUNT_NO=12345678-01
|
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
|
# Google Gemini
|
||||||
|
# ============================================================
|
||||||
GEMINI_API_KEY=your_gemini_api_key_here
|
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
|
# Risk Management
|
||||||
|
# ============================================================
|
||||||
CIRCUIT_BREAKER_PCT=-3.0
|
CIRCUIT_BREAKER_PCT=-3.0
|
||||||
FAT_FINGER_PCT=30.0
|
FAT_FINGER_PCT=30.0
|
||||||
CONFIDENCE_THRESHOLD=80
|
CONFIDENCE_THRESHOLD=80
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
# Database
|
# Database
|
||||||
|
# ============================================================
|
||||||
DB_PATH=data/trade_logs.db
|
DB_PATH=data/trade_logs.db
|
||||||
|
|
||||||
# Rate Limiting (requests per second for KIS API)
|
# ============================================================
|
||||||
# Reduced to 5.0 to avoid "초당 거래건수 초과" errors (EGW00201)
|
# Rate Limiting
|
||||||
RATE_LIMIT_RPS=5.0
|
# ============================================================
|
||||||
|
# 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)
|
||||||
|
# ============================================================
|
||||||
# External Data APIs (optional — for enhanced decision-making)
|
|
||||||
# NEWS_API_KEY=your_news_api_key_here
|
# NEWS_API_KEY=your_news_api_key_here
|
||||||
# NEWS_API_PROVIDER=alphavantage
|
# NEWS_API_PROVIDER=alphavantage
|
||||||
# MARKET_DATA_API_KEY=your_market_data_key_here
|
# MARKET_DATA_API_KEY=your_market_data_key_here
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
# Telegram Notifications (optional)
|
# Telegram Notifications (optional)
|
||||||
|
# ============================================================
|
||||||
# Get bot token from @BotFather on Telegram
|
# Get bot token from @BotFather on Telegram
|
||||||
# Get chat ID from @userinfobot or your chat
|
# Get chat ID from @userinfobot or your chat
|
||||||
# TELEGRAM_BOT_TOKEN=1234567890:ABCdefGHIjklMNOpqrsTUVwxyz
|
# TELEGRAM_BOT_TOKEN=1234567890:ABCdefGHIjklMNOpqrsTUVwxyz
|
||||||
# TELEGRAM_CHAT_ID=123456789
|
# TELEGRAM_CHAT_ID=123456789
|
||||||
# TELEGRAM_ENABLED=true
|
# TELEGRAM_ENABLED=true
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# Dashboard (optional)
|
||||||
|
# ============================================================
|
||||||
|
# DASHBOARD_ENABLED=false
|
||||||
|
# DASHBOARD_HOST=127.0.0.1
|
||||||
|
# DASHBOARD_PORT=8080
|
||||||
|
|||||||
@@ -14,6 +14,11 @@ def init_db(db_path: str) -> sqlite3.Connection:
|
|||||||
if db_path != ":memory:":
|
if db_path != ":memory:":
|
||||||
Path(db_path).parent.mkdir(parents=True, exist_ok=True)
|
Path(db_path).parent.mkdir(parents=True, exist_ok=True)
|
||||||
conn = sqlite3.connect(db_path)
|
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(
|
conn.execute(
|
||||||
"""
|
"""
|
||||||
CREATE TABLE IF NOT EXISTS trades (
|
CREATE TABLE IF NOT EXISTS trades (
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
"""Tests for database helper functions."""
|
"""Tests for database helper functions."""
|
||||||
|
|
||||||
|
import tempfile
|
||||||
|
import os
|
||||||
|
|
||||||
from src.db import get_open_position, init_db, log_trade
|
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:
|
def test_get_open_position_returns_none_when_no_trades() -> None:
|
||||||
conn = init_db(":memory:")
|
conn = init_db(":memory:")
|
||||||
assert get_open_position(conn, "AAPL", "US_NASDAQ") is None
|
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()
|
||||||
|
|||||||
Reference in New Issue
Block a user