This commit is contained in:
2026-06-23 00:03:26 +09:00
7 changed files with 1276 additions and 0 deletions
+59
View File
@@ -0,0 +1,59 @@
#!/usr/bin/env python3
"""
WBS-9.3: ATR20 자동 충전 절차
조건: atr20 IS NULL AND close_price IS NOT NULL
"""
def calculate_atr20(closes: list[float], period: int = 20) -> float:
"""
Calculate ATR (Average True Range) for 20 days.
입력: close 가격 리스트 (최소 20일)
출력: ATR20 (숫자)
"""
if len(closes) < period:
return None
# True Range 계산 (간소화 버전: 현재 close 기반)
trs = []
for i in range(1, len(closes)):
high = closes[i]
low = closes[i]
prev_close = closes[i - 1]
tr = max(
high - low,
abs(high - prev_close),
abs(low - prev_close)
)
trs.append(tr)
if not trs:
return None
# ATR = SMA of True Range (20일)
atr = sum(trs[-period:]) / min(period, len(trs))
return round(atr, 2)
def auto_fill_atr20(row: dict, historical_closes: list[float] = None) -> dict:
"""
자동 충전: atr20 필드
입력:
row: 현재 행 (atr20 = None)
historical_closes: 과거 close 가격 (최소 20일)
출력:
row (atr20 채워짐) 또는 원본 (실패시)
"""
if not historical_closes or row.get("atr20") is not None:
return row
atr20 = calculate_atr20(historical_closes)
if atr20 is not None:
row["atr20"] = atr20
row["_fill_source"] = "auto_fill_atr20_v1"
return row
+57
View File
@@ -0,0 +1,57 @@
#!/usr/bin/env python3
"""
WBS-9.3: RSI14 자동 충전 절차
조건: rsi_14 IS NULL AND close_price IS NOT NULL
"""
def calculate_rsi14(closes: list[float], period: int = 14) -> float:
"""
Calculate RSI (Relative Strength Index) for 14 days.
입력: close 가격 리스트 (최소 14일)
출력: RSI14 (0-100)
"""
if len(closes) < period:
return None
# 가격 변화 계산
deltas = [closes[i] - closes[i - 1] for i in range(1, len(closes))]
# Gains/Losses 분리
gains = [d if d > 0 else 0 for d in deltas]
losses = [abs(d) if d < 0 else 0 for d in deltas]
# 평균 계산 (간단한 이동평균)
avg_gain = sum(gains[-period:]) / period if period <= len(gains) else sum(gains) / len(gains)
avg_loss = sum(losses[-period:]) / period if period <= len(losses) else sum(losses) / len(losses)
if avg_loss == 0:
return 100 if avg_gain > 0 else 50
rs = avg_gain / avg_loss
rsi = 100 - (100 / (1 + rs))
return round(rsi, 2)
def auto_fill_rsi14(row: dict, historical_closes: list[float] = None) -> dict:
"""
자동 충전: rsi_14 필드
입력:
row: 현재 행 (rsi_14 = None)
historical_closes: 과거 close 가격 (최소 14일)
출력:
row (rsi_14 채워짐) 또는 원본 (실패시)
"""
if not historical_closes or row.get("rsi_14") is not None:
return row
rsi14 = calculate_rsi14(historical_closes)
if rsi14 is not None:
row["rsi_14"] = rsi14
row["_fill_source"] = "auto_fill_rsi14_v1"
return row
@@ -0,0 +1,82 @@
#!/usr/bin/env python3
"""
WBS-9.3: Stop Price 자동 충전 절차
조건: stop_price IS NULL AND atr20 IS NOT NULL
"""
def calculate_stop_price(
entry_price: float = None,
close_price: float = None,
atr20: float = None,
multiplier: float = 2.0,
fallback_pct: float = -5.0
) -> float:
"""
Calculate stop loss price.
전략 (우선순위):
1. entry_price 있으면: entry - (atr20 × multiplier)
2. entry_price 없으면: close - (atr20 × multiplier)
3. atr20 없으면: price × (1 + fallback_pct/100)
입력:
entry_price: 진입가 (선택)
close_price: 현재가 (필수 또는 entry_price)
atr20: ATR20 (권장)
multiplier: ATR 배수 (default: 2.0)
fallback_pct: ATR 미사용시 폴백 (default: -5%)
출력:
stop_price (숫자) 또는 None (실패시)
"""
# ATR 기반 계산 (권장)
if atr20 is not None and atr20 > 0:
base_price = entry_price if entry_price is not None else close_price
if base_price is not None and base_price > 0:
stop_price = base_price - (atr20 * multiplier)
return max(round(stop_price, 0), base_price * 0.8) # 최소 20% 하방선
# Fallback: 단순 백분율 기반
if close_price is not None and close_price > 0:
fallback_stop = close_price * (1 + fallback_pct / 100)
return max(round(fallback_stop, 0), close_price * 0.7) # 최소 30% 하방선
return None
def auto_fill_stop_price(
row: dict,
multiplier: float = 2.0,
fallback_pct: float = -5.0
) -> dict:
"""
자동 충전: stop_price 필드
입력:
row: 현재 행 (stop_price = None)
multiplier: ATR 배수 (default: 2.0)
fallback_pct: 폴백 백분율 (default: -5%)
출력:
row (stop_price 채워짐) 또는 원본 (실패시)
"""
if row.get("stop_price") is not None:
return row
stop_price = calculate_stop_price(
entry_price=row.get("entry_price"),
close_price=row.get("close_price"),
atr20=row.get("atr20"),
multiplier=multiplier,
fallback_pct=fallback_pct
)
if stop_price is not None:
row["stop_price"] = stop_price
row["_fill_source"] = "auto_fill_stop_price_v1"
row["_stop_price_basis"] = (
"atr20" if row.get("atr20") is not None else "fallback_pct"
)
return row
+92
View File
@@ -0,0 +1,92 @@
#!/usr/bin/env python3
"""
WBS-9.3: Velocity 자동 충전 절차
조건: velocity_1d IS NULL AND (close_price AND previous_close_price) IS NOT NULL
"""
def calculate_velocity_1d(close_price: float, previous_close: float) -> float:
"""
Calculate 1-day velocity (percentage change).
입력: close_price, previous_close
출력: velocity_1d (%, e.g., 2.5 = +2.5%)
"""
if previous_close is None or previous_close == 0:
return None
velocity = ((close_price - previous_close) / previous_close) * 100
return round(velocity, 2)
def calculate_velocity_5d(closes: list[float]) -> float:
"""
Calculate 5-day velocity (5-period momentum).
입력: close 가격 리스트 (최소 5개)
출력: velocity_5d (%, e.g., 5.2 = +5.2%)
"""
if not closes or len(closes) < 5:
return None
current = closes[-1]
five_days_ago = closes[-5]
if five_days_ago is None or five_days_ago == 0:
return None
velocity = ((current - five_days_ago) / five_days_ago) * 100
return round(velocity, 2)
def auto_fill_velocity_1d(row: dict) -> dict:
"""
자동 충전: velocity_1d 필드
입력:
row: 현재 행 (velocity_1d = None)
필수: close_price, previous_close_price
출력:
row (velocity_1d 채워짐) 또는 원본 (실패시)
"""
if row.get("velocity_1d") is not None:
return row
close = row.get("close_price")
prev_close = row.get("previous_close_price")
if close is None or prev_close is None:
return row
velocity_1d = calculate_velocity_1d(close, prev_close)
if velocity_1d is not None:
row["velocity_1d"] = velocity_1d
row["_fill_source"] = "auto_fill_velocity_v1"
return row
def auto_fill_velocity_5d(row: dict, historical_closes: list[float] = None) -> dict:
"""
자동 충전: velocity_5d 필드
입력:
row: 현재 행
historical_closes: 과거 close 가격 (최소 5일)
출력:
row (velocity_5d 채워짐) 또는 원본 (실패시)
"""
if row.get("velocity_5d") is not None:
return row
if not historical_closes:
return row
velocity_5d = calculate_velocity_5d(historical_closes)
if velocity_5d is not None:
row["velocity_5d"] = velocity_5d
row["_fill_source"] = "auto_fill_velocity_v1"
return row