106 lines
3.2 KiB
Python
106 lines
3.2 KiB
Python
import json
|
|
import os
|
|
import threading
|
|
from typing import Any
|
|
|
|
from .common import DATA_DIR, logger
|
|
|
|
# 상태 파일 경로
|
|
STATE_FILE = str(DATA_DIR / "bot_state.json")
|
|
|
|
# 상태 파일 잠금
|
|
_state_lock = threading.RLock()
|
|
|
|
|
|
def _load_state_unsafe() -> dict[str, Any]:
|
|
"""내부 사용 전용: Lock 없이 상태 파일 로드"""
|
|
if os.path.exists(STATE_FILE):
|
|
try:
|
|
if os.path.getsize(STATE_FILE) == 0:
|
|
return {}
|
|
with open(STATE_FILE, encoding="utf-8") as f:
|
|
return json.load(f)
|
|
except json.JSONDecodeError:
|
|
logger.warning("[StateManager] 상태 파일 손상됨, 빈 상태 반환: %s", STATE_FILE)
|
|
return {}
|
|
except OSError as e:
|
|
logger.error("[StateManager] 상태 파일 읽기 실패: %s", e)
|
|
return {}
|
|
return {}
|
|
|
|
|
|
def _save_state_unsafe(state: dict[str, Any]) -> None:
|
|
"""내부 사용 전용: Lock 없이 상태 파일 저장 (원자적)"""
|
|
try:
|
|
temp_file = f"{STATE_FILE}.tmp"
|
|
with open(temp_file, "w", encoding="utf-8") as f:
|
|
json.dump(state, f, indent=2, ensure_ascii=False)
|
|
f.flush()
|
|
os.fsync(f.fileno())
|
|
|
|
os.replace(temp_file, STATE_FILE)
|
|
except (OSError, TypeError, ValueError) as e:
|
|
logger.error("[StateManager] 상태 저장 실패: %s", e)
|
|
|
|
|
|
def load_state() -> dict[str, Any]:
|
|
"""전체 봇 상태를 로드합니다."""
|
|
with _state_lock:
|
|
return _load_state_unsafe()
|
|
|
|
|
|
def save_state(state: dict[str, Any]) -> None:
|
|
"""전체 봇 상태를 저장합니다."""
|
|
with _state_lock:
|
|
_save_state_unsafe(state)
|
|
|
|
|
|
def get_value(symbol: str, key: str, default: Any = None) -> Any:
|
|
"""
|
|
특정 심볼의 상태 값을 조회합니다.
|
|
예: get_value("KRW-BTC", "max_price", 0.0)
|
|
"""
|
|
with _state_lock:
|
|
state = _load_state_unsafe()
|
|
symbol_data = state.get(symbol, {})
|
|
return symbol_data.get(key, default)
|
|
|
|
|
|
def set_value(symbol: str, key: str, value: Any) -> None:
|
|
"""
|
|
특정 심볼의 상태 값을 설정하고 저장합니다.
|
|
예: set_value("KRW-BTC", "max_price", 100000000)
|
|
"""
|
|
with _state_lock:
|
|
state = _load_state_unsafe()
|
|
if symbol not in state:
|
|
state[symbol] = {}
|
|
|
|
state[symbol][key] = value
|
|
_save_state_unsafe(state)
|
|
logger.debug("[StateManager] 상태 업데이트: [%s] %s = %s", symbol, key, value)
|
|
|
|
|
|
def update_max_price_state(symbol: str, current_price: float) -> float:
|
|
"""
|
|
최고가(max_price)를 상태 파일에 업데이트합니다.
|
|
기존 값보다 클 경우에만 업데이트합니다.
|
|
|
|
Returns:
|
|
업데이트된(또는 유지된) max_price
|
|
"""
|
|
with _state_lock:
|
|
state = _load_state_unsafe()
|
|
if symbol not in state:
|
|
state[symbol] = {}
|
|
|
|
old_max = float(state[symbol].get("max_price", 0.0) or 0.0)
|
|
|
|
if current_price > old_max:
|
|
state[symbol]["max_price"] = current_price
|
|
_save_state_unsafe(state)
|
|
logger.debug("[StateManager] [%s] max_price 갱신: %.2f -> %.2f", symbol, old_max, current_price)
|
|
return current_price
|
|
|
|
return old_max
|