chore: 작업 중 발생한 부수적 변경 사항 및 누락된 파일 전체 커밋

- 프론트엔드 UI 업데이트 (backtest.html, backtest.js) 엔진 라디오 버튼 통합 관련 반영
- Rust 플러그인(kis_rust_core) 및 컴파일 소스코드 추가
- CLI 백테스트 스크립트 수정 및 최신화
- 기타 스크래치 테스트 스크립트, 로그 요약 마크다운(.md) 등 누락 파일 일괄 반영
- 추가적으로 아직 발견되지 않은 엣지 케이스나 렌더링 오류가 포함되어 있을 가능성이 있음
This commit is contained in:
Your Name
2026-09-06 17:04:50 +09:00
parent 4dbb1387a1
commit e1ac8d119b
181 changed files with 8371 additions and 503 deletions

View File

@@ -0,0 +1,89 @@
#!/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()