인과적 GT 신호·복리 배분 시뮬을 도입하고 운영 정합성을 맞춘다.
미래 데이터를 쓰지 않는 causal 신호/tier와 전기간 복리 포트폴리오 비교로 GT 대비 sim_sized 검증 경로를 정리하고, 일한도·매수 상한·live_buy 스케일을 제거한다. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
181
deepcoin/ground_truth/gt_signal_causal.py
Normal file
181
deepcoin/ground_truth/gt_signal_causal.py
Normal file
@@ -0,0 +1,181 @@
|
||||
"""
|
||||
인과적(미래 미사용) GT 스타일 신호 — t봉 시점에 t 이하 데이터만 사용.
|
||||
|
||||
ZigZag/국소극값: pivot bar i-order 는 bar i 에서 확정 (i-order..i 구간만 관측).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
|
||||
from config import (
|
||||
GT_BUY_BB_MAX,
|
||||
GT_BUY_MIN_SWING_PCT,
|
||||
GT_MIN_SWING_PCT,
|
||||
GT_PIVOT_ORDER,
|
||||
)
|
||||
|
||||
|
||||
def _confirmed_trough_mask(
|
||||
low: np.ndarray,
|
||||
order: int,
|
||||
) -> np.ndarray:
|
||||
"""
|
||||
bar i 에서 i-order 봉이 저점임을 확정 (low[i-order:i+1] 만 사용).
|
||||
|
||||
Args:
|
||||
low: Low 가격 배열.
|
||||
order: pivot 반경(봉).
|
||||
|
||||
Returns:
|
||||
길이 n, i 에 1이면 i 시점 매수 확인 신호.
|
||||
"""
|
||||
n = len(low)
|
||||
out = np.zeros(n, dtype=np.int8)
|
||||
for i in range(2 * order, n):
|
||||
p = i - order
|
||||
seg = low[p - order : i + 1]
|
||||
if len(seg) == 0:
|
||||
continue
|
||||
if low[p] <= seg.min() + 1e-12:
|
||||
out[i] = 1
|
||||
return out
|
||||
|
||||
|
||||
def _confirmed_peak_mask(
|
||||
high: np.ndarray,
|
||||
order: int,
|
||||
) -> np.ndarray:
|
||||
"""
|
||||
bar i 에서 i-order 봉이 고점임을 확정.
|
||||
|
||||
Args:
|
||||
high: High 가격 배열.
|
||||
order: pivot 반경.
|
||||
|
||||
Returns:
|
||||
i 시점 매도 확인 신호.
|
||||
"""
|
||||
n = len(high)
|
||||
out = np.zeros(n, dtype=np.int8)
|
||||
for i in range(2 * order, n):
|
||||
p = i - order
|
||||
seg = high[p - order : i + 1]
|
||||
if len(seg) == 0:
|
||||
continue
|
||||
if high[p] >= seg.max() - 1e-12:
|
||||
out[i] = 1
|
||||
return out
|
||||
|
||||
|
||||
def _zigzag_filter_causal(
|
||||
confirm: np.ndarray,
|
||||
prices: np.ndarray,
|
||||
min_swing_pct: float,
|
||||
kind: str,
|
||||
) -> np.ndarray:
|
||||
"""
|
||||
확정 피벗에 ZigZag 최소 스윙% 필터 (인과적, 순차 갱신).
|
||||
|
||||
Args:
|
||||
confirm: bar i 에 확정 플래그.
|
||||
prices: pivot 가격 (i-order 위치의 low/high).
|
||||
pivot_indices: confirm==1 인 bar index.
|
||||
min_swing_pct: 최소 스윙 %.
|
||||
kind: trough | peak.
|
||||
|
||||
Returns:
|
||||
zigzag 통과 시점에 1.
|
||||
"""
|
||||
n = len(confirm)
|
||||
out = np.zeros(n, dtype=np.int8)
|
||||
order = GT_PIVOT_ORDER
|
||||
last_kind: str | None = None
|
||||
last_price = 0.0
|
||||
min_ratio = min_swing_pct / 100.0
|
||||
|
||||
for i in range(n):
|
||||
if confirm[i] != 1:
|
||||
continue
|
||||
p = i - order
|
||||
if p < 0:
|
||||
continue
|
||||
price = float(prices[p])
|
||||
if last_kind is None:
|
||||
out[i] = 1
|
||||
last_kind = kind
|
||||
last_price = price
|
||||
continue
|
||||
if kind == last_kind:
|
||||
if kind == "trough" and price < last_price:
|
||||
out[i - 1] = 0
|
||||
out[i] = 1
|
||||
last_price = price
|
||||
elif kind == "peak" and price > last_price:
|
||||
out[i - 1] = 0
|
||||
out[i] = 1
|
||||
last_price = price
|
||||
continue
|
||||
move = abs(price - last_price) / max(last_price, 1e-9)
|
||||
if move >= min_ratio:
|
||||
out[i] = 1
|
||||
last_kind = kind
|
||||
last_price = price
|
||||
return out
|
||||
|
||||
|
||||
def enrich_scan_frame_gt_signals_causal(
|
||||
frame: pd.DataFrame,
|
||||
*,
|
||||
pivot_order: int = GT_PIVOT_ORDER,
|
||||
buy_swing_pct: float = GT_BUY_MIN_SWING_PCT,
|
||||
sell_swing_pct: float = GT_MIN_SWING_PCT,
|
||||
bb_max: float = GT_BUY_BB_MAX,
|
||||
) -> pd.DataFrame:
|
||||
"""
|
||||
인과적 GT 신호 컬럼 (gt_*). t 시점 신호는 데이터 index<=t 만 사용.
|
||||
|
||||
Args:
|
||||
frame: m3 스캔 프레임.
|
||||
pivot_order: 확정 지연(봉).
|
||||
buy_swing_pct: 매수 ZigZag 스윙%.
|
||||
sell_swing_pct: 매도 ZigZag 스윙%.
|
||||
bb_max: BB 하단 필터.
|
||||
|
||||
Returns:
|
||||
gt_* 컬럼 추가 DataFrame.
|
||||
"""
|
||||
out = frame.copy()
|
||||
if "Low" not in out.columns or "High" not in out.columns:
|
||||
return out
|
||||
|
||||
low = out["Low"].astype(float).values
|
||||
high = out["High"].astype(float).values
|
||||
n = len(low)
|
||||
|
||||
trough_conf = _confirmed_trough_mask(low, pivot_order)
|
||||
peak_conf = _confirmed_peak_mask(high, pivot_order)
|
||||
|
||||
trough_z = _zigzag_filter_causal(
|
||||
trough_conf, low, buy_swing_pct, "trough"
|
||||
)
|
||||
peak_z = _zigzag_filter_causal(
|
||||
peak_conf, high, sell_swing_pct, "peak"
|
||||
)
|
||||
|
||||
out["gt_trough_local"] = trough_conf
|
||||
out["gt_peak_local"] = peak_conf
|
||||
out["gt_trough_zigzag"] = trough_z
|
||||
out["gt_peak_zigzag"] = peak_z
|
||||
|
||||
bb_ok = pd.Series(True, index=out.index)
|
||||
if "bb_pos" in out.columns:
|
||||
bb = pd.to_numeric(out["bb_pos"], errors="coerce")
|
||||
bb_ok = bb <= bb_max
|
||||
|
||||
out["gt_buy_signal"] = (pd.Series(trough_z, index=out.index) == 1) & bb_ok
|
||||
out["gt_buy_signal"] = out["gt_buy_signal"].astype(int)
|
||||
out["gt_sell_signal"] = pd.Series(peak_z, index=out.index).astype(int)
|
||||
out["gt_signal_causal"] = 1
|
||||
return out
|
||||
Reference in New Issue
Block a user