Coverage for src / mysingle / dsl / stdlib.py: 0%
52 statements
« prev ^ index » next coverage.py v7.12.0, created at 2025-12-02 00:58 +0900
« prev ^ index » next coverage.py v7.12.0, created at 2025-12-02 00:58 +0900
1"""DSL 표준 라이브러리 - 공통 함수"""
3from typing import Any, Callable, Literal
5import pandas as pd
8def SMA(series: pd.Series, window: int) -> pd.Series:
9 """
10 Simple Moving Average
12 Args:
13 series: 입력 시리즈 (보통 close 가격)
14 window: 이동평균 기간
16 Returns:
17 pd.Series: SMA 값
18 """
19 return series.rolling(window=window).mean()
22def EMA(series: pd.Series, span: int) -> pd.Series:
23 """
24 Exponential Moving Average
26 Args:
27 series: 입력 시리즈
28 span: EMA 기간
30 Returns:
31 pd.Series: EMA 값
32 """
33 return series.ewm(span=span, adjust=False).mean()
36def WMA(series: pd.Series, window: int) -> pd.Series:
37 """
38 Weighted Moving Average
40 Args:
41 series: 입력 시리즈
42 window: WMA 기간
44 Returns:
45 pd.Series: WMA 값
46 """
47 weights = pd.Series(range(1, window + 1))
48 return series.rolling(window).apply(lambda x: (x * weights).sum() / weights.sum())
51def crossover(series1: pd.Series, series2: pd.Series) -> pd.Series:
52 """
53 상향 돌파 검사
55 series1이 series2를 아래에서 위로 돌파하는 시점 탐지
57 Args:
58 series1: 비교 대상 시리즈 1
59 series2: 비교 대상 시리즈 2
61 Returns:
62 pd.Series: 상향 돌파 시 True
63 """
64 return (series1 > series2) & (series1.shift(1) <= series2.shift(1))
67def crossunder(series1: pd.Series, series2: pd.Series) -> pd.Series:
68 """
69 하향 돌파 검사
71 series1이 series2를 위에서 아래로 돌파하는 시점 탐지
73 Args:
74 series1: 비교 대상 시리즈 1
75 series2: 비교 대상 시리즈 2
77 Returns:
78 pd.Series: 하향 돌파 시 True
79 """
80 return (series1 < series2) & (series1.shift(1) >= series2.shift(1))
83def highest(series: pd.Series, window: int) -> pd.Series:
84 """
85 N일 최고값
87 Args:
88 series: 입력 시리즈
89 window: 기간
91 Returns:
92 pd.Series: N일 최고값
93 """
94 return series.rolling(window=window).max()
97def lowest(series: pd.Series, window: int) -> pd.Series:
98 """
99 N일 최저값
101 Args:
102 series: 입력 시리즈
103 window: 기간
105 Returns:
106 pd.Series: N일 최저값
107 """
108 return series.rolling(window=window).min()
111def change(series: pd.Series, periods: int = 1) -> pd.Series:
112 """
113 절대 변화량
115 Args:
116 series: 입력 시리즈
117 periods: 기간 (기본 1)
119 Returns:
120 pd.Series: 절대 변화량
121 """
122 return series.diff(periods)
125def pct_change(series: pd.Series, periods: int = 1) -> pd.Series:
126 """
127 백분율 변화량
129 Args:
130 series: 입력 시리즈
131 periods: 기간 (기본 1)
133 Returns:
134 pd.Series: 백분율 변화량
135 """
136 return series.pct_change(periods)
139def stdev(series: pd.Series, window: int) -> pd.Series:
140 """
141 표준편차
143 Args:
144 series: 입력 시리즈
145 window: 기간
147 Returns:
148 pd.Series: 표준편차
149 """
150 return series.rolling(window=window).std()
153def bbands(series: pd.Series, window: int = 20, num_std: float = 2.0) -> pd.DataFrame:
154 """
155 Bollinger Bands
157 Args:
158 series: 입력 시리즈 (보통 close 가격)
159 window: 이동평균 기간 (기본 20)
160 num_std: 표준편차 배수 (기본 2.0)
162 Returns:
163 pd.DataFrame: upper, middle, lower 컬럼
164 """
165 middle = SMA(series, window)
166 std = stdev(series, window)
168 return pd.DataFrame(
169 {
170 "upper": middle + (std * num_std),
171 "middle": middle,
172 "lower": middle - (std * num_std),
173 }
174 )
177def RSI(series: pd.Series, period: int = 14) -> pd.Series:
178 """
179 Relative Strength Index
181 Args:
182 series: 입력 시리즈 (보통 close 가격)
183 period: RSI 기간 (기본 14)
185 Returns:
186 pd.Series: RSI 값 (0-100 범위)
187 """
188 if period <= 0:
189 raise ValueError("Period must be greater than 0")
191 # 가격 변화 계산
192 delta = series.diff().astype(float)
194 # 상승/하락 분리
195 gain = delta.where(delta > 0, 0.0)
196 loss = -delta.where(delta < 0, 0.0)
198 # 평균 상승/하락 (EMA 사용)
199 avg_gain = gain.ewm(span=period, adjust=False).mean()
200 avg_loss = loss.ewm(span=period, adjust=False).mean()
202 # RS 계산
203 rs = avg_gain / avg_loss
205 # RSI 계산
206 rsi = 100 - (100 / (1 + rs))
208 return rsi
211def atr(
212 high: pd.Series, low: pd.Series, close: pd.Series, window: int = 14
213) -> pd.Series:
214 """
215 Average True Range
217 Args:
218 high: 고가 시리즈
219 low: 저가 시리즈
220 close: 종가 시리즈
221 window: ATR 기간 (기본 14)
223 Returns:
224 pd.Series: ATR 값
225 """
226 # True Range 계산
227 high_low = high - low
228 high_close = abs(high - close.shift(1))
229 low_close = abs(low - close.shift(1))
231 tr = pd.DataFrame({"hl": high_low, "hc": high_close, "lc": low_close}).max(axis=1)
233 # ATR (EMA of TR)
234 return EMA(tr, window)
237# ============================================================================
238# 전략 특화 함수 (Strategy Service 전용)
239# ============================================================================
242def generate_signal(
243 condition: pd.Series, signal_type: Literal["long", "short"] = "long"
244) -> pd.Series:
245 """
246 조건을 명시적 boolean 시그널로 변환
248 Args:
249 condition: 조건 Series (True/False)
250 signal_type: 시그널 방향 ("long" or "short")
252 Returns:
253 pd.Series[bool]: 시그널 (True = 진입, False = 보유)
255 Example:
256 >>> oversold = data['RSI'] < 30
257 >>> buy_signal = generate_signal(oversold, signal_type="long")
258 """
259 # 명시적 boolean 변환
260 return condition.astype(bool)
263def entry_exit_signals(
264 entry_condition: pd.Series, exit_condition: pd.Series
265) -> pd.DataFrame:
266 """
267 진입 조건과 청산 조건을 페어로 생성
269 Args:
270 entry_condition: 진입 조건 Series
271 exit_condition: 청산 조건 Series
273 Returns:
274 pd.DataFrame: {'entry': pd.Series[bool], 'exit': pd.Series[bool]}
276 Example:
277 >>> entry = crossover(data['SMA_50'], data['SMA_200'])
278 >>> exit = crossunder(data['SMA_50'], data['SMA_200'])
279 >>> signals = entry_exit_signals(entry, exit)
280 >>> result = signals['entry'] # 진입 시그널만 반환
281 """
282 return pd.DataFrame(
283 {"entry": entry_condition.astype(bool), "exit": exit_condition.astype(bool)}
284 )
287def signal_filter(signals: pd.Series, filter_condition: pd.Series) -> pd.Series:
288 """
289 시그널을 필터 조건으로 필터링
291 Args:
292 signals: 시그널 Series
293 filter_condition: 필터 조건 Series
295 Returns:
296 pd.Series[bool]: 필터링된 시그널
298 Example:
299 >>> # RSI 과매도 시그널
300 >>> oversold = data['RSI'] < 30
301 >>>
302 >>> # 거래량 필터 (평균 대비 1.5배 이상)
303 >>> high_volume = data['volume'] > data['volume'].rolling(20).mean() * 1.5
304 >>>
305 >>> # 필터링된 시그널
306 >>> filtered = signal_filter(oversold, high_volume)
307 """
308 return (signals & filter_condition).astype(bool)
311def get_stdlib_functions() -> dict[str, Callable[..., Any]]:
312 """
313 표준 라이브러리 함수 딕셔너리 반환
315 Returns:
316 dict: 함수명 -> 함수 매핑
317 """
318 return {
319 # 이동평균
320 "SMA": SMA,
321 "EMA": EMA,
322 "WMA": WMA,
323 # 기술적 지표
324 "RSI": RSI,
325 # 크로스오버
326 "crossover": crossover,
327 "crossunder": crossunder,
328 # 최고/최저
329 "highest": highest,
330 "lowest": lowest,
331 # 변화율
332 "change": change,
333 "pct_change": pct_change,
334 # 변동성
335 "stdev": stdev,
336 "bbands": bbands,
337 "atr": atr,
338 # 전략 특화 함수
339 "generate_signal": generate_signal,
340 "entry_exit_signals": entry_exit_signals,
341 "signal_filter": signal_filter,
342 }