Files
AutoCoinTrader/docs/v2_vs_v4_review_comparison.md

12 KiB

v2 코드 리뷰 개선사항 검토 및 평가

요약 (Executive Summary)

v2 리포트에서 제안된 5개 Critical + 6개 High 이슈를 현재 코드베이스와 대조하여 검토했습니다.

결과:

  • 완전 해결: 1개 (재매수 쿨다운 파일 락)
  • ⚠️ 부분 해결: 3개 (상태 동기화, OHLCV 캐시, 예외 처리)
  • 미해결: 7개 (나머지 Critical/High 이슈)

1. Critical 이슈 분석 (5개)

CRITICAL-1: 동일 심볼 복수 주문 시 예산 충돌 미해결

v2 지적사항:

KRWBudgetManager가 심볼 단일 슬롯만 보유 → 같은 심볼 복수 주문이 동시에 발생하면 후행 주문이 선행 주문 할당액을 덮어쓰기/해제하며 이중 사용 가능.

현재 상태:

# src/common.py (현재)
class KRWBudgetManager:
    def __init__(self):
        self._lock = threading.RLock()
        self._allocated: dict[str, float] = {}  # {symbol: allocated_amount}

평가: 여전히 문제 존재

  • 구조가 v2 리포트 당시와 동일: {symbol: float} 단일 슬롯
  • 동일 심볼에 대한 복수 주문 시나리오 테스트 부재
  • 현실적 리스크: 낮음 (현재 4시간봉 기반, 동일 심볼 동시 매수 가능성 희박)
  • 권장: P2 우선순위로 주문 UUID 기반 멀티 할당 구조로 리팩토링

CRITICAL-2: 분당 Rate Limit 미적용 미해결

v2 지적사항:

현재 초당 8회만 제한, 분당 600회 제한/엔드포인트별 제한 미적용. 잦은 현재가/잔고 조회는 제한 우회 없이 직행 → 418/429 위험.

현재 상태:

# src/common.py
class TokenBucketRateLimiter:
    def __init__(self, rate: int = 8, per: float = 1.0):  # 초당 8회
        # 분당 제한 없음

평가: 부분적으로만 개선

  • 초당 제한만 적용, 분당 600회 제한 미구현
  • get_current_price, get_upbit_balances 일부 경로에서 Rate Limiter 미적용
  • 현실적 리스크: 중간 (멀티스레드 + 짧은 심볼 딜레이 시 분당 한도 초과 가능)
  • 권장: P0 - 이중 버킷(초/분) 구현 및 모든 API 호출 경로에 적용

CRITICAL-3: 재매수 쿨다운 기록 레이스/손상 가능 해결됨

v2 지적사항:

recent_sells.json 접근 시 파일 Lock/원자적 쓰기 없음, 예외도 무시 → 동시 매도 시 기록 손상/쿨다운 무시 가능.

현재 상태:

# src/common.py (현재)
_recent_sells_lock = threading.RLock()

def record_sell(symbol: str):
    with _recent_sells_lock:
        # 원자적 쓰기 (temp file + os.replace)
        temp_file = f"{RECENT_SELLS_FILE}.tmp"
        # ... atomic write ...
        os.replace(temp_file, RECENT_SELLS_FILE)

평가: 완전 해결

  • RLock + 원자적 쓰기 적용됨
  • JSONDecodeError 예외 처리도 추가됨
  • v4 리포트에서도 문제 없음으로 확인

CRITICAL-4: 가격/수량 부동소수점 정밀도 손실 미해결

v2 지적사항:

Decimal 적용 필요했던 HIGH-006 미완료. 슬리피지 계산·호가 반올림·수량 계산이 float 기반 → 체결 실패/초과주문 리스크.

현재 상태:

# src/order.py (현재)
def place_buy_order_upbit(market: str, amount_krw: float, cfg: RuntimeConfig):
    # 여전히 float 기반 계산
    fee_rate = 0.0005
    net_amount = amount_krw * (1 - fee_rate)  # float 연산

평가: 미해결

  • 일부 함수(_adjust_sell_ratio_for_min_order)에서만 Decimal 사용
  • 핵심 매수/매도 로직은 여전히 float 기반
  • 현실적 리스크: 낮음 (소액 거래에서는 영향 미미)
  • 권장: P1 - calc_price_amount() 유틸 함수 구현 및 전체 주문 경로에 적용

CRITICAL-5: 현재가 조회 재시도/캐시 없음 미해결

v2 지적사항:

단일 요청 실패 시 0 반환 후 상위 로직이 손절/익절 판단에 잘못된 가격 사용 가능. RateLimiter도 미적용.

현재 상태:

