11 KiB
코드 리뷰 개선사항 구현 요약
날짜: 2025-12-09
참조: docs/code_review_report_v1.md
제외 항목: CRITICAL-004, HIGH-004, MEDIUM-004
📋 구현 완료 항목
🔴 CRITICAL Issues
[CRITICAL-001] API Rate Limiter 구현 ✅
문제: Upbit API 초당 10회 제한을 멀티스레딩 환경에서 초과 위험
해결:
- 파일:
src/common.py - 토큰 버킷 알고리즘 기반
RateLimiter클래스 추가 - 초당 8회 제한 (여유분 확보)
- Thread-Safe 구현 (threading.Lock 사용)
src/indicators.py의fetch_ohlcv()에 적용
코드:
# src/common.py
class RateLimiter:
def __init__(self, max_calls: int = 8, period: float = 1.0):
self.max_calls = max_calls
self.period = period
self.calls = deque()
self.lock = threading.Lock()
def acquire(self):
# Rate Limit 초과 시 자동 대기
...
api_rate_limiter = RateLimiter(max_calls=8, period=1.0)
# src/indicators.py
from .common import api_rate_limiter
api_rate_limiter.acquire() # API 호출 전
df = pyupbit.get_ohlcv(...)
영향: API 호출 제한 초과로 인한 418 에러 방지
[CRITICAL-002] 최고가 갱신 로직 구현 ✅
문제: holdings.json의 max_price가 실시간 갱신되지 않아 트레일링 스톱 오작동
해결:
- 파일:
src/holdings.py,main.py update_max_price()함수 추가 (Thread-Safe)- main.py의 손절/익절 체크 전 자동 갱신
코드:
# src/holdings.py
def update_max_price(symbol: str, current_price: float, holdings_file: str = HOLDINGS_FILE) -> None:
with holdings_lock:
holdings = load_holdings(holdings_file)
if symbol in holdings:
old_max = holdings[symbol].get("max_price", 0)
if current_price > old_max:
holdings[symbol]["max_price"] = current_price
save_holdings(holdings, holdings_file)
# main.py
for symbol in holdings.keys():
current_price = get_current_price(symbol)
update_max_price(symbol, current_price, HOLDINGS_FILE)
영향: 정확한 트레일링 스톱 동작, 수익 극대화
[CRITICAL-003] Thread-Safe holdings 저장 ✅
문제: Race Condition으로 holdings.json 데이터 손실 위험
해결:
- 파일:
src/holdings.py save_holdings()에 holdings_lock 적용 (이미 구현됨 확인)- 원자적 쓰기 (.tmp 파일 → rename)
영향: 멀티스레드 환경에서 데이터 무결성 보장
[CRITICAL-005] 부분 매수 지원 ✅
문제: 잔고 9,000원일 때 10,000원 주문 시도 시 매수 불가 (5,000원 이상이면 가능한데)
해결:
- 파일:
src/order.py place_buy_order_upbit()수정- 잔고 부족 시 가능한 만큼 매수 (최소 주문 금액 이상)
코드:
# 잔고 확인 및 조정
if not cfg.dry_run:
krw_balance = upbit.get_balance("KRW")
if krw_balance < amount_krw:
if krw_balance >= min_order_value:
logger.info("[%s] 잔고 부족, 부분 매수: %.0f원 → %.0f원", market, amount_krw, krw_balance)
amount_krw = krw_balance
else:
return {"status": "skipped_insufficient_balance"}
# 수수료 고려 (0.05%)
amount_krw = amount_krw * 0.9995
영향: 기회 손실 방지, 자금 효율성 증가
🟡 HIGH Priority Issues
[HIGH-005] Circuit Breaker 임계값 조정 ✅
문제: failure_threshold=5는 너무 높음
해결:
- 파일:
src/circuit_breaker.py - failure_threshold: 5 → 3
- recovery_timeout: 30초 → 300초 (5분)
영향: API 오류 발생 시 더 빠르게 차단, 계정 보호
[HIGH-007] Telegram 메시지 자동 분할 ✅
문제: Telegram 메시지 4096자 제한 초과 시 전송 실패
해결:
- 파일:
src/notifications.py send_telegram()수정- 4000자 초과 메시지 자동 분할 전송
- 분할 메시지 간 0.5초 대기 (Rate Limit 방지)
코드:
if len(payload_text) > max_length:
chunks = [payload_text[i:i+max_length] for i in range(0, len(payload_text), max_length)]
for i, chunk in enumerate(chunks, 1):
header = f"[메시지 {i}/{len(chunks)}]\n"
send_message(header + chunk)
if i < len(chunks):
time.sleep(0.5) # Rate Limit 방지
영향: 긴 메시지 전송 실패 방지, 알림 안정성 향상
[HIGH-008] 재매수 방지 기능 ✅
문제: 매도 직후 같은 코인 재매수 → 휩소 손실
해결:
- 파일:
src/common.py,src/signals.py,src/order.py record_sell(): 매도 기록 저장can_buy(): 재매수 가능 여부 확인 (기본 24시간 쿨다운)- 매도 성공 시 자동 기록, 매수 전 자동 확인
코드:
# src/common.py
def record_sell(symbol: str):
sells = json.load(open(RECENT_SELLS_FILE))
sells[symbol] = time.time()
json.dump(sells, open(RECENT_SELLS_FILE, "w"))
def can_buy(symbol: str, cooldown_hours: int = 24) -> bool:
sells = json.load(open(RECENT_SELLS_FILE))
if symbol in sells:
elapsed = time.time() - sells[symbol]
return elapsed >= cooldown_hours * 3600
return True
# src/signals.py (_process_symbol_core)
if not can_buy(symbol, cooldown_hours):
return {"summary": [f"재매수 대기 중"]}
# src/order.py (execute_sell_order_with_confirmation)
if trade_status in ["simulated", "filled"]:
record_sell(symbol)
영향: 휩소 손실 방지, 거래 효율성 증가
🟢 MEDIUM Priority Issues
[MEDIUM-001] 설정 파일 검증 ✅
문제: config.json 필수 항목 누락 시 런타임 에러
해결:
- 파일:
src/config.py validate_config()함수 추가- 필수 항목 확인, 범위 검증, 타입 체크
코드:
def validate_config(cfg: dict) -> tuple[bool, str]:
required_keys = [
"buy_check_interval_minutes",
"stop_loss_check_interval_minutes",
"profit_taking_check_interval_minutes",
"dry_run",
"auto_trade"
]
for key in required_keys:
if key not in cfg:
return False, f"필수 설정 항목 누락: '{key}'"
# 범위 검증
if cfg["buy_check_interval_minutes"] < 1:
return False, "buy_check_interval_minutes는 1 이상이어야 함"
return True, ""
영향: 설정 오류 조기 발견, 안정성 향상
🔒 보안 개선
파일 권한 설정 ✅
문제: holdings.json, config.json 파일 권한 미설정 → 유출 위험
해결:
- 파일:
src/holdings.py - holdings.json 저장 시 자동으로 0o600 권한 설정 (소유자만 읽기/쓰기)
코드:
import stat
os.chmod(holdings_file, stat.S_IRUSR | stat.S_IWUSR) # rw-------
영향: 민감 정보 보호
API 키 유효성 검증 ✅
문제: 실전 모드 시작 시 API 키 검증 없음 → 런타임 에러
해결:
- 파일:
main.py - 프로그램 시작 시 Upbit API 키 유효성 검증 (실전 모드 전용)
코드:
if not cfg.dry_run:
is_valid, msg = validate_upbit_api_keys(cfg.upbit_access_key, cfg.upbit_secret_key)
if not is_valid:
logger.error("[ERROR] Upbit API 키 검증 실패: %s. 종료합니다.", msg)
return
영향: 조기 에러 발견, 안전한 운영
🚫 제외된 항목
CRITICAL-004: RSI/MACD 조건 개선
- 사유: 함수 미존재 (과거에 제거됨) + 사용자 요청으로 제외
- 현황:
check_rsi_oversold,check_macd_signal함수는 코드베이스에 없음- 현재는
_evaluate_buy_conditions()함수가 MACD + SMA + ADX 복합 조건 사용 - RSI와 단순 MACD 체크는 사용되지 않음
- 결론: 현재 전략이 더 정교하므로 개선 불필요
HIGH-004: Bollinger Bands 로직 수정
- 사유: 함수 미존재 (Bollinger Bands 미사용) + 사용자 요청으로 제외
- 현황:
check_bollinger_reversal함수는 코드베이스에 없음- 현재 매수 전략에 Bollinger Bands 미사용
- 결론: 필요 시 추가 구현 가능 (선택사항)
MEDIUM-004: 백테스팅 기능
- 사유: 사용자 요청으로 제외
- 내용: 과거 데이터 기반 전략 검증
📊 개선 효과 예상
| 항목 | 개선 전 | 개선 후 | 효과 |
|---|---|---|---|
| API Rate Limit 초과 | 가능 (멀티스레드) | 불가능 | 계정 정지 방지 |
| 최고가 갱신 | 수동 | 자동 (실시간) | 정확한 트레일링 스톱 |
| holdings 데이터 손실 | 가능 (Race Condition) | 불가능 (Lock) | 데이터 무결성 보장 |
| 잔고 부족 시 매수 | 실패 | 부분 매수 | 기회 손실 방지 |
| Circuit Breaker | 5회 실패 후 차단 | 3회 실패 후 차단 | 빠른 보호 |
| Telegram 긴 메시지 | 전송 실패 | 자동 분할 | 알림 안정성 |
| 재매수 방지 | 없음 | 24시간 쿨다운 | 휩소 손실 방지 |
| 설정 오류 | 런타임 에러 | 시작 시 검증 | 안정성 향상 |
✅ 검증 방법
1. 자동 테스트 실행
python scripts/verify_improvements.py
테스트 항목:
- Rate Limiter 동작 확인
- 설정 파일 검증
- 재매수 방지 기능
- 최고가 갱신 로직
- Telegram 메시지 분할
2. 수동 검증
Rate Limiter
from src.common import api_rate_limiter
import time
start = time.time()
for i in range(10):
api_rate_limiter.acquire()
print(f"호출 {i+1}: {time.time() - start:.2f}초")
재매수 방지
from src.common import record_sell, can_buy
symbol = "KRW-BTC"
print(can_buy(symbol)) # True
record_sell(symbol)
print(can_buy(symbol)) # False (24시간 동안)
최고가 갱신
from src.holdings import update_max_price, load_holdings
symbol = "KRW-BTC"
update_max_price(symbol, 50000000)
holdings = load_holdings()
print(holdings[symbol]["max_price"]) # 50000000
🔄 향후 개선 계획
Phase 2 (1주 내)
- HIGH-001: 타입 힌팅 추가 (전체 프로젝트)
- HIGH-002: 예외 처리 구체화 (나머지 모듈)
- HIGH-003: 로깅 레벨 일관성 개선
- HIGH-006: Decimal 기반 가격 계산
Phase 3 (1개월 내)
- MEDIUM-002: 캔들 데이터 캐싱 (lru_cache)
- MEDIUM-003: 에러 코드 표준화
- MEDIUM-005~012: 운영 편의성 개선
- LOW-001~007: 코드 품질 개선
📝 참고 문서
- 코드 리뷰 보고서:
docs/code_review_report_v1.md - 프로젝트 상태:
docs/project_state.md - 검증 스크립트:
scripts/verify_improvements.py
작성자: GitHub Copilot (Claude Sonnet 4.5) 작성일: 2025-12-09 버전: v1.0