- 프론트엔드 UI 업데이트 (backtest.html, backtest.js) 엔진 라디오 버튼 통합 관련 반영 - Rust 플러그인(kis_rust_core) 및 컴파일 소스코드 추가 - CLI 백테스트 스크립트 수정 및 최신화 - 기타 스크래치 테스트 스크립트, 로그 요약 마크다운(.md) 등 누락 파일 일괄 반영 - 추가적으로 아직 발견되지 않은 엣지 케이스나 렌더링 오류가 포함되어 있을 가능성이 있음
90 lines
3.3 KiB
Python
90 lines
3.3 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
kis_trader/utils/export_ticks_parquet.py
|
|
DB의 ws_ticks 데이터를 Parquet 파일로 변환하여 디스크에 저장합니다.
|
|
주기적인 백그라운드 작업이나 On-demand 호출을 위해 설계되었습니다.
|
|
"""
|
|
import os
|
|
import argparse
|
|
import sys
|
|
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..")))
|
|
from datetime import datetime, timedelta
|
|
import polars as pl
|
|
|
|
from kis_trader.utils.logger import get_logger
|
|
logger = get_logger("export_ticks_parquet")
|
|
from database import TradeDB, _DB_HOST, _DB_USER, _DB_PASS, _DB_NAME, _DB_PORT
|
|
|
|
# 저장 폴더 경로 설정
|
|
TICKS_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "data", "ticks"))
|
|
os.makedirs(os.path.join(TICKS_DIR, "KR"), exist_ok=True)
|
|
os.makedirs(os.path.join(TICKS_DIR, "US"), exist_ok=True)
|
|
|
|
# Polars 용 URI 생성 (connectorx)
|
|
DB_URI = f"mysql://{_DB_USER}:{_DB_PASS}@{_DB_HOST}:{_DB_PORT}/{_DB_NAME}"
|
|
|
|
def export_day(market: str, day: str) -> bool:
|
|
"""특정 일자의 데이터를 Parquet으로 변환합니다 (day: YYYYMMDD)"""
|
|
market = market.strip().upper()
|
|
table = "ws_ticks_us" if market == "US" else "ws_ticks"
|
|
|
|
start_str = f"{day}000000"
|
|
end_str = f"{day}235959"
|
|
|
|
# 쿼리 작성 (MariaDB에서 직접 Polars로 로드)
|
|
query = f"""
|
|
SELECT code, tick_time, price, volume, source, recv_ts, tick_time_raw
|
|
FROM {table}
|
|
WHERE market = '{market}'
|
|
AND tick_time >= '{start_str}'
|
|
AND tick_time <= '{end_str}'
|
|
"""
|
|
|
|
out_file = os.path.join(TICKS_DIR, market, f"{day}.parquet")
|
|
|
|
try:
|
|
logger.info(f"[{market}] {day} 틱 데이터 로딩 중... ({table})")
|
|
# polars의 read_database 사용 (connectorx 백엔드 사용 시 매우 빠름, 기본 adbc)
|
|
# pip install adbc-driver-mysql 를 권장하지만 일단 pandas 폴백 혹은 pyarrow 활용
|
|
df = pl.read_database_uri(query, uri=DB_URI)
|
|
|
|
if df.height == 0:
|
|
logger.warning(f"[{market}] {day} 데이터가 없습니다.")
|
|
return False
|
|
|
|
logger.info(f"[{market}] {day} 로딩 완료 ({df.height} rows). Parquet 저장 시작...")
|
|
df.write_parquet(out_file)
|
|
logger.info(f"[{market}] {day} Parquet 저장 완료: {out_file}")
|
|
return True
|
|
except Exception as e:
|
|
logger.error(f"[{market}] {day} Parquet 저장 실패: {e}")
|
|
return False
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description="틱 데이터 Parquet 익스포터")
|
|
parser.add_argument("--market", type=str, default="KR", help="마켓 (KR/US)")
|
|
parser.add_argument("--start", type=str, help="시작일 (YYYY-MM-DD)")
|
|
parser.add_argument("--end", type=str, help="종료일 (YYYY-MM-DD)")
|
|
|
|
args = parser.parse_args()
|
|
|
|
if not args.start or not args.end:
|
|
logger.error("start 와 end 인자가 필요합니다.")
|
|
return
|
|
|
|
try:
|
|
start_dt = datetime.strptime(args.start, "%Y-%m-%d")
|
|
end_dt = datetime.strptime(args.end, "%Y-%m-%d")
|
|
except Exception as e:
|
|
logger.error(f"날짜 형식 오류: {e}")
|
|
return
|
|
|
|
cur_dt = start_dt
|
|
while cur_dt <= end_dt:
|
|
day_str = cur_dt.strftime("%Y%m%d")
|
|
export_day(args.market, day_str)
|
|
cur_dt += timedelta(days=1)
|
|
|
|
if __name__ == "__main__":
|
|
main()
|