# src/holdings.py (현재)
def get_current_price(symbol: str) -> float:
    try:
        return pyupbit.get_current_price(symbol)  # 단일 시도, 캐시 없음
    except Exception as e:
        logger.error("현재가 조회 실패: %s", e)
        return 0.0  # 위험: 0 반환

평가: 미해결 + 매우 위험

  • 재시도 로직 없음
  • 캐시 없음 (OHLCV는 캐시하지만 ticker는 안 함)
  • 실패 시 0 반환 → 손절 로직이 -100% 수익률로 오판 가능
  • 현실적 리스크: 높음 (네트워크 일시 장애 시 잘못된 매도 가능)
  • 권장: P0 - 짧은 backoff 재시도 + 1~2초 TTL 캐시 + 실패 시 None 반환

2. High 이슈 분석 (6개)

HIGH-1: 예산 할당 최소 주문 금액 미검증 미해결

v2 지적사항:

부분 할당이 5,000원 미만일 수 있음 → 이후 주문 경로에서 실패.

현재 상태:

# src/common.py
def allocate(self, symbol: str, amount: float) -> bool:
    # MIN_KRW_ORDER 검증 없음
    self._allocated[symbol] = amount

평가: 미해결

  • 할당 시점에 최소 금액 검증 안 함
  • 주문 시점에서만 검증 → 예산은 잠겼으나 주문 실패 가능
  • 권장: P1 - allocate() 내부에서 MIN_KRW_ORDER 검증 추가

HIGH-2: 상태 동기화 불일치 가능성 ⚠️ 부분 해결

v2 지적사항:

StateManager와 holdings.json을 이중 관리하지만, holdings 저장 실패 시 상태 불일치 남음.

현재 상태:

  • state_manager.py 도입으로 bot_state.json을 Single Source of Truth로 지정
  • holdings.json은 캐시 역할로 명시적 분리
  • 그러나: holdings 업데이트 실패 시 state_manager와 불일치 가능성 여전히 존재

평가: ⚠️ 개선되었으나 완전하지 않음

  • v4에서 구조적 개선 완료
  • 주기적 검증/리빌드 로직은 없음
  • 권장: P2 - 주기적으로 bot_state.json 기준으로 holdings.json 재구축

HIGH-3: Pending/Confirm 파일 기반 워크플로 취약 미해결

v2 지적사항:

JSON append + rename 기반으로 손상/유실 가능, 만료/청소 로직 없음.

현재 상태:

  • 여전히 JSON 파일 기반
  • TTL 클린업 로직 없음
  • 무한 대기 가능성 존재

평가: 미해결

  • SQLite 또는 JSONL 마이그레이션 미진행
  • 권장: P2 - SQLite로 마이그레이션 또는 TTL 기반 클린업 추가

HIGH-4: OHLCV 캐시 TTL 고정·지표 시간대 미고려 ⚠️ 부분 해결

v2 지적사항:

5분 TTL 고정, 타임프레임/거래소 홀리데이/서버 시간 불일치 고려 없음.

현재 상태:

# src/indicators.py
CACHE_TTL = 300  # 5분 고정

평가: ⚠️ 부분적으로만 개선

  • 실패 시 캐시 미기록 로직은 개선됨
  • 그러나: 여전히 TTL 고정 (타임프레임별 동적 TTL 없음)
  • 권장: P2 - 타임프레임별 TTL 조정 (예: 4시간봉은 30분 TTL)

HIGH-5: 예외 타입·로깅 불균일 ⚠️ 부분 해결

v2 지적사항:

일부 함수 except Exception 광범위, 로깅 레벨 혼재.

현재 상태:

  • 여전히 많은 except Exception 존재
  • 그러나: v4 리포트에서 이미 지적하고 개선 권장함

평가: ⚠️ 인지되었으나 미개선

  • v4 리포트 MEDIUM-003에서 동일 이슈 지적
  • 권장: P1 - 구체적 예외 타입 지정

HIGH-6: 스레드 수/재시도 기본값 과도 미해결

v2 지적사항:

최대 재시도 5, 누적 대기 300s는 메인 루프 정지 유발 가능.

현재 상태:

# src/indicators.py
max_attempts = int(os.getenv("MAX_FETCH_ATTEMPTS", "5"))
max_total_backoff = float(os.getenv("MAX_TOTAL_BACKOFF", "300"))

평가: 환경변수화되었으나 기본값 여전히 과도

  • 환경변수로 조절 가능하게는 개선됨
  • 그러나: 기본값 300초는 여전히 과도
  • 권장: P2 - 기본값을 30~60초로 하향 조정

3. 종합 평가 및 우선순위 재조정

v2 vs v4 비교표

