Files
tradeing/settings_db.py
T
chpark c4e6aab7b2 React + FastAPI 풀 마이그레이션 — Streamlit 제거
- backend/ — FastAPI + JWT + 모든 REST 엔드포인트
- frontend/ — Next.js 14 + Tailwind + 7페이지 (대시보드/트레이드/거래소/자동매매/설정/내정보/로그인)
- core_logic.py — 신호계산/알림 로직 분리 (기존 app_streamlit.py 에서 추출)
- users_db.py + bcrypt 인증, exchange_keys.py + Fernet 암호화
- trades_db.py — 진입/청산 lifecycle 추적, signal_events raw 로그
- settings_db.py — 모든 운영 파라미터 DB 영속 저장 (RSI/거래량/펀딩비 임계값 포함)
- docker-compose: frontend / backend / postgres + Traefik 라우팅
- assets/logo.svg — JUNGGOMOA 그라디언트 로고

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 17:27:11 +09:00

150 lines
4.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
설정값 영속 저장. SQLite key-value 테이블 한 개로 단순화.
- 텔레그램 토큰 / chat_id
- 모니터링 심볼
- 알림 시간축 목록
- 쿨다운 / STOP_LOSS_PCT 등 운영 파라미터
Streamlit rerun 안전: 모듈 최상단의 connection 캐시는 모듈 캐싱 (sys.modules) 으로
process lifetime 동안 보존. 멀티 스레드 (alert thread) 도 같은 DB 파일을 읽으므로
SQLite 의 thread-safe 모드 (`check_same_thread=False`) 로 연다.
"""
import os
import sqlite3
import threading
from typing import Any, Optional
DB_PATH = os.environ.get("SETTINGS_DB_PATH", "/app/data/settings.db")
_lock = threading.RLock()
_conn: Optional[sqlite3.Connection] = None
def _get_conn() -> sqlite3.Connection:
global _conn
if _conn is None:
os.makedirs(os.path.dirname(DB_PATH), exist_ok=True)
_conn = sqlite3.connect(DB_PATH, check_same_thread=False, isolation_level=None)
_conn.execute("PRAGMA journal_mode=WAL")
_conn.execute(
"""
CREATE TABLE IF NOT EXISTS settings (
key TEXT PRIMARY KEY,
value TEXT NOT NULL,
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
)
"""
)
return _conn
DEFAULTS = {
# ── 알림 / 모니터링 ──
"telegram_token": "",
"telegram_chat_id": "",
"alert_symbol": "BTCUSDT",
"alert_timeframes": "5m,15m,30m,1h",
"alert_cooldown_sec": "600",
"stop_loss_pct": "0.0075",
"alert_enabled": "1",
"daily_report_enabled": "1",
"polling_interval_sec": "30",
# ── 신호 임계값 (RSI / body) ──
"long_rsi_max": "75",
"short_rsi_min": "25",
"strong_long_rsi_max": "65",
"strong_short_rsi_min": "35",
"body_pct_min": "0.002", # 일반 신호 캔들 body 최소 (양/음봉)
"reversal_body_pct": "0.003", # 추세 꺾임 body 최소
"reversal_vol_mult": "1.3", # 추세 꺾임 거래량 동반 배수
# ── 거래량 / 펀딩비 ──
"vol_exhaustion_mult": "3.0", # exhaustion 판정 (vol > avg × N)
"vol_net_mult": "2.0", # vol_long/short_signal: net > avg × N
"oi_active_pct": "0.001", # OI 활성도 임계 (변동률 절대값)
"fr_long_overheat": "0.005", # 롱 과열 (배너)
"fr_short_caution": "-0.005", # 숏스퀴즈 경보
"fr_short_extreme": "-0.007", # 숏 주의 신호 임계
# ── 차트 / UI ──
"candle_limit_desktop": "53",
"candle_limit_mobile": "14",
"forming_stable_polls": "2", # forming candle 안정성 — 연속 N polls True 요구
}
def init_db_with_env_defaults():
"""최초 기동 시 .env 값을 DB 기본값으로 복사. 이미 존재하는 키는 건드리지 않음."""
with _lock:
conn = _get_conn()
for k, default in DEFAULTS.items():
cur = conn.execute("SELECT value FROM settings WHERE key=?", (k,))
if cur.fetchone() is not None:
continue
seed = default
env_map = {
"telegram_token": "TELEGRAM_TOKEN",
"telegram_chat_id": "TELEGRAM_CHAT_ID",
}
if k in env_map:
seed = os.environ.get(env_map[k], default) or default
conn.execute(
"INSERT INTO settings(key, value) VALUES (?, ?)",
(k, seed),
)
def get(key: str, default: Any = None) -> str:
with _lock:
conn = _get_conn()
cur = conn.execute("SELECT value FROM settings WHERE key=?", (key,))
row = cur.fetchone()
if row is None:
return DEFAULTS.get(key, default) if default is None else default
return row[0]
def get_int(key: str, default: int = 0) -> int:
try:
return int(get(key, default))
except (TypeError, ValueError):
return default
def get_float(key: str, default: float = 0.0) -> float:
try:
return float(get(key, default))
except (TypeError, ValueError):
return default
def get_bool(key: str, default: bool = False) -> bool:
v = get(key, "1" if default else "0")
return str(v).strip().lower() in ("1", "true", "yes", "on")
def get_list(key: str, default=None, sep: str = ",") -> list:
v = get(key, "")
if not v:
return list(default or [])
return [s.strip() for s in v.split(sep) if s.strip()]
def set_value(key: str, value: Any):
with _lock:
conn = _get_conn()
conn.execute(
"""
INSERT INTO settings(key, value, updated_at) VALUES(?, ?, datetime('now'))
ON CONFLICT(key) DO UPDATE SET value=excluded.value, updated_at=excluded.updated_at
""",
(key, str(value)),
)
def all_settings() -> dict:
with _lock:
conn = _get_conn()
cur = conn.execute("SELECT key, value FROM settings ORDER BY key")
return dict(cur.fetchall())