fix: address code review round 2 — 9 issues (2 critical, 3 important, 4 minor)

Critical:
- #2: Add _entry_lock in RiskManager to serialize concurrent entry (balance race)
- #3: Add startTime to get_recent_income + record _entry_time_ms (SYNC PnL fix)

Important:
- #1: Add threading.Lock + _run_api() helper for thread-safe Client access
- #4: Convert reset_daily to async with lock
- #8: Add 24h TTL to exchange_info_cache

Minor:
- #7: Remove duplicate Indicators creation in _open_position (use ATR directly)
- #11: Add input validation for LEVERAGE, MARGIN ratios, ML_THRESHOLD
- #12: Replace hardcoded corr[0]/corr[1] with dict-based dynamic access
- #14: Add fillna(0.0) to LightGBM path for NaN consistency with ONNX

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
21in7
2026-03-21 17:26:15 +09:00
parent e3623293f7
commit 41b0aa3f28
11 changed files with 291 additions and 99 deletions

View File

@@ -1,3 +1,4 @@
import asyncio
import pytest
import os
from src.risk_manager import RiskManager
@@ -137,3 +138,31 @@ async def test_max_positions_global_limit(shared_risk):
await shared_risk.register_position("XRPUSDT", "LONG")
await shared_risk.register_position("TRXUSDT", "SHORT")
assert await shared_risk.can_open_new_position("DOGEUSDT", "LONG") is False
@pytest.mark.asyncio
async def test_reset_daily_with_lock(shared_risk):
"""reset_daily가 lock 하에서 PnL을 초기화한다."""
await shared_risk.close_position("DUMMY", 5.0) # dummy 기록
shared_risk.open_positions.clear() # clean up
assert shared_risk.daily_pnl == 5.0
await shared_risk.reset_daily()
assert shared_risk.daily_pnl == 0.0
@pytest.mark.asyncio
async def test_entry_lock_serializes_access(shared_risk):
"""_entry_lock이 동시 접근을 직렬화하는지 확인."""
order = []
async def simulated_entry(name: str):
async with shared_risk._entry_lock:
order.append(f"{name}_start")
await asyncio.sleep(0.05)
order.append(f"{name}_end")
await asyncio.gather(simulated_entry("A"), simulated_entry("B"))
# 직렬화 확인: A_start, A_end, B_start, B_end 또는 B_start, B_end, A_start, A_end
assert order[0].endswith("_start")
assert order[1].endswith("_end")
assert order[0][0] == order[1][0] # 같은 이름으로 시작/끝