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:
93
deepcoin/data/candle_intervals.py
Normal file
93
deepcoin/data/candle_intervals.py
Normal 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
|
||||
@@ -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}")
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user