perf: optimize kill switch - tail-read only last N lines on boot

- Replace full file scan with _tail_lines() that reads from EOF
- Only load max(FAST_KILL=8, SLOW_KILL=15) lines on boot
- Trim in-memory trade history to prevent unbounded growth
- No I/O bottleneck regardless of history file size

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
21in7
2026-03-18 23:50:17 +09:00
parent 4930140b19
commit 42e53b9ae4

View File

@@ -23,6 +23,34 @@ _SLOW_KILL_PF_THRESHOLD = 0.75 # PF < 이 값이면 중단
_TRADE_HISTORY_DIR = Path("data/trade_history")
def _tail_lines(path: Path, n: int) -> list[str]:
"""파일 끝에서 최대 n줄을 효율적으로 읽는다 (전체 파일 로드 없이)."""
with open(path, "rb") as f:
f.seek(0, 2) # EOF
fsize = f.tell()
if fsize == 0:
return []
# 뒤에서부터 청크 단위로 읽기
chunk_size = min(4096, fsize)
lines: list[str] = []
pos = fsize
remaining = b""
while pos > 0 and len(lines) < n + 1:
read_size = min(chunk_size, pos)
pos -= read_size
f.seek(pos)
chunk = f.read(read_size) + remaining
remaining = b""
split = chunk.split(b"\n")
# 첫 조각은 이전 청크와 이어질 수 있으므로 따로 보관
remaining = split[0]
lines = [s.decode() for s in split[1:] if s.strip()] + lines
# 남은 조각 처리
if remaining.strip():
lines = [remaining.decode()] + lines
return lines[-n:]
class TradingBot:
def __init__(self, config: Config, symbol: str = None, risk: RiskManager = None):
self.config = config
@@ -68,17 +96,19 @@ class TradingBot:
return _TRADE_HISTORY_DIR / f"{self.symbol.lower()}.jsonl"
def _restore_trade_history(self) -> None:
"""부팅 시 파일에서 거래 이력을 복원한다."""
"""부팅 시 파일 마지막 N줄만 읽어 거래 이력을 복원한다.
킬스위치 판단에 필요한 최대 윈도우(_SLOW_KILL_WINDOW)만큼만 유지."""
path = self._trade_history_path()
if not path.exists():
return
try:
with open(path) as f:
for line in f:
line = line.strip()
if line:
self._trade_history.append(json.loads(line))
logger.info(f"[{self.symbol}] 거래 이력 복원: {len(self._trade_history)}")
tail_n = max(_FAST_KILL_STREAK, _SLOW_KILL_WINDOW)
lines = _tail_lines(path, tail_n)
for line in lines:
line = line.strip()
if line:
self._trade_history.append(json.loads(line))
logger.info(f"[{self.symbol}] 거래 이력 복원: {len(self._trade_history)}건 (최근 {tail_n}건)")
except Exception as e:
logger.warning(f"[{self.symbol}] 거래 이력 복원 실패: {e}")
@@ -101,6 +131,10 @@ class TradingBot:
"ts": datetime.now(timezone.utc).isoformat(),
}
self._trade_history.append(record)
# 메모리에는 킬스위치 윈도우만큼만 유지
max_window = max(_FAST_KILL_STREAK, _SLOW_KILL_WINDOW)
if len(self._trade_history) > max_window * 2:
self._trade_history = self._trade_history[-max_window:]
# 파일에 append (JSONL)
try:
_TRADE_HISTORY_DIR.mkdir(parents=True, exist_ok=True)