거래 빠르게 안티에서 병신만든거 커서로

feat: Implement backtest source management and enhance candle data handling

Changes:
- Introduced a new function `_apply_backtest_source_env_from_request` to manage the environment variables for candle, tick, and order book sources based on incoming requests.
- Added a teardown function `_teardown_backtest_source_env` to ensure that environment variables do not persist between requests, enhancing the stability of the backtesting environment.
- Refactored existing code to utilize the new source management functions, improving code readability and maintainability.
- Added new utility functions in `bt_candle_source.py` for fetching and managing candle data, ensuring consistency with live trading data sources.

Impact:
- These changes improve the flexibility and reliability of the backtesting framework, allowing for better management of data sources and reducing the risk of cross-request contamination.
This commit is contained in:
Your Name
2026-08-13 16:03:40 +09:00
parent c6bd62a25f
commit 2c7ad867f4
53 changed files with 15251 additions and 637 deletions

View File

@@ -215,8 +215,24 @@ def build_live_config_groups() -> List[GroupDef]:
"OFF(기본) = KIS가 키움과 동일한 후보 40종목을 전부 수신하여 DB에 저장 | "
"ON = KIS가 현재 내가 사고 있는 종목만 수신 (KIS ID 한도 아낄 때)"
)),
_f("WS_SUBSCRIBE_SPILL", "📡 구독초과/실패 즉시 벤더 spill", "bool", default=True,
hint=(
"ON(기본)=한도초과·구독실패·해당 WS 미연결 시 대기 없이 다음 벤더로 구독 "
"(틱/호가 체인 기본: LIVE_* 1차 → 상대 → ls 3차 RAM). "
"OFF=예전처럼 거절/패스. 기존 LIVE_TICK/OB_PROVIDER 값은 바꾸지 않음."
)),
_f("WS_TICK_SUBSCRIBE_CHAIN", "📡 틱 구독 spill 체인(csv)", "text", default="",
hint="비우면 LIVE_TICK_PROVIDER→나머지→ls. 예: kiwoom,kis,ls"),
_f("WS_OB_SUBSCRIBE_CHAIN", "📡 호가 구독 spill 체인(csv)", "text", default="",
hint="비우면 LIVE_OB_PROVIDER→나머지→ls(3차 RAM). 예: kis,kiwoom,ls"),
_f("LS_WS_ENABLED", "📡 LS WS 기동 (재시작 필요)", "bool", default=False,
hint=(
"ON = LS증권 실시간 WS 기동 — 구독 spill 3차·영구구독 RAM 백업용. "
"OFF(기본)=VALIDATION/조건이력/ls_condition 전략이 있을 때만 기동. "
"키: env_config LS_APP_KEY_REAL / LS_APP_SECRET_REAL 필요."
)),
_f("KIS_APP_KEY_OB_REAL", "🔑 KIS 실전 호가 전용 앱키 (선택)", "text",
hint="한투 40종목 구독 한도 우회를 위한 2번째 실전 앱키. 미입력 시 기존 앱키로 호가까지 공용 구독합니다."),
hint="호가 전용 2번째 실전 앱키(시세 키와 역할 분리). 미입력 시 메인 세션+SAVE 플래그 현행. spill 틱용 아님."),
_f("KIS_APP_SECRET_OB_REAL", "🔑 KIS 실전 호가 전용 시크릿 (선택)", "text",
hint="호가 전용 2번째 실전 시크릿키."),
_f("KIS_APP_KEY_OB_MOCK", "🔑 KIS 모의 호가 전용 앱키 (선택)", "text",
@@ -229,10 +245,11 @@ def build_live_config_groups() -> List[GroupDef]:
},
{
"id": "ws_trigger_global",
"title": "⚡ 매수 판단 필터 설정 (실시간 WS 기반)",
"title": "💾 TRIGGER 스냅샷 저장 (분석용 · 매수 차단 아님)",
"hint": (
"매수 타이밍을 결정할 때 호가·프로그램 데이터를 어떻게 사용할지 설정. "
"모든 데이터는 실시간 메모리(RAM)에서 직접 읽으므로 딜레이 없음."
"매수 검토 순간을 DB에 기록할지 설정. "
"여기 ON/OFF는 주문을 막지 않습니다. "
"진입 차단은 아래 「진입 호가필터」「프로그램 매매 필터」「휩쏘」를 보세요."
),
"fields": [
_f(
@@ -242,7 +259,7 @@ def build_live_config_groups() -> List[GroupDef]:
default=True,
hint=(
"ON = 기관·외국인 프로그램 매매 흐름을 메모리에 저장 | "
"OFF = 프로그램 매매 필터 사용 불가"
"OFF = 프로그램 매매 필터·수집 불가"
),
),
_f(
@@ -252,7 +269,7 @@ def build_live_config_groups() -> List[GroupDef]:
default=True,
hint=(
"ON = 매수 검토할 때 호가·프로그램 스냅샷을 DB에 기록 (분석·디버깅용) | "
"OFF = 아래 모든 저장 비활성 (DB 용량 아낄 때)"
"OFF = 아래 모든 저장 비활성 (DB 용량 아낄 때) · 매수 차단과 무관"
),
),
@@ -273,7 +290,7 @@ def build_live_config_groups() -> List[GroupDef]:
default=True,
hint=(
"ON = 매수 검토 순간 호가창(10호가 잔량 등) 스냅샷도 DB에 저장 | "
"매수/거절 당시의 호가 상태 정밀 분석용 (용량 주의)"
"→ 분석용 (용량 주의) · 진입 호가필터 ON/OFF와 별개"
),
),
_f(
@@ -283,7 +300,7 @@ def build_live_config_groups() -> List[GroupDef]:
default=True,
hint=(
"ON = 매수 검토 순간 프로그램 매매 스냅샷도 DB에 저장 | "
"호가 저장과 동일 개념, 프로그램 필터 분석용"
"분석용 · 프로그램 필터 차단 스위치와 별개"
),
),
_f(
@@ -296,24 +313,35 @@ def build_live_config_groups() -> List[GroupDef]:
"평소엔 OFF 권장, 변경 후 봇 재시작 필요"
),
),
],
},
{
"id": "program_entry_filter",
"title": "🚫 프로그램 매매 필터 (진입 차단)",
"hint": (
"기관·외국인 프로그램 수급이 나쁠 때 매수를 막는 스위치. "
"진입 호가필터·휩쏘·EXIT_OB(매도 호가)와 전부 별개."
),
"fields": [
_f(
"PROGRAM_FILTER_ENABLED",
"🚫 프로그램 매매 필터 (매수 차단)",
"bool",
default=True,
hint=(
"ON = 프로그램 매매 흐름이 나쁠 때 매수를 차단 (기본 ON) | "
"OFF = 프로그램 매매 무시하고 그냥 매수"
"ON = 프로그램 매매 흐름이 나쁠 때 매수를 차단 | "
"OFF = 프로그램 수급 무시하고 매수 허용"
),
),
],
},
{
"id": "ws_whipsaw_global",
"title": "🌊 휩쏘 필터 (가짜 상승 차단)",
"title": "🌊 휩쏘 필터 (진입 차단 · 틱 급락→급반등)",
"hint": (
"매수 신호가 떴는데 직전에 급락이 있었으면 가짜 신호(휩쏘)로 판단해서 차단. "
"예: 30초 안에 0.3% 이상 뚝 떨어졌다가 올라온 종목은 패스."
"호가창이 아니라 최근 틱/미니봉 가격 패턴으로 가짜 급반등을 차단. "
"예: 30초 안에 0.3% 이상 뚝 떨어졌다가 올라온 종목은 패스. "
"진입 호가필터(스프레드·잔량)·프로그램 필터와 다름."
),
"fields": [
_f(
@@ -429,44 +457,49 @@ def build_live_config_groups() -> List[GroupDef]:
ob_fields.extend([
_f(
f"{pfx}_ORDERBOOK_FILTER_ENABLED",
f"{lbl} 호가필터 ON",
f"{lbl} 진입 호가필터 ON (매수 차단)",
"bool",
default=OB_DEFAULT_FILTER_ENABLED,
hint="ON=스프레드·잔량비·얇은호가·매도벽으로 매수 차단 · OFF=저장만",
hint=(
"ON=스프레드·잔량비·얇은호가·매도벽으로 매수 차단 | "
"OFF=진입은 안 막음(수치 보관만). "
"EXIT_OB/STOP_OB(매도 호가익절·손절)와 무관 · 휩쏘·프로그램 필터와도 무관"
),
),
_f(
f"{pfx}_ORDERBOOK_MAX_SPREAD_PCT",
f"{lbl} 스프레드상한(%)",
"float",
default=OB_DEFAULT_MAX_SPREAD_PCT,
hint="0.45=0.45% · 그리드 max_spread_pct 앵커",
hint="진입 필터용 · 0.45=0.45% · 그리드 max_spread_pct 앵커",
),
_f(
f"{pfx}_ORDERBOOK_MIN_BID_ASK_RATIO",
f"{lbl} 매수/매도잔량비 하한",
"float",
default=OB_DEFAULT_MIN_BID_ASK_RATIO,
hint="그리드 min_bid_ask_ratio",
hint="진입 필터용 · 그리드 min_bid_ask_ratio",
),
_f(
f"{pfx}_ORDERBOOK_ENTRY_ASK_MAX_MULT",
f"{lbl} 매도벽 허용배수",
"float",
default=OB_DEFAULT_ENTRY_ASK_MAX_MULT,
hint="필요수량×N · 그리드 ask_max_mult",
hint="진입 필터용 · 필요수량×N · 그리드 ask_max_mult",
),
_f(
f"{pfx}_ORDERBOOK_ENTRY_BID_DEPTH_MULT",
f"{lbl} 매수호가깊이 배수",
"float",
default=OB_DEFAULT_ENTRY_BID_DEPTH_MULT,
hint="얇은호가 탈락 · 필요수량×N",
hint="진입 필터용 · 얇은호가 탈락 · 필요수량×N",
),
_f(
f"{pfx}_ORDERBOOK_ENTRY_BID_LEVELS",
f"{lbl} 호가단계(N호가)",
"int",
default=OB_DEFAULT_ENTRY_BID_LEVELS,
hint="진입 필터용 · N호가 잔량 합산",
),
])
if pfx == "BREAKOUT":
@@ -476,15 +509,17 @@ def build_live_config_groups() -> List[GroupDef]:
"돌파 매도벽 상한(주)",
"int",
default=OB_DEFAULT_ASK_WALL_MAX_QTY,
hint="그리드 ask_wall_max_qty",
hint="진입 필터용 · 그리드 ask_wall_max_qty",
),
)
groups.append({
"id": "orderbook_strategy",
"title": "호가필터 — 전략별 (수치)",
"title": "🚫 진입 호가필터 (매수 차단) — 전략별",
"hint": (
"글로벌 ORDERBOOK_MAX_SPREAD_PCT 등 폐기 · "
"파람서치 apply-best ↔ *_ORDERBOOK_* 정합"
"호가창 스프레드·잔량·벽으로 매수만 막음. "
"끄면(OFF) 진입 차단만 해제 · 매도 쪽 EXIT_OB/STOP_OB는 아래 "
"「매도 호가익절·손절」카드에서 따로 킴/끔. "
"휩쏘(틱 패턴)·프로그램 필터와도 별개."
),
"fields": ob_fields,
})
@@ -698,9 +733,11 @@ def build_live_config_groups() -> List[GroupDef]:
)
groups.append({
"id": "whipsaw_strategy",
"title": "휩쏘 — 전략별 ON/OFF",
"title": "휩쏘 — 전략별 ON/OFF (진입 차단)",
"hint": (
"전략별 휩쏘 필터를 켤지 끌지 결정합니다. (글로벌 휩쏘 필터 수치 상속)"
"전략별 휩쏘(틱 급락→급반등) 진입 차단 ON/OFF. "
"수치는 위 「휩쏘 필터」 글로벌. "
"진입 호가필터·프로그램 필터·EXIT_OB와 별개."
),
"fields": ws_fields,
})
@@ -1019,6 +1056,27 @@ def build_live_config_groups() -> List[GroupDef]:
_f("STRATEGY_LOOP_SLEEP_MIN", "루프 끝 sleep 최소(초)", "float", default=0.1,
hint="한 바퀴 끝 대기 · WS 구독 캐시 폴링 · 0.1=실매 반응 빠르게"),
_f("STRATEGY_LOOP_SLEEP_MAX", "루프 끝 sleep 최대(초)", "float", default=0.1),
_f("LOOP_PROFILE_ENABLED", "루프 숙제 ms 계측", "bool", default=False,
hint="ON=한 바퀴 단계별·락 wait/hold·check_buy 합/max 로그 · 병목 확정용 · 기본 OFF"),
_f("LOOP_PROFILE_EVERY_N", "계측 샘플 주기(바퀴)", "int", default=1,
hint="N바퀴마다 1회 상세 로그 · 1=매바퀴 · 로그 폭주 시 5~10"),
_f("LOOP_PROFILE_LOG_PATH", "계측 로그 파일", "str", default="logs/loop_profile.log",
hint="상대경로=레포루트 기준 · 비우면 전략 logger INFO 만"),
_f("CHECK_BUY_PROF_MIN_MS", "check_buy 상세 로그 하한(ms)", "int", default=500,
hint="이 ms 이상인 check_buy 만 [CHECK_BUY_PROF] 단계별 기록 · LOOP_PROFILE_ENABLED 필요"),
_f("HOLDINGS_DB_SYNC_INTERVAL_SEC", "보유 DB sync 안전망(초)", "int", default=60,
hint="루프마다 get_active_trades 대신 N초마다만 · 매수/매도 체결은 RAM 즉시 · "
"0=매루프(레거시) · 재시작·지정가체결은 강제 sync"),
_f("TODAY_TRADES_CACHE_TTL_SEC", "당일거래 RAM TTL(초)", "float", default=1.0,
hint="check_buy·일일한도 가드 공유 · N초 내 DB 재조회 생략 · 체결 시 즉시 무효화 · 0=매번 DB"),
_f("STRATEGY_ENV_CACHE_TTL_SEC", "전략설정 RAM TTL(초)", "float", default=1.0,
hint="config_* reload 매루프 SELECT 완화 · 기본 1초 · 웹 저장 시 즉시 무효화 · "
"유니버스/봉 분슬롯과 무관(임계값만)"),
_f("UNIVERSE_SLOT_CACHE_TTL_SEC", "유니버스슬롯 RAM TTL(초)", "float", default=1.0,
hint="history∩후보 정합 ON 유지 · 같은 슬롯키면 N초 RAM · 슬롯 바뀌면 즉시 DB 재조회"),
_f("UNIVERSE_SLOT_FLIGHT_WAIT_SEC", "유니버스슬롯 flight대기(초)", "float", default=5.0,
hint="TTL 만료 시 같은 키 DB SELECT 1회(single-flight) · 대기자 Event 상한 · "
"분슬롯 freeze 아님"),
_f("TICK_SELL_ENABLED", "틱매도(공통)", "bool", default=False,
hint="ON=WS 현재가 갱신마다 매도검사(백테 틱청산에 근접) · OFF=루프(~0.1초)만 · "
"전략별 *_TICK_SELL_ENABLED 가 비어있지 않으면 그쪽 우선 · 기본 OFF"),
@@ -1133,14 +1191,18 @@ def build_live_config_groups() -> List[GroupDef]:
groups.append({
"id": "breakout_exit",
"title": "Optuna 청산 파라미터 (돌파)",
"hint": "AI(Optuna)가 최적값을 찾아내는 수익/손실구간 호가매도 파라미터 모음입니다.",
"title": "📉 매도 호가익절·손절 (EXIT_OB/STOP_OB) — 돌파",
"hint": (
"보유 중 청산만. 진입 호가필터(*_ORDERBOOK_FILTER_ENABLED)를 꺼도 "
"여기 ON이면 익절/손절 호가매도는 그대로 동작. "
"꼬리·스캘프는 스위치 없음."
),
"fields": [
_f("BREAKOUT_MAX_HOLD_BARS", "최대보유(분)", "int", default=0,
hint="0=OFF · 진입 후 N분 경과 시 max_hold 청산"),
_f("BREAKOUT_EXIT_OB_ENABLED", "수익구간 호가매도 ON", "bool", default=False,
_f("BREAKOUT_EXIT_OB_ENABLED", "수익구간 호가매도 ON (익절)", "bool", default=False,
danger=True,
hint="[주의] 꼬리잡기/스캘핑(히트앤런 단타)은 필요 없어서 스위치가 없습니다. ON=이득+매수잔량 붕괴 시 즉시 탈출"),
hint="진입 필터와 무관 · ON=이득+매수잔량 붕괴 시 즉시 탈출"),
_f("BREAKOUT_EXIT_OB_RATIO_MIN", "수익구간 OR_MA 임계", "float", default=0.4,
hint="매수잔량/매도잔량 MA 미만이면 붕괴 · 예: 0.4"),
_f("BREAKOUT_EXIT_OB_MA_WINDOW", "수익구간 OR MA 창", "int", default=5,
@@ -1151,7 +1213,7 @@ def build_live_config_groups() -> List[GroupDef]:
hint="보유 분(1분봉≈1) 미달이면 미발동"),
_f("BREAKOUT_STOP_OB_ENABLED", "손절호가 ON", "bool", default=False,
danger=True,
hint="[주의] 꼬리잡기/스캘핑(히트앤런 단타)은 필요 없어서 스위치가 없습니다. ON=손해+매수잔량 붕괴 시 하드손절 전 미리 탈출"),
hint="진입 필터와 무관 · ON=손해+매수잔량 붕괴 시 하드손절 전 미리 탈출"),
_f("BREAKOUT_STOP_OB_RATIO_MIN", "손절호가 OR_MA 임계", "float", default=0.4,
hint="손실구간 OR_MA 임계"),
_f("BREAKOUT_STOP_OB_MA_WINDOW", "손절호가 OR MA 창", "int", default=5,
@@ -1165,12 +1227,15 @@ def build_live_config_groups() -> List[GroupDef]:
groups.append({
"id": "momentum_exit",
"title": "Optuna 청산 파라미터 (모멘텀)",
"hint": "AI(Optuna)가 최적값을 찾아내는 수익/손실구간 호가매도 파라미터 모음입니다.",
"title": "📉 매도 호가익절·손절 (EXIT_OB/STOP_OB) — 모멘텀",
"hint": (
"보유 중 청산만. 진입 호가필터를 꺼도 여기 ON이면 그대로 동작. "
"돌파 카드와 동일 개념 · 꼬리·스캘프 없음."
),
"fields": [
_f("MOMENTUM_EXIT_OB_ENABLED", "수익구간 호가매도 ON", "bool", default=False,
_f("MOMENTUM_EXIT_OB_ENABLED", "수익구간 호가매도 ON (익절)", "bool", default=False,
danger=True,
hint="[주의] 꼬리잡기/스캘핑(히트앤런 단타)은 필요 없어서 스위치가 없습니다. ON=이득+매수잔량 붕괴 시 즉시 탈출"),
hint="진입 필터와 무관 · ON=이득+매수잔량 붕괴 시 즉시 탈출"),
_f("MOMENTUM_EXIT_OB_RATIO_MIN", "수익구간 OR_MA 임계", "float", default=0.4,
hint="매수잔량/매도잔량 MA 미만이면 붕괴 · 예: 0.4"),
_f("MOMENTUM_EXIT_OB_MA_WINDOW", "수익구간 OR MA 창", "int", default=5,
@@ -1181,7 +1246,7 @@ def build_live_config_groups() -> List[GroupDef]:
hint="보유 분(1분봉≈1) 미달이면 미발동"),
_f("MOMENTUM_STOP_OB_ENABLED", "손절호가 ON", "bool", default=False,
danger=True,
hint="[주의] 꼬리잡기/스캘핑(히트앤런 단타)은 필요 없어서 스위치가 없습니다. ON=손해+매수잔량 붕괴 시 하드손절 전 미리 탈출"),
hint="진입 필터와 무관 · ON=손해+매수잔량 붕괴 시 하드손절 전 미리 탈출"),
_f("MOMENTUM_STOP_OB_RATIO_MIN", "손절호가 OR_MA 임계", "float", default=0.4,
hint="총사÷총팔 MA 미만이면 붕괴 · 예: 0.4"),
_f("MOMENTUM_STOP_OB_MA_WINDOW", "손절호가 OR MA 창", "int", default=5,