이슈 v2 우선순위 현재 상태 v4 권장 우선순위
동일 심볼 복수 주문 P0 미해결 P2 (현실적 리스크 낮음)
분당 Rate Limit P0 미해결 P0 (여전히 중요)
재매수 쿨다운 락 P0 해결 -
Decimal 정밀도 P0 미해결 P1 (소액에서는 영향 적음)
현재가 재시도/캐시 P1 미해결 P0 (매우 위험)
예산 최소 금액 검증 P1 미해결 P1
상태 동기화 P1 ⚠️ 개선 P2
Pending 파일 정비 P2 미해결 P2
OHLCV TTL 동적화 P2 ⚠️ 부분 P2
예외 타입 구체화 P2 ⚠️ 부분 P1
재시도 기본값 P2 미해결 P2

최종 우선순위 (v4 기준)

🔴 P0 (즉시)

  1. 현재가 조회 재시도/캐시 - 가장 위험한 이슈
  2. 분당 Rate Limit 구현 - API 차단 위험

🟡 P1 (1주 내)

  1. Decimal 정밀도 - 주문 안정성
  2. 예산 최소 금액 검증 - 실패 방지
  3. 예외 타입 구체화 - 디버깅 용이성

🟢 P2 (1개월 내)

  1. 동일 심볼 복수 주문 처리 - 현실적 리스크 낮음
  2. 상태 동기화 주기적 검증 - 장기 운영 안정성
  3. Pending 파일 정비 - SQLite 마이그레이션
  4. OHLCV TTL 동적화 - 성능 최적화
  5. 재시도 기본값 조정 - 리소스 효율

4. 전문가 의견 (Expert Commentary)

v2 리포트의 탁월한 점

  1. 실전 중심 분석: 분당 Rate Limit, Decimal 정밀도 등 실제 거래 시 발생할 수 있는 문제 정확히 지적
  2. 우선순위 명확: P0/P1/P2 구분이 합리적
  3. 구체적 해결안: "주문 토큰 기반", "이중 버킷" 등 구현 가능한 솔루션 제시

⚠️ v2 리포트의 과도한 지적

  1. CRITICAL-1 (동일 심볼 복수 주문):

    • 현재 4시간봉 전략에서는 발생 가능성 희박
    • 실제로는 P2 수준 (v2는 P0로 과대평가)
  2. CRITICAL-4 (Decimal 정밀도):

    • 소액 거래(5만원 이하)에서는 float 오차가 실질적 영향 미미
    • P1 수준이 적절 (v2는 P0로 과대평가)

🎯 v4 관점에서 가장 중요한 이슈

  1. 현재가 조회 실패 처리 (v2의 CRITICAL-5)

    • 이것이 진짜 CRITICAL: 네트워크 일시 장애 시 0원으로 인식 → 전량 손절 가능
    • v2가 P1로 낮게 평가한 것은 실수
    • 즉시 수정 필요
  2. 분당 Rate Limit (v2의 CRITICAL-2)

    • 멀티스레드 환경에서 실제로 Upbit API 차단 위험 존재
    • v2 평가 정확

5. 실행 가능한 개선안 (Quick Wins)

🚀 30분 내 구현 가능

# 1. 현재가 재시도 로직 (src/holdings.py)
def get_current_price(symbol: str, retries: int = 3) -> float | None:
    for attempt in range(retries):
        try:
            price = pyupbit.get_current_price(symbol)
            if price and price > 0:
                return price
        except Exception as e:
            if attempt == retries - 1:
                logger.error("현재가 조회 최종 실패: %s", symbol)
                return None  # 0이 아닌 None 반환
            time.sleep(0.5 * (2 ** attempt))
    return None

📊 2시간 내 구현 가능

# 2. 분당 Rate Limit 추가 (src/common.py)
class DualRateLimiter:
    def __init__(self):
        self.per_second = TokenBucketRateLimiter(rate=8, per=1.0)
        self.per_minute = TokenBucketRateLimiter(rate=600, per=60.0)

    def acquire(self):
        self.per_second.acquire()
        self.per_minute.acquire()

6. 결론

v2 리포트는 매우 전문적이고 실전 중심의 분석이었으나, 일부 이슈의 우선순위가 과대평가되었습니다.

핵심 요약:

  • v2의 11개 이슈 중 1개만 완전 해결, 7개는 미해결
  • 가장 위험한 이슈는 "현재가 조회 실패 처리" (v2가 P1로 과소평가)
  • v4 백테스팅/포트폴리오 관리 이슈가 v2에는 없었음 → v4가 더 포괄적

최종 권장:

  1. v2 P0 이슈 중 현재가 재시도 + 분당 Rate Limit만 즉시 구현
  2. 나머지는 v4 리포트 우선순위에 따라 순차 진행
  3. v2와 v4를 통합한 마스터 백로그 작성 권장