# AutoCoinTrader Code Review Report (v5) ## 1. 개요 (Overview) 본 보고서는 `AutoCoinTrader` 프로젝트의 **전체 코드베이스**에 대한 **v5 종합 심층 분석**입니다. v4 리포트 이후의 변경사항을 반영하고, **Python 전문가** 및 **전문 암호화폐 트레이더** 관점에서 단계별로 꼼꼼하게 검토하였습니다. **분석 범위**: - 14개 핵심 소스 모듈 (총 ~5,500줄) - 15개 테스트 파일 - 아키텍처, 코드 품질, 성능, 안정성, 트레이딩 로직, 리스크 관리 **분석 방법론**: 1. 정적 코드 분석 (Static Analysis) 2. 논리 흐름 추적 (Control Flow Analysis) 3. 동시성 패턴 검토 (Concurrency Review) 4. 트레이딩 전략 유효성 검토 (Strategy Validation) --- ## 2. 🚨 긴급 수정 필요 사항 (CRITICAL Issues) ### CRITICAL-001: `order.py` 구문 오류 (Syntax Error) **파일**: `src/order.py` (라인 789-792) **문제점**: ```python # 현재 코드 (오류) if attempt == max_retries: raise time.sleep(ORDER_RETRY_DELAY) continue # ← IndentationError: 들여쓰기 오류 except (requests.exceptions.RequestException, ValueError, TypeError, OSError) as e: ``` **영향**: - **Python 인터프리터 오류**: 이 파일을 import하면 `IndentationError` 발생 - **전체 시스템 작동 불가**: 매도 주문 기능 완전 실패 **수정 방안**: ```python # 수정된 코드 if attempt == max_retries: raise time.sleep(ORDER_RETRY_DELAY) continue except (requests.exceptions.RequestException, ValueError, TypeError, OSError) as e: ``` **우선순위**: 🔴 **즉시 수정 필수** (P0) --- ### CRITICAL-002: `holdings.py` 중복 return 문 **파일**: `src/holdings.py` (라인 510-513) **문제점**: ```python if not new_holdings_map: return {} return {} # ← 접근 불가능한 코드 (dead code) ``` **영향**: - 코드는 실행되지만, 죽은 코드(dead code)가 존재 - 유지보수 혼란 및 코드 품질 저하 **수정 방안**: 중복 `return {}` 제거 **우선순위**: 🟡 **빠른 수정 권장** (P1) --- ## 3. Python 전문가 관점 분석 (Technical Review) ### 3.1 아키텍처 및 디자인 패턴 ⭐⭐⭐⭐⭐ #### ✅ 우수한 점 | 패턴 | 적용 내용 | 평가 | |------|-----------|------| | **단일 책임 원칙 (SRP)** | 각 모듈이 명확한 역할 수행 (`order.py`=주문, `signals.py`=신호, `holdings.py`=보유관리) | Excellent | | **불변 설정 객체** | `RuntimeConfig` dataclass (frozen=True)로 설정 불변성 보장 | Best Practice | | **상태 분리** | `StateManager`로 봇 상태와 거래소 캐시 분리 | v4 대비 개선 | | **원자적 파일 쓰기** | 임시 파일 → `os.replace()` → `os.fsync()` 패턴 일관 적용 | Production Ready | #### ⚠️ 개선 필요 영역 **1. 순환 의존성 잠재 리스크 (Medium)** ```mermaid graph TD A[order.py] --> B[holdings.py] A --> C[signals.py] A --> D[common.py] B --> E[state_manager.py] C --> A C --> B ``` - `order.py` ↔ `signals.py` 간 상호 참조 존재 - `TYPE_CHECKING`으로 런타임 순환 회피 중이나, 리팩토링 시 주의 필요 **2. 모듈 크기 불균형 (Low)** | 모듈 | 라인 수 | 권장 | |------|---------|------| | `order.py` | 1,289 | ⚠️ 500~700 권장 (분할 고려) | | `signals.py` | 960 | ⚠️ 분할 고려 | | `holdings.py` | 700 | ✅ 적정 | | `common.py` | 413 | ✅ 적정 | **권장**: `order.py`를 `order_buy.py`, `order_sell.py`, `order_monitor.py`로 분할 --- ### 3.2 코드 품질 및 스타일 ⭐⭐⭐⭐ #### ✅ 우수한 점 **1. 타입 힌팅 (Type Hinting) - 95%+ 커버리지** ```python def place_buy_order_upbit(market: str, amount_krw: float, cfg: RuntimeConfig) -> dict: ``` **2. 정밀도 관리 (Precision Handling)** ```python # Decimal 사용으로 부동소수점 오차 방지 getcontext().prec = 28 d_price = Decimal(str(price)) volume = (d_amount / d_price).quantize(Decimal("0.00000001"), rounding=ROUND_DOWN) ``` **3. 방어적 프로그래밍 (Defensive Programming)** ```python # None-safe 포매팅 def _safe_format(value, precision: int = 2, default: str = "N/A") -> str: if value is None: return default if pd.isna(value): return default ``` #### ⚠️ 개선 필요 영역 **1. 광범위한 Exception 처리 (High)** 현재 여러 곳에서 `Exception` 전체를 catch하고 있음: ```python # 문제점: 예상치 못한 버그를 숨길 수 있음 except Exception as e: logger.error("오류: %s", e) return None ``` **권장**: 구체적 예외 타입 지정 ```python except (requests.exceptions.RequestException, json.JSONDecodeError, ValueError) as e: logger.error("알려진 오류: %s", e) except Exception as e: logger.exception("예상치 못한 오류 - 재발생: %s", e) raise # 또는 특정 처리 ``` **영향 범위**: - `holdings.py`: 9개소 - `order.py`: 12개소 - `signals.py`: 7개소 **2. 매직 넘버 하드코딩 (Medium)** ```python # 현재 time.sleep(0.5) # 무슨 의미? if len(calls) >= 590: # 왜 590? # 권장 TELEGRAM_RATE_LIMIT_DELAY = 0.5 # Telegram API 초당 제한 대응 UPBIT_MINUTE_RATE_LIMIT = 590 # Upbit 분당 600회 제한의 안전 마진 ``` **3. 일관성 없는 로그 포맷 (Low)** ```python # 혼재된 스타일 logger.info("[INFO] [%s] 매수 성공", symbol) # [INFO] 중복 logger.info("[%s] 매수 성공", symbol) # 권장 스타일 logger.info(f"[{symbol}] 매수 성공") # f-string 스타일 ``` --- ### 3.3 동시성 및 스레드 안전성 ⭐⭐⭐⭐⭐ #### ✅ 우수한 점 **1. 리소스별 Lock 분리 (Best Practice)** ```python # 각 리소스에 전용 Lock 할당 → 데드락 위험 최소화 holdings_lock = threading.RLock() # holdings.json 보호 _state_lock = threading.RLock() # bot_state.json 보호 _cache_lock = threading.Lock() # 가격/잔고 캐시 보호 _pending_order_lock = threading.Lock() # 대기 주문 보호 krw_balance_lock = threading.RLock() # KRW 잔고 조회 직렬화 recent_sells_lock = threading.RLock() # recent_sells.json 보호 ``` **2. KRW 예산 관리자 (Token 기반)** ```python class KRWBudgetManager: """동일 심볼 다중 주문도 안전하게 지원""" def allocate(self, symbol, amount_krw, ...) -> tuple[bool, float, str | None]: # 고유 토큰으로 각 주문 구분 token = secrets.token_hex(8) ``` **3. Rate Limiter (Token Bucket)** ```python # 초당 8회 + 분당 590회 동시 제한 api_rate_limiter = RateLimiter( max_calls=8, period=1.0, additional_limits=[(590, 60.0)] ) ``` #### ⚠️ 개선 필요 영역 **1. Lock 획득 순서 미문서화 (Medium)** 여러 Lock을 동시에 획득하는 경우가 있으나, 획득 순서가 문서화되지 않음. ```python # 잠재적 데드락 시나리오 # Thread A: holdings_lock → _state_lock # Thread B: _state_lock → holdings_lock # 권장: Lock 획득 순서 규약 문서화 # 1. holdings_lock # 2. _state_lock # 3. _cache_lock ``` **2. 캐시 만료 시 경합 (Low)** ```python # 캐시 TTL 만료 시 여러 스레드가 동시에 API 호출 가능 if (now - ts) > PRICE_CACHE_TTL: # 여러 스레드가 이 조건을 동시에 통과할 수 있음 price = pyupbit.get_current_price(market) ``` **권장**: Double-checked locking 또는 cache stampede prevention --- ### 3.4 예외 처리 및 회복력 ⭐⭐⭐⭐⭐ #### ✅ 우수한 점 **1. Circuit Breaker 패턴** ```python class CircuitBreaker: """API 장애 시 자동 차단""" STATES = ["closed", "open", "half_open"] # 연속 3회 실패 → 5분 차단 → 점진적 복구 ``` **2. 지수 백오프 재시도 (Exponential Backoff with Jitter)** ```python sleep_time = base_backoff * (2 ** (attempt - 1)) sleep_time += random.uniform(0, jitter_factor * sleep_time) ``` **3. ReadTimeout 복구 로직** ```python except requests.exceptions.ReadTimeout: # 1단계: 중복 주문 확인 is_dup, dup_order = _has_duplicate_pending_order(...) if is_dup: return dup_order # 중복 방지 # 2단계: 최근 주문 조회 found = _find_recent_order(...) if found: return found # 이미 체결된 주문 반환 ``` #### ⚠️ 개선 필요 영역 **1. 중복 주문 검증 정확도 (Medium)** ```python # 현재: 수량/가격만으로 중복 판단 if abs(order_vol - volume) < 1e-8: if price is None or abs(order_price - price) < 1e-4: return True, order # 문제: 다른 사유로 동일 수량/가격 주문이 존재할 수 있음 # 권장: UUID 캐시 또는 client_order_id 사용 ``` --- ### 3.5 테스트 커버리지 분석 ⭐⭐⭐⭐ #### ✅ 테스트 파일 현황 (15개) | 테스트 파일 | 대상 모듈 | 커버리지 | |------------|----------|---------| | `test_order.py` | `order.py` | 핵심 기능 | | `test_evaluate_sell_conditions.py` | `signals.py` | 매도 조건 | | `test_krw_budget_manager.py` | `common.py` | 예산 관리 | | `test_concurrent_buy_orders.py` | 동시성 로직 | 경합 조건 | | `test_circuit_breaker.py` | `circuit_breaker.py` | 복구 로직 | | `test_state_reconciliation.py` | `holdings.py`/`state_manager.py` | 상태 동기화 | | `test_boundary_conditions.py` | 다수 | 경계값 | | `test_critical_fixes.py` | 다수 | 주요 버그픽스 | | `test_recent_sells.py` | `common.py` | 재매수 방지 | | `test_holdings_cache.py` | `holdings.py` | 캐시 로직 | | ... | | | #### ⚠️ 개선 필요 영역 **1. 통합 테스트 부재 (High)** - 현재: 대부분 단위 테스트 - 권장: 매수→보유→매도 전체 플로우 통합 테스트 **2. 엣지 케이스 미커버 (Medium)** - 네트워크 단절 중 여러 주문 동시 발생 - API Rate Limit 도달 시 동작 - 파일 시스템 권한 오류 **3. 모킹 의존도 높음 (Low)** - `pyupbit` 모킹으로 실제 API 동작 검증 불가 - 권장: 별도 샌드박스 환경 테스트 --- ## 4. 전문 트레이더 관점 분석 (Trading Logic Review) ### 4.1 진입 전략 (Entry Strategy) ⭐⭐⭐⭐ #### ✅ 복합 매수 조건 (Triple Confirmation) ```mermaid graph TD A[매수조건1] -->|MACD 상향 돌파| B{SMA5 > SMA200} B -->|Yes| C{ADX > 25} C -->|Yes| D[매수 신호] E[매수조건2] -->|SMA 골든크로스| F{MACD > Signal} F -->|Yes| G{ADX > 25} G -->|Yes| D H[매수조건3] -->|ADX 상향 돌파| I{SMA5 > SMA200} I -->|Yes| J{MACD > Signal} J -->|Yes| D ``` **분석**: - ✅ 다중 확인으로 False Signal 감소 - ✅ ADX 필터로 추세 확인 (횡보장 회피) - ⚠️ 보수적 접근으로 진입 기회 감소 가능 #### ✅ 재매수 방지 (Rebuy Cooldown) ```python def can_buy(symbol: str, cooldown_hours: int = 24) -> bool: """매도 후 24시간 쿨다운""" ``` **장점**: 감정적 재진입 방지, Pump & Dump 피해 최소화 #### ⚠️ 개선 필요 영역 **1. 볼륨 확인 부재 (High)** 현재 MACD/SMA/ADX만 확인하고 **거래량 확인 없음**: ```python # 현재 cross_macd_signal = prev_macd < prev_signal and curr_macd > curr_signal # 권장: 거래량 동반 확인 volume_surge = curr_volume > sma_volume * 1.5 # 평균 대비 150% valid_signal = cross_macd_signal and volume_surge ``` **2. 시장 상태 필터 부재 (Medium)** ```python # 권장: 비트코인 방향성 확인 def is_btc_bullish(): btc_data = fetch_ohlcv("KRW-BTC", "1d", 20) return btc_data["close"].iloc[-1] > btc_data["close"].rolling(20).mean().iloc[-1] ``` **3. 진입 가격 최적화 부재 (Low)** - 현재: 신호 발생 시 시장가/지정가 즉시 매수 - 권장: VWAP 또는 지지선 근처 지정가 대기 --- ### 4.2 청산 전략 (Exit Strategy) ⭐⭐⭐⭐⭐ #### ✅ 계층적 트레일링 스탑 (Tiered Trailing Stop) ```python # 구간별 차등 스탑 설정 저수익 구간 (< 10%): 최고점 대비 -5% → 전량 매도 중간 구간 (10~30%): 수익률 10% 이하 복귀 시 전량 매도 또는 최고점 대비 -5% → 전량 매도 고수익 구간 (> 30%): 수익률 30% 이하 복귀 시 전량 매도 또는 최고점 대비 -15% → 전량 매도 ``` **분석**: - ✅ 수익 구간별 차등 보호 (전문가 수준) - ✅ 상승 여력 확보하면서 수익 보호 - ✅ max_price 영구 저장으로 재시작 시에도 유지 #### ✅ 분할 매도 (Partial Profit Taking) ```python # 10% 달성 시 50% 부분 익절 (1회 제한) if not partial_sell_done and profit_rate >= 10.0: return {"status": "stop_loss", "sell_ratio": 0.5, "set_partial_sell_done": True} ``` **분석**: - ✅ 리스크 감소 + 나머지로 큰 수익 추구 - ✅ `partial_sell_done` 플래그로 중복 방지 - ✅ 최소 주문 금액 미만 시 자동 전량 매도 전환 #### ✅ 손절 즉시 실행 ```python # is_stop_loss 플래그로 확인 절차 건너뜀 bypass_confirmation = not confirm_via_file or (final_is_stop_loss and not confirm_stop_loss) ``` #### ⚠️ 개선 필요 영역 **1. ATR 기반 동적 스탑 미적용 (Medium)** ```python # 현재: 고정 퍼센티지 drawdown_1 = 5.0 # 모든 코인에 동일 # 권장: ATR 기반 동적 스탑 def calculate_dynamic_stop(symbol, atr_multiplier=2.0): atr = ta.atr(df["high"], df["low"], df["close"], length=14).iloc[-1] current_price = df["close"].iloc[-1] stop_distance = (atr / current_price) * atr_multiplier * 100 return max(3.0, min(stop_distance, 15.0)) # 3~15% 범위 제한 ``` **2. 시간 기반 청산 미적용 (Low)** - 현재: 가격 조건만 확인 - 권장: 장기 횡보 시 기회비용 고려 청산 --- ### 4.3 리스크 관리 (Risk Management) ⭐⭐⭐⭐ #### ✅ 우수한 점 | 리스크 관리 항목 | 구현 상태 | 평가 | |----------------|----------|------| | API 장애 대응 (Circuit Breaker) | ✅ | Excellent | | KRW 잔고 경쟁 방지 | ✅ | Excellent | | 부분 매수 지원 | ✅ | Good | | Rate Limiting | ✅ | Excellent | | 슬리피지 관리 | ✅ | Good | | 최소 주문 금액 검증 | ✅ | Excellent | #### ⚠️ 개선 필요 영역 **1. 최대 보유 종목 수 제한 없음 (High)** ```python # 현재: symbols.txt의 모든 심볼 매수 가능 # 문제: 과도한 분산 투자 → 관리 어려움 # 권장: 최대 보유 종목 수 제한 MAX_HOLDINGS = 5 def can_open_new_position(holdings): return len(holdings) < MAX_HOLDINGS ``` **2. 포트폴리오 손실 한도 부재 (High)** ```python # 권장: 일일/주간 손실 한도 모니터링 def check_portfolio_drawdown(holdings, initial_balance): current_value = calculate_portfolio_value(holdings) drawdown = (current_value - initial_balance) / initial_balance * 100 if drawdown <= -20: # 20% 손실 시 logger.error("포트폴리오 손실 한도 도달: %.2f%%", drawdown) # 신규 매수 차단 또는 전량 청산 ``` **3. 심볼별 상관관계 미고려 (Medium)** - 현재: 각 심볼 독립적으로 처리 - 문제: BTC 하락 시 대부분 알트코인 동반 하락 - 권장: 상관관계 높은 종목 그룹화하여 동시 보유 제한 **4. 변동성 기반 포지션 사이징 미적용 (Medium)** ```python # 현재: 고정 금액 buy_amount_krw = 50000 # 권장: 변동성 역비례 사이징 def calculate_position_size(symbol, base_amount, max_volatility=5.0): volatility = calculate_volatility(symbol) # ATR 기반 if volatility > max_volatility: return base_amount * (max_volatility / volatility) return base_amount ``` --- ## 5. v4 → v5 변경사항 및 신규 발견 ### 🟢 v4 이후 확인된 개선사항 1. **StateManager 안정화**: `max_price` 영구 저장 안정적 동작 2. **Stop-Loss 즉시 실행**: `is_stop_loss` 플래그 정상 작동 3. **KRW 예산 관리**: 토큰 기반 독립 할당으로 동시 주문 안전 4. **테스트 커버리지 증가**: 15개 테스트 파일 ### 🔴 v5에서 새로 발견된 문제 | ID | 심각도 | 문제 | 영향 | |----|--------|------|------| | CRITICAL-001 | 🔴 Critical | `order.py` 구문 오류 | 시스템 작동 불가 | | CRITICAL-002 | 🟡 High | `holdings.py` 중복 return | 죽은 코드 | | HIGH-001 | 🟡 High | 광범위한 Exception 처리 | 버그 은폐 가능 | | HIGH-002 | 🟡 High | 최대 보유 종목 수 제한 없음 | 과도한 분산 | | HIGH-003 | 🟡 High | 포트폴리오 손실 한도 부재 | 대손 리스크 | | HIGH-004 | 🟡 High | 볼륨 확인 부재 (매수) | False Signal | | MEDIUM-001 | 🟠 Medium | Lock 획득 순서 미문서화 | 잠재적 데드락 | | MEDIUM-002 | 🟠 Medium | 매직 넘버 하드코딩 | 유지보수 어려움 | | MEDIUM-003 | 🟠 Medium | ATR 동적 스탑 미적용 | 비최적 청산 | --- ## 6. 코드 품질 지표 요약 | 항목 | v4 평가 | v5 평가 | 변화 | |------|---------|---------|------| | 아키텍처 설계 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | 유지 | | 타입 안전성 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | 유지 | | 예외 처리 | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | 유지 (개선 필요) | | 동시성 안전성 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | 유지 | | 테스트 커버리지 | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | 유지 (15개 파일) | | 코드 품질 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⬇️ (구문 오류 발견) | | 트레이딩 로직 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⬇️ (볼륨 미확인) | | 리스크 관리 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⬇️ (포트폴리오 관리 부재) | **종합 평가**: ⭐⭐⭐⭐ (4.2/5.0) - v4 대비 0.5점 하락 (구문 오류 발견으로 인한 감점) --- ## 7. 우선순위별 권장사항 ### 🚨 즉시 수정 (P0 - 24시간 이내) 1. **CRITICAL-001 수정**: `order.py` 구문 오류 해결 2. **CRITICAL-002 수정**: `holdings.py` 중복 return 제거 ### 🔴 긴급 개선 (P1 - 1주 이내) 3. **최대 보유 종목 수 제한** 추가 (`MAX_HOLDINGS = 5`) 4. **포트폴리오 손실 한도** 모니터링 추가 5. **광범위한 Exception 처리** 구체화 ### 🟡 단기 개선 (P2 - 1개월 이내) 6. **거래량 확인** 로직 추가 (매수 신호) 7. **ATR 기반 동적 스탑** 구현 8. **Lock 획득 순서** 문서화 9. **통합 테스트** 추가 ### 🟢 중장기 개선 (P3 - 분기 이내) 10. **`order.py` 모듈 분할** (1,289줄 → 3개 파일) 11. **백테스팅 프레임워크** 구축 12. **매직 넘버 상수화** 13. **심볼 상관관계 분석** 추가 --- ## 8. 파일별 상세 분석 ### 핵심 모듈 (Critical Path) | 파일 | 라인 수 | 역할 | 품질 | 주요 이슈 | |------|---------|------|------|----------| | `order.py` | 1,289 | 주문 실행 | ⚠️ | 구문 오류, 크기 과대 | | `signals.py` | 960 | 신호 분석 | ✅ | 크기 적정 초과 | | `holdings.py` | 700 | 보유 관리 | ✅ | 중복 return 문 | | `state_manager.py` | 106 | 상태 관리 | ✅✅ | 없음 | | `common.py` | 413 | 공통 유틸 | ✅✅ | 없음 | | `config.py` | 328 | 설정 관리 | ✅✅ | 없음 | ### 보조 모듈 | 파일 | 라인 수 | 역할 | 품질 | |------|---------|------|------| | `indicators.py` | 172 | 기술 지표 | ✅✅ | | `notifications.py` | 183 | 알림 전송 | ✅ | | `circuit_breaker.py` | 79 | 장애 복구 | ✅✅ | | `retry_utils.py` | 62 | 재시도 유틸 | ✅✅ | | `constants.py` | 60 | 상수 정의 | ✅✅ | | `threading_utils.py` | 207 | 스레드 유틸 | ✅ | | `metrics.py` | 30 | 메트릭 수집 | ⚠️ (미사용) | | `main.py` | 388 | 엔트리포인트 | ✅ | --- ## 9. 결론 ### ✅ 강점 - **아키텍처**: 모듈화, 단일 책임 원칙, 상태 분리 우수 - **동시성**: RLock, 토큰 기반 예산 관리, Rate Limiter 완비 - **정밀도**: Decimal 기반 계산, 원자적 파일 쓰기 - **복구력**: Circuit Breaker, 지수 백오프, ReadTimeout 복구 ### ⚠️ 개선 필요 - **긴급**: 구문 오류 수정 필수 (시스템 작동 불가) - **리스크 관리**: 포트폴리오 레벨 관리 부재 - **트레이딩**: 거래량 확인 및 동적 스탑 미적용 - **코드 품질**: 광범위한 Exception, 모듈 크기 과대 ### 🎯 최종 권장사항 1. **즉시**: CRITICAL-001/002 수정 후 재배포 2. **1주 내**: 최대 보유 종목 및 손실 한도 추가 3. **1개월 내**: 거래량 확인 및 통합 테스트 추가 4. **장기**: 백테스팅으로 전략 검증 후 파라미터 최적화 --- **보고서 작성일**: 2025-12-10 **작성자**: AI Code Reviewer (Python Expert + Crypto Trader Perspective) **버전**: v5.0