최초 프로젝트 업로드 (Script Auto Commit)

This commit is contained in:
2025-12-03 22:40:47 +09:00
commit dd9acf62a3
39 changed files with 5251 additions and 0 deletions

111
src/common.py Normal file
View File

@@ -0,0 +1,111 @@
import os
import logging
from pathlib import Path
import logging.handlers
import gzip
import shutil
LOG_DIR = os.getenv("LOG_DIR", "logs")
LOG_LEVEL = os.getenv("LOG_LEVEL", "INFO").upper()
Path(LOG_DIR).mkdir(parents=True, exist_ok=True)
LOG_FILE = os.path.join(LOG_DIR, "AutoCoinTrader.log")
logger = logging.getLogger("macd_alarm")
_logger_configured = False
# 거래소 및 계산 상수
# 부동소수점 비교용 엡실론 (일반적 정밀도)
FLOAT_EPSILON = 1e-10
# 거래소별 최소 수량 (Upbit 기준)
MIN_TRADE_AMOUNT = 1e-8 # 0.00000001 (암호화폐 최소 단위)
# 최소 주문 금액 (KRW)
MIN_KRW_ORDER = 5000 # Upbit 최소 주문 금액
# 데이터 파일 경로 상수 (중앙 집중 관리)
DATA_DIR = Path("data")
DATA_DIR.mkdir(parents=True, exist_ok=True)
HOLDINGS_FILE = str(DATA_DIR / "holdings.json")
TRADES_FILE = str(DATA_DIR / "trades.json")
PENDING_ORDERS_FILE = str(DATA_DIR / "pending_orders.json")
class CompressedRotatingFileHandler(logging.handlers.RotatingFileHandler):
"""RotatingFileHandler with gzip compression for rotated logs."""
def rotation_filename(self, default_name):
"""Append .gz to rotated log files."""
return default_name + ".gz"
def rotate(self, source, dest):
"""Compress the rotated log file."""
if os.path.exists(source):
with open(source, "rb") as f_in:
with gzip.open(dest, "wb") as f_out:
shutil.copyfileobj(f_in, f_out)
os.remove(source)
def setup_logger(dry_run: bool):
"""
Configure logging with rotation and compression.
Args:
dry_run: If True, also output to console. If False, only to file.
Log Rotation Strategy:
- Size-based: 10MB per file, keep 7 backups (total ~80MB)
- Time-based: Daily rotation, keep 30 days
- Compression: Old logs are gzipped (saves ~70% space)
Log Levels (production recommendation):
- dry_run=True: INFO (development/testing)
- dry_run=False: WARNING (production - only important events)
"""
global logger, _logger_configured
if _logger_configured:
return
logger.handlers.clear()
# Use WARNING level for production, INFO for development
effective_level = getattr(logging, LOG_LEVEL, logging.INFO if dry_run else logging.WARNING)
logger.setLevel(effective_level)
formatter = logging.Formatter("%(asctime)s - %(levelname)s - [%(threadName)s] - %(message)s")
# Console handler (only in dry_run mode)
if dry_run:
ch = logging.StreamHandler()
ch.setLevel(effective_level)
ch.setFormatter(formatter)
logger.addHandler(ch)
# Size-based rotating file handler with compression
fh_size = CompressedRotatingFileHandler(
LOG_FILE, maxBytes=10 * 1024 * 1024, backupCount=7, encoding="utf-8" # 10MB per file # Keep 7 backups
)
fh_size.setLevel(effective_level)
fh_size.setFormatter(formatter)
logger.addHandler(fh_size)
# Time-based rotating file handler (daily rotation, keep 30 days)
daily_log_file = os.path.join(LOG_DIR, "AutoCoinTrader_daily.log")
fh_time = logging.handlers.TimedRotatingFileHandler(
daily_log_file, when="midnight", interval=1, backupCount=30, encoding="utf-8"
)
fh_time.setLevel(effective_level)
fh_time.setFormatter(formatter)
fh_time.suffix = "%Y-%m-%d" # Add date suffix to rotated files
logger.addHandler(fh_time)
_logger_configured = True
logger.info(
"[SYSTEM] 로그 설정 완료: level=%s, size_rotation=%dMB×%d, daily_rotation=%d",
logging.getLevelName(effective_level),
10,
7,
30,
)