refactor(phase2): Add DayTradingStrategy example and indicators module
This commit is contained in:
158
backend/app/strategy/indicators.py
Normal file
158
backend/app/strategy/indicators.py
Normal file
@@ -0,0 +1,158 @@
|
||||
"""
|
||||
Technical Indicators
|
||||
|
||||
보조지표 계산 함수들
|
||||
"""
|
||||
from typing import List, Optional, Tuple
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def sma(prices: List[float], period: int) -> Optional[float]:
|
||||
"""단순 이동평균 (Simple Moving Average)"""
|
||||
if len(prices) < period:
|
||||
return None
|
||||
return sum(prices[-period:]) / period
|
||||
|
||||
|
||||
def ema(prices: List[float], period: int, prev_ema: Optional[float] = None) -> Optional[float]:
|
||||
"""지수 이동평균 (Exponential Moving Average)"""
|
||||
if len(prices) < period:
|
||||
return None
|
||||
|
||||
k = 2 / (period + 1)
|
||||
if prev_ema is None:
|
||||
# 초기값은 SMA
|
||||
prev_ema = sum(prices[-period:]) / period
|
||||
|
||||
return prices[-1] * k + prev_ema * (1 - k)
|
||||
|
||||
|
||||
def rsi(prices: List[float], period: int = 14) -> Optional[float]:
|
||||
"""
|
||||
RSI (Relative Strength Index)
|
||||
|
||||
Args:
|
||||
prices: 종가 리스트
|
||||
period: 기간 (기본 14)
|
||||
|
||||
Returns:
|
||||
0~100 사이 RSI 값
|
||||
"""
|
||||
if len(prices) < period + 1:
|
||||
return None
|
||||
|
||||
gains, losses = [], []
|
||||
for i in range(-period, 0):
|
||||
delta = prices[i] - prices[i - 1]
|
||||
gains.append(max(delta, 0))
|
||||
losses.append(max(-delta, 0))
|
||||
|
||||
avg_gain = sum(gains) / period
|
||||
avg_loss = sum(losses) / period
|
||||
|
||||
if avg_loss == 0:
|
||||
return 100.0
|
||||
|
||||
rs = avg_gain / avg_loss
|
||||
return 100 - (100 / (1 + rs))
|
||||
|
||||
|
||||
def bollinger_bands(prices: List[float], period: int = 20, std_mult: float = 2.0) -> Optional[Tuple[float, float, float]]:
|
||||
"""
|
||||
볼린저 밴드
|
||||
|
||||
Returns:
|
||||
(upper, middle, lower) 또는 None
|
||||
"""
|
||||
if len(prices) < period:
|
||||
return None
|
||||
|
||||
window = prices[-period:]
|
||||
middle = sum(window) / period
|
||||
variance = sum((p - middle) ** 2 for p in window) / period
|
||||
std = variance ** 0.5
|
||||
|
||||
upper = middle + std_mult * std
|
||||
lower = middle - std_mult * std
|
||||
|
||||
return (upper, middle, lower)
|
||||
|
||||
|
||||
def macd(prices: List[float], fast: int = 12, slow: int = 26, signal: int = 9) -> Optional[Tuple[float, float, float]]:
|
||||
"""
|
||||
MACD (Moving Average Convergence Divergence)
|
||||
|
||||
Returns:
|
||||
(macd_line, signal_line, histogram) 또는 None
|
||||
"""
|
||||
if len(prices) < slow:
|
||||
return None
|
||||
|
||||
# EMA 계산 (재귀적으로 계산)
|
||||
def calc_ema(data: List[float], period: int) -> float:
|
||||
k = 2 / (period + 1)
|
||||
ema_val = sum(data[:period]) / period
|
||||
for p in data[period:]:
|
||||
ema_val = p * k + ema_val * (1 - k)
|
||||
return ema_val
|
||||
|
||||
ema_fast = calc_ema(prices, fast)
|
||||
ema_slow = calc_ema(prices, slow)
|
||||
macd_line = ema_fast - ema_slow
|
||||
|
||||
# Signal line = MACD의 EMA(9)
|
||||
# 실제로는 과거 MACD 값들 필요 (간략화)
|
||||
signal_line = macd_line # TODO: 정확한 구현 필요
|
||||
histogram = macd_line - signal_line
|
||||
|
||||
return (macd_line, signal_line, histogram)
|
||||
|
||||
|
||||
def atr(highs: List[float], lows: List[float], closes: List[float], period: int = 14) -> Optional[float]:
|
||||
"""
|
||||
ATR (Average True Range)
|
||||
|
||||
Args:
|
||||
highs: 고가 리스트
|
||||
lows: 저가 리스트
|
||||
closes: 종가 리스트
|
||||
period: 기간
|
||||
|
||||
Returns:
|
||||
ATR 값
|
||||
"""
|
||||
if len(highs) < period + 1 or len(lows) < period + 1 or len(closes) < period + 1:
|
||||
return None
|
||||
|
||||
trs = []
|
||||
for i in range(1, len(highs)):
|
||||
h = highs[i]
|
||||
l = lows[i]
|
||||
cp = closes[i - 1]
|
||||
tr = max(h - l, abs(h - cp), abs(l - cp))
|
||||
trs.append(tr)
|
||||
|
||||
if len(trs) < period:
|
||||
return None
|
||||
|
||||
return sum(trs[-period:]) / period
|
||||
|
||||
|
||||
def vwap(prices: List[float], volumes: List[float]) -> Optional[float]:
|
||||
"""
|
||||
VWAP (Volume Weighted Average Price)
|
||||
|
||||
Returns:
|
||||
VWAP 값
|
||||
"""
|
||||
if len(prices) != len(volumes) or not prices:
|
||||
return None
|
||||
|
||||
total_volume = sum(volumes)
|
||||
if total_volume == 0:
|
||||
return None
|
||||
|
||||
total_pv = sum(p * v for p, v in zip(prices, volumes))
|
||||
return total_pv / total_volume
|
||||
Reference in New Issue
Block a user