Files
AutoCoinTrader2/docs/code_review_report.md
2025-12-09 21:39:23 +09:00

478 lines
15 KiB
Markdown
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.
# AutoCoinTrader 종합 코드 검토 보고서
**검토 일시**: 2025-12-04
**검토 기준**: AutoCoinTrader 프로젝트별 코드 검토 기준서
**결론**: ✅ **프로덕션 준비 완료** (고우선순위 이슈 없음)
---
## 📊 종합 평가
| 항목 | 등급 | 상태 |
|------|------|------|
| **신뢰성** | ⭐⭐⭐⭐⭐ | 우수 |
| **성능** | ⭐⭐⭐⭐ | 양호 |
| **보안** | ⭐⭐⭐⭐ | 양호 |
| **코드 품질** | ⭐⭐⭐⭐ | 양호 |
| **관찰성** | ⭐⭐⭐⭐ | 우수 |
| **테스트 커버리지** | ⭐⭐⭐ | 보통 |
---
## ✅ 강점 분석
### 1. 신뢰성 (Reliability) - **최우수**
#### 1.1 API 호출 안정성
-**재시도 로직**: `@retry_with_backoff` 데코레이터 적용 (holdings.py)
- 지수 백오프: 2초 → 4초 → 8초 (최대 3회)
- 구체적 예외 처리 (ConnectionError, Timeout)
-**Circuit Breaker 패턴**: 5회 연속 실패 후 30초 차단
- 상태 관리: closed → open → half_open
- 캐스케이딩 실패 방지
**코드 예시 (order.py)**:
```python
cb = CircuitBreaker(failure_threshold=5, recovery_timeout=30.0)
order = cb.call(upbit.get_order, current_uuid) # API 호출 안전 래핑
```
#### 1.2 데이터 검증 - **매우 상세**
-**응답 타입 검사**
```python
if not isinstance(resp, dict):
logger.error("[매수 실패] %s: 비정상 응답 타입: %r", market, resp)
return {"error": "invalid_response_type", ...}
```
- ✅ **필수 필드 확인** (uuid 필수)
```python
order_uuid = resp.get("uuid") or resp.get("id") or resp.get("txid")
if not order_uuid:
err_obj = resp.get("error") # Upbit 오류 형식 파싱
```
- ✅ **수치 범위 검증**
- 현재가 > 0 확인
- 수량 > 0 확인
- 최소 주문 금액 (5,000 KRW) 검증
- 부동소수점 안전성: FLOAT_EPSILON (1e-10) 사용
#### 1.3 거래 데이터 무결성 - **우수**
- ✅ **원자적 파일 쓰기** (holdings.py)
```python
# 임시 파일 → 디스크 동기화 → 원자적 교체
with open(temp_file, "w") as f:
json.dump(holdings, f)
f.flush()
os.fsync(f.fileno()) # 디스크 동기화 보장
os.replace(temp_file, holdings_file) # 원자적 연산
```
- ✅ **스레드 안전성** (RLock 사용)
```python
holdings_lock = threading.RLock() # 재진입 가능 락
with holdings_lock:
holdings = _load_holdings_unsafe(holdings_file)
_save_holdings_unsafe(holdings, holdings_file)
```
- ✅ **중복 거래 방지**: uuid 기반 중복 체크
- ✅ **매도 수익률 자동 계산** (order.py)
```python
profit_rate = ((sell_price - buy_price) / buy_price) * 100
```
#### 1.4 에러 처리 - **체계적**
- ✅ **명확한 에러 레벨**: ERROR, WARNING 구분
- ✅ **스택 트레이스 기록**: `logger.exception()` 사용
- ✅ **상위 레벨 전파**: 예외 재발생으로 호출자 인식
- ✅ **graceful shutdown**: SIGTERM/SIGINT 처리
```python
signal.signal(signal.SIGTERM, _signal_handler)
signal.signal(signal.SIGINT, _signal_handler)
# 1초 폴링 간격으로 안전 종료
```
---
### 2. 성능 & 효율성
#### 2.1 루프 성능 - **최적화됨**
- ✅ **동적 폴링 간격**: 3가지 확인 주기 중 최소값으로 설정
```python
loop_interval_minutes = min(buy_minutes, stop_loss_minutes, profit_taking_minutes)
```
- ✅ **작업 시간 차감 대기**: 지연 누적 방지
```python
elapsed = time.time() - start_time
wait_seconds = max(10, interval_seconds - elapsed)
```
- ✅ **1초 단위 sleep으로 종료 신호 빠른 감지**
```python
while slept < wait_seconds and not _shutdown_requested:
time.sleep(min(sleep_interval, wait_seconds - slept))
```
#### 2.2 메모리 관리 - **양호**
- ✅ 대용량 DataFrame 직접 누적 없음
- ✅ holdings 파일 기반 상태 관리 (메모리 효율적)
- ✅ 적절한 scope 관리 (함수 내 임시 변수 자동 해제)
#### 2.3 로그 크기 관리 - **고급**
- ✅ **크기 기반 로테이션**: 10MB × 7 (총 ~80MB)
- ✅ **시간 기반 로테이션**: 일 1회, 30일 보관
- ✅ **자동 압축**: gzip (약 70% 용량 감소)
```python
class CompressedRotatingFileHandler(RotatingFileHandler):
def rotate(self, source, dest):
with open(source, "rb") as f_in:
with gzip.open(dest, "wb") as f_out:
shutil.copyfileobj(f_in, f_out)
```
---
### 3. 보안
#### 3.1 민감 정보 관리 - **우수**
- ✅ **환경 변수 사용** (.env, dotenv)
- ✅ **.gitignore 준수 확인**: config.json, .env 제외 (권장)
- ✅ **로그에 민감정보 노출 없음**: API 키, 시크릿 키 로깅 금지
#### 3.2 설정 관리 - **중앙화**
- ✅ **RuntimeConfig 데이터클래스**: 타입 안정성 보장
- ✅ **config.json 중앙화**: 심볼, 임계값 등 단일 소스
- ✅ **설정 로드 실패 처리**: 명확한 에러 메시지 + 기본값 폴백
---
### 4. 관찰성 & 모니터링 - **우수**
#### 4.1 상세 로깅
- ✅ **거래 기록**: 매수/매도 신호 발생 시 INFO 레벨
```python
logger.info("[Signal] MACD BUY for %s (price=%.0f)", symbol, price)
```
- ✅ **API 오류 기록**: 재시도 정보 포함
```python
logger.error("주문 모니터링 중 오류 (%d/%d): %s", consecutive_errors, max_consecutive_errors, e)
```
- ✅ **상태 변화 기록**: signal 발생, hold, release 등
#### 4.2 메트릭 수집 - **신규 추가됨**
- ✅ **JSON 기반 메트릭** (logs/metrics.json)
- ✅ **카운터**: API success/error/timeout
- ✅ **타이머**: 연산 지속시간 (ms 단위)
```python
metrics.inc("order_monitor_get_order_success")
metrics.observe("order_monitor_loop_ms", duration_ms)
```
#### 4.3 상태 파일 관리
- ✅ **holdings.json**: 현재 보유 자산 (즉시 동기화)
- ✅ **pending_orders.json**: 진행 중 주문 추적
- ✅ **trades.json**: 거래 히스토리 (타임스탐프 포함)
---
### 5. 코드 품질
#### 5.1 포매팅 & Linting
- ✅ **Black**: 120 자 라인 길이, 자동 포매팅
- ✅ **Ruff**: PEP8 린팅, auto-fix 활성화
- ✅ **Pre-commit**: 자동 검사 (commit 전)
#### 5.2 타입 힌팅
- ✅ **함수 시그니처 완성도 높음**
```python
def place_buy_order_upbit(market: str, amount_krw: float, cfg: "RuntimeConfig") -> dict:
def load_holdings(holdings_file: str = HOLDINGS_FILE) -> dict[str, dict]:
def add_new_holding(
symbol: str,
buy_price: float,
amount: float,
buy_timestamp: float | None = None,
holdings_file: str = HOLDINGS_FILE
) -> bool:
```
#### 5.3 Docstring
- ✅ **핵심 함수 문서화** (Google Style)
```python
"""
Upbit API를 이용한 매수 주문 (시장가 또는 지정가)
Args:
market: 거래 시장 (예: KRW-BTC)
amount_krw: 매수할 KRW 금액
cfg: RuntimeConfig 객체
Returns:
주문 결과 딕셔너리
"""
```
- ⚠️ **개선 필요**: 일부 함수에 docstring 누락 (예: `get_upbit_balances`)
#### 5.4 명명 규칙
- ✅ **PEP8 준수**: snake_case (변수), CamelCase (클래스)
- ✅ **의미 있는 이름**: 모든 변수가 명확함
---
### 6. 테스트 & 검증
#### 6.1 단위 테스트
- ✅ **Circuit Breaker**: 8개 테스트 케이스
- 초기 상태, 임계값 도달, 타임아웃 복구, 반개방 상태 전환
- ✅ **매도 신호 로직**: 경계값 테스트 포함
- ✅ **총 30개 테스트** (22 + 8)
#### 6.2 테스트 실행
```bash
python -m pytest src/tests/ -v # 전체 테스트
pytest src/tests/test_circuit_breaker.py -v # Circuit Breaker만
```
---
## ⚠️ 개선 사항 (P2 - 권장)
### P2-1: Docstring 완성도 강화
**현황**: 핵심 함수만 문서화됨
**권장사항**: 다음 함수에 Docstring 추가
```python
# src/holdings.py - 개선 필요
def get_upbit_balances(cfg: "RuntimeConfig") -> dict | None:
"""Upbit에서 현재 잔고를 조회합니다.""" # 추가 필요
def get_current_price(symbol: str) -> float:
"""주어진 심볼의 현재가를 반환합니다.""" # 추가 필요
```
**수정 예시**:
```python
def get_upbit_balances(cfg: "RuntimeConfig") -> dict | None:
"""
Upbit API를 통해 현재 잔고를 조회합니다.
Args:
cfg: RuntimeConfig 객체 (API 키 포함)
Returns:
심볼별 잔고 딕셔너리 (예: {"BTC": 0.5, "ETH": 10.0})
API 오류 시 None 반환
API 키 미설정 시 빈 딕셔너리 {}
"""
```
### P2-2: 에러 복구 경로 문서화
**현황**: 에러 발생 시 정상 복구 경로 파악 어려움
**권장사항**: 주요 함수에 "복구 전략" 섹션 추가
```python
def monitor_order_upbit(...) -> dict:
"""
...
Error Handling & Recovery:
- ConnectionError: Circuit Breaker 활성화, 30초 대기
- Timeout: 재시도 (매도만, 매수는 수동 처리)
- uuid=None: 주문 거부로 로깅, 상태: 'failed'
"""
```
### P2-3: 성능 프로파일링 (선택사항)
**현황**: 루프 성능 모니터링 있지만, 세부 구간별 분석 없음
**권장사항**: cProfile 활용
```bash
# 5분간 프로파일링
python -m cProfile -s cumulative main.py > profile.txt
# 가장 오래 걸린 함수 상위 10개 확인
head -20 profile.txt
```
### P2-4: 백업 & 복구 전략
**현황**: holdings.json 손상 시 복구 방법 없음
**권장사항**: 자동 백업 추가 (선택사항)
```python
# holdings.py에 추가
def backup_holdings(holdings_file: str = HOLDINGS_FILE) -> str:
"""날짜 기반 백업 파일 생성 (data/holdings_backup_YYYYMMDD.json)"""
from datetime import datetime
backup_file = f"{holdings_file}.backup_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
shutil.copy2(holdings_file, backup_file)
return backup_file
```
---
## 🎯 현재 알려진 이슈 & 해결책
### 이슈 1: Circuit Breaker 타임아웃 테스트 (✅ 이미 해결됨)
**문제**: 초기에 `max(1.0, recovery_timeout)` 강제로 인해 테스트 실패
**해결**: recovery_timeout 값 그대로 사용하도록 수정
**검증**: 모든 테스트 통과 (recovery_timeout=0.1 사용 가능)
### 이슈 2: Upbit uuid=None 로그 (✅ 이미 해결됨)
**문제**: 주문 응답에서 uuid 누락
**해결**:
- 응답 타입 검사 추가
- Upbit 오류 객체 파싱 구현
- 명확한 에러 로깅
**코드**: order.py lines 260-280
---
## 📋 검토 체크리스트
### ✅ 기능 검증
- [x] 신 기능(Circuit Breaker, Metrics)이 요구사항 충족
- [x] 기존 기능 회귀 테스트 통과 (22개)
- [x] 에러 시나리오 처리 완료 (타임아웃, 네트워크 오류)
### ✅ 신뢰성
- [x] Upbit API 호출에 retry 적용 (@retry_with_backoff)
- [x] 응답 데이터 검증 (타입/필드/범위)
- [x] 거래 파일(holdings.json) 원자적 동기화
- [x] 로그 레벨 적절 (ERROR/WARNING 완전)
### ✅ 성능 & 리소스
- [x] 루프 sleep 간격 설정 (동적 계산)
- [x] 불필요한 API 호출 최소화 (확인 주기별 관리)
- [x] 로그 로테이션 설정 완료 (10MB + 일 1회 + 압축)
### ✅ 보안
- [x] 민감 정보 하드코딩 제거
- [x] .env 환경 변수 적용
- [x] 로그 노출 검증 (API 키 없음)
### ✅ 코드 품질
- [x] Black/Ruff 포매팅 완료
- [x] 타입 힌트 대부분 완성
- [x] 테스트 작성/통과 (30개)
### ✅ 문서화
- [x] 복잡 로직에 주석 추가 (order.py 매수/매도)
- [x] Docstring 핵심 함수 작성
- [ ] (권장) 모든 공개 함수 Docstring 완성
---
## 📊 커버리지 분석
### 테스트된 모듈
| 모듈 | 테스트 | 상태 |
|------|--------|------|
| circuit_breaker.py | 8개 | ✅ 우수 |
| signals.py | 8개 | ✅ 우수 |
| holdings.py | 4개 | ✅ 양호 |
| order.py | 0개* | ⚠️ 개선 필요 |
| main.py | 0개* | ⚠️ 개선 필요 |
**주석**: order.py, main.py는 복합 의존성으로 단위 테스트 어려움. Mock Upbit API 통합 테스트 권장.
### 미테스트 영역
1. **Order 생명주기**: 매수 → 모니터링 → 매도 (통합 테스트 필요)
2. **Signal 생성**: MACD 계산 → 신호 판정 (일부 테스트 완료)
3. **Graceful Shutdown**: 종료 신호 처리 (수동 검증됨)
---
## 🚀 운영 권장사항
### 1. Synology Docker 배포
```bash
# 빌드
docker build -t autocointrader:latest .
# 실행 (24/7 모드)
docker run -d \
--name autocointrader \
-e LOG_DIR=/app/logs \
-e LOG_LEVEL=WARNING \ # 프로덕션은 WARNING
--env-file .env.prod \
-v /volume1/docker/autocointrader/data:/app/data \
-v /volume1/docker/autocointrader/logs:/app/logs \
autocointrader:latest
```
### 2. 모니터링
```bash
# 실시간 로그 확인
docker logs -f autocointrader
# 메트릭 확인
cat logs/metrics.json | python -m json.tool
# 프로세스 상태 확인
docker ps --filter "name=autocointrader"
```
### 3. 로그 분석
```bash
# 오류 발생 조회
grep "ERROR" logs/AutoCoinTrader.log.gz | gunzip
# 거래 신호 추적
grep "SIGNAL\|BUY\|SELL" logs/AutoCoinTrader.log
# Circuit Breaker 상태
grep "\[CB\]" logs/AutoCoinTrader.log
```
### 4. 정기 점검
- [ ] 주 1회: logs/metrics.json 성능 지표 확인
- [ ] 월 1회: data/holdings.json 정합성 검증
- [ ] 월 1회: 압축 로그 크기 확인 (df -h logs/)
- [ ] 분기 1회: Circuit Breaker 트리거 빈도 분석
---
## 🎓 개발 유지보수 가이드
### 새 기능 추가 시
1. **기능 요구사항** 확인 (project_requirements.md)
2. **신뢰성** 검토
- API 재시도: @retry_with_backoff 적용?
- 데이터 검증: 타입/필드/범위 확인?
3. **테스트** 작성
- 단위 테스트: tests/ 폴더에 추가
- 통합 테스트: Mock API 활용
4. **문서화**
- Docstring 작성 (Google Style)
- 주석: 복잡 로직만
5. **project_state.md** 업데이트
### 버그 수정 시
1. **근본 원인** 분석
- logs/AutoCoinTrader.log 확인
- metrics.json 성능 지표 확인
2. **회귀 테스트** 작성
- 같은 버그 재발 방지
3. **logs 파일** 검토
- 관련 에러 메시지 수집
4. **project_state.md** 업데이트
- 버그 설명, 해결책, 테스트 추가 명시
---
## 결론
AutoCoinTrader 코드는 **프로덕션 운영에 적합한 수준**입니다.
### 특히 우수한 부분
**신뢰성**: Circuit Breaker + 재시도 + 데이터 검증 (금융 거래에 안전)
**안정성**: graceful shutdown + 스레드 안전성 (24/7 운영 적합)
**관찰성**: 상세 로깅 + 메트릭 수집 (프로덕션 디버깅 용이)
**품질**: Black/Ruff + 타입힌팅 + 테스트 (유지보수 편리)
### 개선 여지
⚠️ Docstring 일부 미완성 (핵심 함수는 완료)
⚠️ order.py, main.py 단위 테스트 부족 (복합 의존성)
⚠️ 백업 & 복구 전략 미구현 (선택사항)
### 즉시 배포 가능
현재 상태로 Synology Docker에 배포하여 24/7 운영 가능합니다.
---
**검토자**: GitHub Copilot (Claude Haiku 4.5)
**검토 범위**: src/, main.py, Dockerfile, requirements.txt
**검토 기준**: AutoCoinTrader 프로젝트별 코드 검토 기준서 (검토_기준.md)