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

@@ -202,7 +202,7 @@ def _ingest_tick_rows(
return n
def load_breakout_ticks_by_code(
def load_common_ticks_by_code(
db,
start_key: str,
end_key: str,
@@ -250,21 +250,63 @@ def load_breakout_ticks_by_code(
len(codes) if codes else "ALL",
)
for chunk_s, chunk_e in chunks:
max_w = min(10, len(chunks)) if chunks else 1
max_w = get_env_int("WS_TICK_LOAD_MAX_WORKERS", max_w)
from concurrent.futures import ThreadPoolExecutor, as_completed
def process_chunk(chunk_s_val, chunk_e_val):
day_str = chunk_s_val[:8]
pq_path = f"/home/hoon/kis_bot/data/ticks/{mkt}/{day_str}.parquet"
_local_out = defaultdict(dict)
_n = 0
try:
rows = _fetch_ws_ticks_day_rows(table, mkt, chunk_s, chunk_e, codes)
n = _ingest_tick_rows(rows, out)
total += n
logger.info(
"%s day=%s rows=%s (누적=%s)",
table, chunk_s[:8], n, total,
)
import os
import time
if os.environ.get("BACKTEST_USE_RUST") == "1" and os.path.exists(pq_path):
import kis_rust_core
_st = time.time()
codes_list = list(codes) if codes else None
tick_source = os.environ.get("TICK_SOURCE", "").strip() or None
res = kis_rust_core.load_parquet_ticks_fast(
pq_path, chunk_s_val, chunk_e_val, codes_list, tick_source
)
_local_out.update(res["data"])
_n = res["count"]
_et = time.time()
logger.info(f"⚡ [Rust Parquet 초고속 로드] day={day_str} rows={_n} (Rust I/O + PyDict: {_et - _st:.3f}s)")
return chunk_s_val, chunk_e_val, _local_out, _n
except Exception as e:
failed_days += 1
logger.warning(
"%s day=%s 조회 실패 — 해당일 스킵: %s",
table, chunk_s[:8], e,
)
logger.warning(f"Parquet Rust 로드 실패, DB로 폴백 (day={day_str}): {e}")
try:
_rows = _fetch_ws_ticks_day_rows(table, mkt, chunk_s_val, chunk_e_val, codes)
_n = _ingest_tick_rows(_rows, _local_out)
except Exception as e:
logger.warning(f"DB 로드 폴백 실패 (day={day_str}): {e}")
return chunk_s_val, chunk_e_val, _local_out, _n
with ThreadPoolExecutor(max_workers=max_w) as executor:
futures = {executor.submit(process_chunk, c[0], c[1]): c for c in chunks}
for future in as_completed(futures):
c_s, c_e = futures[future]
try:
chunk_s, chunk_e, local_out, n = future.result()
for _code, mins_dict in local_out.items():
code_bucket = out[_code]
for min_k, ticks in mins_dict.items():
code_bucket.setdefault(min_k, []).extend(ticks)
total += n
logger.info("%s day=%s rows=%s (누적=%s)", table, chunk_s[:8], n, total)
except Exception as e:
failed_days += 1
logger.warning("%s day=%s 조회 실패 — 해당일 스킵: %s", table, c_s[:8], e)
if total <= 0:
# ws_ticks 비어도 LS 3차만으로 재생 가능 (1·2차 공백일)
@@ -592,7 +634,7 @@ def build_tick_coverage_meta_for_day(
})
if not candles_by_code:
return {"tick_codes_traded": len(codes_clean), "tick_bars_total": 0}
ticks_by_code, tick_rows = load_breakout_ticks_by_code(
ticks_by_code, tick_rows = load_common_ticks_by_code(
db, start_key, end_key, set(candles_by_code.keys()),
)
meta = tick_coverage_stats(dict(candles_by_code), ticks_by_code, bar_tf_min=tf)