Files
openclaw/agents/stock/workspace/sim/indicators.py
T
hyowons 69ef9c09e8 auto: 일일 백업 2026-06-09 02:00
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 02:00:01 +09:00

84 lines
3.0 KiB
Python

"""기술지표 — 순수 함수. 일봉 리스트(시간 오름차순)나 종가 리스트를 받아 계산만 한다.
입력 candle dict 키: open/high/low/close/volume (daily_candles_cache.get_candles 형식).
모든 함수는 데이터 부족 시 None 을 반환한다 (raise X).
"""
from __future__ import annotations
def sma(values: list[float], period: int) -> float | None:
"""최근 period 개 단순이동평균."""
if period <= 0 or len(values) < period:
return None
return sum(values[-period:]) / period
def rsi(closes: list[float], period: int = 14) -> float | None:
"""Wilder RSI. closes 오름차순, period+1 개 이상 필요."""
if len(closes) < period + 1:
return None
gains = losses = 0.0
for i in range(1, period + 1):
diff = closes[i] - closes[i - 1]
if diff >= 0:
gains += diff
else:
losses -= diff
avg_gain = gains / period
avg_loss = losses / period
for i in range(period + 1, len(closes)):
diff = closes[i] - closes[i - 1]
gain = diff if diff > 0 else 0.0
loss = -diff if diff < 0 else 0.0
avg_gain = (avg_gain * (period - 1) + gain) / period
avg_loss = (avg_loss * (period - 1) + loss) / period
if avg_loss == 0:
return 100.0
rs = avg_gain / avg_loss
return 100.0 - (100.0 / (1.0 + rs))
def atr(candles: list[dict], period: int = 14) -> float | None:
"""Average True Range (Wilder). candles 오름차순, period+1 개 이상 필요."""
if len(candles) < period + 1:
return None
trs: list[float] = []
for i in range(1, len(candles)):
h = candles[i]['high']
lo = candles[i]['low']
prev_close = candles[i - 1]['close']
trs.append(max(h - lo, abs(h - prev_close), abs(lo - prev_close)))
if len(trs) < period:
return None
atr_val = sum(trs[:period]) / period
for tr in trs[period:]:
atr_val = (atr_val * (period - 1) + tr) / period
return atr_val
def recent_high(candles: list[dict], period: int, exclude_last: bool = True) -> float | None:
"""최근 period 봉 최고가. exclude_last 면 마지막(오늘) 봉 제외 → 돌파 판정용."""
series = candles[:-1] if exclude_last else candles
window = series[-period:]
if not window:
return None
return max(c['high'] for c in window)
def recent_low(candles: list[dict], period: int, exclude_last: bool = True) -> float | None:
"""최근 period 봉 최저가 — 손절선 후보."""
series = candles[:-1] if exclude_last else candles
window = series[-period:]
if not window:
return None
return min(c['low'] for c in window)
def volume_avg(candles: list[dict], period: int, exclude_last: bool = True) -> float | None:
"""최근 period 봉 평균 거래량. exclude_last 면 오늘 제외."""
series = candles[:-1] if exclude_last else candles
window = series[-period:]
if len(window) < period:
return None
return sum(c['volume'] for c in window) / period