Files
Bithumb/scripts/verify_env.py
dsyoon b52d61b777 WLD DeepCoin 단계별 구조 재편 및 설정·문서 통합
로고스/루트 레거시를 제거하고 deepcoin 패키지·scripts 01~05 CLI·docs/reference로
데이터·GT·분석·매칭·운영 단계를 정리했다. config와 .env 기반 설정, trade_anaysis.html 동기화 포함.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-30 22:58:25 +09:00

220 lines
6.0 KiB
Python

#!/usr/bin/env python3
"""`.env` 완전성 및 config 로드 점검."""
from __future__ import annotations
import os
import re
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT))
ENV_FILE = ROOT / ".env"
CONFIG_FILE = ROOT / "config.py"
# config.py에서 읽는 환경 변수 키 (코드 기본값이 있는 항목)
CONFIG_ENV_KEYS = {
"BITHUMB_API_URL",
"BITHUMB_API_CANDLE_COUNT",
"BITHUMB_MINUTE_INTERVALS",
"HTS_API_RETRY_SLEEP_SEC",
"SYMBOL",
"COIN_NAME",
"DAILY_INTERVAL_MIN",
"ENTRY_INTERVAL",
"TREND_INTERVAL_1H",
"TREND_INTERVAL_1D",
"ALL_INTERVALS",
"DOWNLOAD_INTERVALS",
"GENERAL_ANALYSIS_INTERVALS",
"TIMING_INTERVALS",
"TREND_INTERVALS",
"INTERVAL_PREFIX",
"BB_PERIOD",
"BB_STD",
"BB_MIN_WIDTH_PCT",
"RSI_PERIOD",
"DISPARITY_PERIODS",
"DISPARITY_OVERBOUGHT",
"DISPARITY_OVERSOLD",
"MACD_FAST",
"MACD_SLOW",
"MACD_SIGNAL",
"STOCH_K_PERIOD",
"STOCH_D_PERIOD",
"STOCH_SMOOTH_K",
"STOCH_OVERSOLD",
"STOCH_OVERBOUGHT",
"TREND_RANGE_MA_GAP_PCT",
"ALIGN_RSI_OVERSOLD",
"ALIGN_RSI_OVERBOUGHT",
"ALIGN_RSI_CONFLICT_TIMING_LOW",
"ALIGN_RSI_CONFLICT_TIMING_HIGH",
"ALIGN_RSI_CONFLICT_TREND_LOW",
"ALIGN_RSI_CONFLICT_TREND_HIGH",
"ALIGN_BB_POS_LOW",
"ALIGN_BB_POS_HIGH",
"DOWNLOAD_MONTHS",
"DOWNLOAD_MONTHS_1M",
"INCREMENTAL_OVERLAP_BARS",
"DOWNLOAD_BACKFILL_EXTRA_BARS",
"DOWNLOAD_MIN_INCREMENTAL_BARS",
"DOWNLOAD_DAILY_EXTRA_DAYS",
"CHART_LOOKBACK_DAYS",
"DB_READ_LIMIT_DEFAULT",
"DB_ROW_WARMUP_BARS",
"DB_ROW_MIN_DAILY_BARS",
"DB_ROW_DAILY_PADDING_DAYS",
"DB_PATH",
"GROUND_TRUTH_FILE",
"GT_UNLIMITED_CHRONOLOGICAL_DAYS",
"GT_MIN_SWING_PCT",
"GT_PIVOT_ORDER",
"GT_MIN_BARS_BETWEEN",
"GT_MAX_ROUND_TRIPS",
"GT_SELECTION_MODE",
"GT_MIN_LEG_PCT",
"GT_BUY_MIN_SWING_PCT",
"GT_BUY_BB_MAX",
"GT_BUY_MIN_BARS",
"GT_MAX_BUYS_PER_LEG",
"GT_MAX_SELLS_PER_LEG",
"GT_SELL_SPLIT_GAP_PCT",
"GT_MARKER_SIZE_MIN",
"GT_MARKER_SIZE_MAX",
"GT_INITIAL_CASH_KRW",
"TRADING_FEE_RATE",
"MONITOR_LOOP_SLEEP_SEC",
"MONITOR_POOL_WORKERS",
"MONITOR_DEFAULT_INTERVAL",
"MONITOR_API_RETRIES",
"MONITOR_API_BONG_COUNT",
"MONITOR_SLEEP_AFTER_REQUEST_SEC",
"MONITOR_SLEEP_RATE_LIMIT_SEC",
"MONITOR_SLEEP_BETWEEN_CHUNKS_SEC",
"MONITOR_API_CHUNK_BARS",
"MONITOR_MA_WINDOWS",
"MONITOR_NORM_WINDOW",
"MONITOR_TELEGRAM_BATCH_SIZE",
"GA_COL_PREFIX",
"LOOKBACK_BARS",
"CONTEXT_TAIL_ROWS",
"GA_DEFAULT_TAIL_EXPORT",
"GA_PATTERN_TOLERANCE_PCT",
"GA_VP_BINS",
"GA_VP_VALUE_AREA_PCT",
"GA_HV_ROLLING_BARS",
"GA_HV_PERCENTILE_WINDOW",
"GA_HV_ANNUALIZE_SQRT",
"GA_DIVERGENCE_LOOKBACK",
"GA_SMA_PERIODS",
"GA_EMA_SPANS",
"GA_ATR_PERIOD",
"GA_KELTNER_ATR_MULT",
"GA_AO_FAST",
"GA_AO_SLOW",
"GA_LINREG_WINDOW",
"GA_ADX_PERIOD",
"GA_ADX_TREND_THRESHOLD",
"GA_SUPERTREND_ATR_MULT",
"GA_VOL_SPIKE_MULT",
"GA_VOL_MA_WINDOW",
"GA_CCI_PERIOD",
"GA_WILLIAMS_PERIOD",
"GA_ROC_PERIOD",
"GA_MFI_PERIOD",
"GA_CMF_PERIOD",
"GA_DONCHIAN_PERIOD",
"GA_BB_SQUEEZE_WINDOW",
"GA_BB_SQUEEZE_QUANTILE",
"GA_PIVOT_ORDER",
"GA_PSAR_AF_START",
"GA_PSAR_AF_STEP",
"GA_PSAR_AF_MAX",
}
# 비어 있어도 되는 선택 항목 (현재는 모두 채움)
OPTIONAL_EMPTY = frozenset()
def parse_env_file(path: Path) -> dict[str, str]:
"""`.env` 키=값 파싱."""
out: dict[str, str] = {}
if not path.is_file():
return out
for line in path.read_text(encoding="utf-8").splitlines():
line = line.strip()
if not line or line.startswith("#"):
continue
if "=" not in line:
continue
key, _, val = line.partition("=")
out[key.strip()] = val.strip()
return out
def main() -> int:
errors: list[str] = []
if not ENV_FILE.is_file():
errors.append(f".env 없음: {ENV_FILE}")
for e in errors:
print(f"FAIL: {e}")
return 1
env_vars = parse_env_file(ENV_FILE)
empty = [k for k, v in env_vars.items() if v == "" and k not in OPTIONAL_EMPTY]
missing = sorted(CONFIG_ENV_KEYS - set(env_vars.keys()))
extra = sorted(set(env_vars.keys()) - CONFIG_ENV_KEYS - {
"BITHUMB_ACCESS_KEY",
"BITHUMB_SECRET_KEY",
"COIN_TELEGRAM_BOT_TOKEN",
"COIN_TELEGRAM_CHAT_ID",
})
if empty:
errors.append(f"빈 값: {', '.join(empty)}")
if missing:
errors.append(f"config 대비 .env 누락: {', '.join(missing)}")
if extra:
print(f"WARN: config 미사용 .env 키: {', '.join(extra)}")
from deepcoin.env_loader import env_status, load_project_env
loaded = load_project_env(override=True)
if not loaded:
errors.append("load_project_env: .env 로드 실패 (python-dotenv 확인)")
import config # noqa: E402
checks = [
("SYMBOL", config.SYMBOL, env_vars.get("SYMBOL")),
("DB_PATH", config.DB_PATH, env_vars.get("DB_PATH")),
("BITHUMB_ACCESS_KEY", bool(config.BITHUMB_ACCESS_KEY), bool(env_vars.get("BITHUMB_ACCESS_KEY"))),
("LOOKBACK_BARS[3]", config.LOOKBACK_BARS.get(3), 120),
]
for name, got, expected in checks:
if got != expected and expected is not None:
errors.append(f"config 불일치 {name}: got={got!r} expected={expected!r}")
status = env_status()
print("env_status:", status)
print(f".env 키 수: {len(env_vars)} (config 필수 {len(CONFIG_ENV_KEYS)})")
print(f"SYMBOL={config.SYMBOL} DB_PATH={config.DB_PATH}")
print(f"BITHUMB_KEY set={bool(config.BITHUMB_ACCESS_KEY)} TELEGRAM set={bool(config.COIN_TELEGRAM_BOT_TOKEN)}")
if errors:
print("\n=== 점검 실패 ===")
for e in errors:
print(f" - {e}")
return 1
print("\n=== 점검 통과: .env → config 로드 정상 ===")
return 0
if __name__ == "__main__":
raise SystemExit(main())