refactor: GT·시뮬·운영 3축 정리 및 hybrid 실거래 정합

Phase C/dry-run·미사용 모듈·재생성 HTML을 제거하고, 운영 체결을
sim_causal_hybrid와 동일한 hybrid 로직으로 통합한다.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
xavis
2026-06-03 23:50:28 +09:00
parent a16c942be4
commit d7848df6f7
85 changed files with 177180 additions and 196131 deletions

View File

@@ -0,0 +1,93 @@
"""
봉 간격 상수·빗썸(Upbit 호환) 캔들 API 경로.
분봉·일봉 외 주봉(10080)·월봉(43200)은 DB 테이블명 `{symbol}_{interval}` 에 그대로 사용합니다.
"""
from __future__ import annotations
from dateutil.relativedelta import relativedelta
from config import DAILY_INTERVAL_MIN, MONTH_INTERVAL_MIN, WEEK_INTERVAL_MIN
WM_INTERVALS: frozenset[int] = frozenset({WEEK_INTERVAL_MIN, MONTH_INTERVAL_MIN})
# 01_download·ops_sync 대상에서 제외 (DB 적재 안 함)
DOWNLOAD_EXCLUDED_INTERVALS: frozenset[int] = frozenset({1})
def is_excluded_from_download(interval: int) -> bool:
"""01_download·ops_sync에서 건너뛸 간격(1분봉 등)."""
return interval in DOWNLOAD_EXCLUDED_INTERVALS
def is_week_or_month(interval: int) -> bool:
"""주봉·월봉 여부."""
return interval in WM_INTERVALS
def candle_api_segment(interval: int) -> str:
"""
REST 경로 세그먼트 (minutes/{n} 제외).
Returns:
'weeks' | 'months' | 'days' | minutes/{interval}
"""
if interval == WEEK_INTERVAL_MIN:
return "weeks"
if interval == MONTH_INTERVAL_MIN:
return "months"
if interval >= DAILY_INTERVAL_MIN:
return "days"
return f"minutes/{interval}"
def interval_display_label(interval: int) -> str:
"""로그·UI용 라벨."""
if interval == WEEK_INTERVAL_MIN:
return "주봉"
if interval == MONTH_INTERVAL_MIN:
return "월봉"
if interval >= DAILY_INTERVAL_MIN:
return "일봉(1440)"
return f"{interval}분봉"
def pagination_step(interval: int, chunk_bars: int) -> relativedelta:
"""
get_coin_more_data 역순 수집 시 `to` 감소 단위.
Args:
interval: 봉 간격(분 표기).
chunk_bars: API 1회 최대 봉 수(겹침 청크).
Returns:
relativedelta.
"""
if interval == WEEK_INTERVAL_MIN:
return relativedelta(weeks=chunk_bars)
if interval == MONTH_INTERVAL_MIN:
return relativedelta(months=chunk_bars)
return relativedelta(minutes=interval * chunk_bars)
def bars_for_months(interval: int, months: int, *, extra_days: int = 0) -> int:
"""
N개월치 예상 봉 수(여유 포함).
Args:
interval: 봉 간격.
months: 보관·적재 개월 수.
extra_days: 일봉 계열 여유 일수.
Returns:
API 요청 목표 봉 수.
"""
if interval == WEEK_INTERVAL_MIN:
return int(months * 30 / 7) + max(extra_days // 7, 5)
if interval == MONTH_INTERVAL_MIN:
return months + max(extra_days // 30, 2)
if interval >= DAILY_INTERVAL_MIN:
return months * 30 + extra_days
bars_per_day = (24 * 60) // interval
return months * 30 * bars_per_day + 200

View File

@@ -16,24 +16,36 @@ from dateutil.relativedelta import relativedelta
from config import (
BITHUMB_MINUTE_INTERVALS,
COIN_NAME,
DAILY_INTERVAL_MIN,
DB_PATH,
DOWNLOAD_BACKFILL_EXTRA_BARS,
DOWNLOAD_DAILY_EXTRA_DAYS,
DOWNLOAD_INTERVALS,
DOWNLOAD_INTERVALS_WM,
DOWNLOAD_MIN_INCREMENTAL_BARS,
DOWNLOAD_MONTHS,
DOWNLOAD_MONTHS_1M,
DOWNLOAD_MONTHS_WM,
INCREMENTAL_OVERLAP_BARS,
KR_COINS,
SYMBOL,
)
from deepcoin.data.candle_intervals import (
bars_for_months,
interval_display_label,
is_excluded_from_download,
is_week_or_month,
)
from deepcoin.ops.monitor import Monitor
def bong_count_for_months(interval_minutes: int, months: int) -> int:
"""N개월치 봉 개수(여유분 포함)."""
if is_week_or_month(interval_minutes):
return bars_for_months(
interval_minutes, months, extra_days=DOWNLOAD_DAILY_EXTRA_DAYS
)
days = months * 30
from config import DAILY_INTERVAL_MIN
if interval_minutes >= DAILY_INTERVAL_MIN:
return days + DOWNLOAD_DAILY_EXTRA_DAYS
bars_per_day = (24 * 60) // interval_minutes
@@ -69,36 +81,44 @@ def trim_to_recent_months(data: pd.DataFrame, months: int) -> pd.DataFrame:
def interval_label(interval: int) -> str:
if interval >= 1440:
return "일봉(1440)"
return f"{interval}분봉"
"""로그용 간격 라벨."""
return interval_display_label(interval)
def months_for_interval(interval: int, default_months: int) -> int:
"""간격별 DB 보관 개월 수 (1분봉은 별도 상한)."""
if interval == 1:
return DOWNLOAD_MONTHS_1M
"""간격별 DB 보관 개월 수 (주·월봉은 DOWNLOAD_MONTHS_WM)."""
if is_week_or_month(interval):
return DOWNLOAD_MONTHS_WM
return default_months
def all_download_intervals() -> tuple[int, ...]:
"""분봉·일봉 + 주·월봉 간격 목록(1분 제외, 중복 제거, 순서 유지)."""
seen: set[int] = set()
out: list[int] = []
for iv in (*DOWNLOAD_INTERVALS, *DOWNLOAD_INTERVALS_WM):
if is_excluded_from_download(iv) or iv in seen:
continue
seen.add(iv)
out.append(iv)
return tuple(out)
def download_jobs() -> list[tuple[int, str]]:
labels = {
1: "1분",
3: "3분",
5: "5분",
10: "10분",
15: "15분",
30: "30분",
60: "60분(1시간)",
240: "240분(4시간)",
1440: "1440분(1일)",
}
jobs = []
for iv in DOWNLOAD_INTERVALS:
if iv < 1440 and iv not in BITHUMB_MINUTE_INTERVALS:
"""
01_download 대상 간격.
Returns:
(interval_min, 표시명) 리스트.
"""
jobs: list[tuple[int, str]] = []
for iv in all_download_intervals():
if is_excluded_from_download(iv):
continue
if not is_week_or_month(iv) and iv < 1440 and iv not in BITHUMB_MINUTE_INTERVALS:
print(f"경고: {iv}분봉은 빗썸 API 미지원 — 건너뜀")
continue
jobs.append((iv, labels.get(iv, f"{iv}")))
jobs.append((iv, interval_label(iv)))
return jobs
@@ -374,23 +394,27 @@ def download_symbol(
def download(months: int | None = None) -> None:
"""
WLD 다중 분봉·일봉을 coins.db에 증분 적재합니다.
WLD 다중 분봉·일봉·주봉·월봉을 coins.db에 증분 적재합니다.
간격: config.DOWNLOAD_INTERVALS
간격: DOWNLOAD_INTERVALS + DOWNLOAD_INTERVALS_WM (주·월은 DOWNLOAD_MONTHS_WM, 기본 24=2년)
"""
months = months or DOWNLOAD_MONTHS
default_months = months or DOWNLOAD_MONTHS
monitor = Monitor(cooldown_file=None)
jobs = download_jobs()
intervals_str = ", ".join(str(iv) for iv, _ in jobs)
print(f"=== {COIN_NAME} ({SYMBOL}) -> {DB_PATH} (증분 INSERT) ===")
print(f"보관 {months}개월 | 간격(분): {intervals_str}")
print(
f"보관 분봉·일봉 {default_months}개월 | "
f"주·월봉 {DOWNLOAD_MONTHS_WM}개월 | 간격(분): {intervals_str}"
)
started = datetime.now()
for interval, desc in jobs:
print(f"\n--- {desc} ---")
job_months = months_for_interval(interval, default_months)
try:
download_symbol(monitor, SYMBOL, interval, months)
download_symbol(monitor, SYMBOL, interval, job_months)
except Exception as e:
print(f"오류 interval={interval}: {e}")

View File

@@ -6,14 +6,23 @@ from __future__ import annotations
import pandas as pd
from config import DOWNLOAD_INTERVALS, SYMBOL
from config import DOWNLOAD_INTERVALS, DOWNLOAD_INTERVALS_WM, SYMBOL
from deepcoin.data.candle_intervals import interval_display_label
def interval_label(interval: int) -> str:
"""봉 간격 표시 라벨."""
if interval >= 1440:
return "일봉"
return f"{interval}"
return interval_display_label(interval)
def _all_load_intervals() -> tuple[int, ...]:
seen: set[int] = set()
out: list[int] = []
for iv in (*DOWNLOAD_INTERVALS, *DOWNLOAD_INTERVALS_WM):
if iv not in seen:
seen.add(iv)
out.append(iv)
return tuple(out)
def load_frames_from_db(
@@ -33,7 +42,7 @@ def load_frames_from_db(
간격(분) → OHLCV+지표 DataFrame.
"""
frames: dict[int, pd.DataFrame] = {}
for iv in DOWNLOAD_INTERVALS:
for iv in _all_load_intervals():
db_max = None
if lookback_days is not None:
db_max = monitor.db_row_limit_for_interval(iv, lookback_days)