Skip to content

Commit 947df0a

Browse files
authored
Merge pull request #45 from SynergyX-AI-Pattern/refactor/#43_backtest_accuracy
Refactor/#43 backtest accuracy
2 parents 9db895f + 8113f44 commit 947df0a

File tree

1 file changed

+53
-5
lines changed

1 file changed

+53
-5
lines changed

app/services/backtest_service.py

Lines changed: 53 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,13 @@ def execute_backtest(stock_id: int, pattern_id: int, request: BacktestRequest, d
5353
)
5454

5555
# dtw 로직
56-
idxes, _ = find_match_indices(pattern_obj.points, closes, pattern_obj.tolerance)
56+
idxes, distances = find_match_indices(pattern_obj.points, closes, pattern_obj.tolerance)
57+
58+
# 유사도 계산 (백테스팅 평가 지표)
59+
similarities = [BacktestService._convert_distance_to_similarity(d) for d in distances]
60+
if similarities:
61+
avg_sim = sum(similarities) / len(similarities)
62+
logger.info(f"[Backtest] 평균 유사도: {avg_sim:.3f}, 매칭 개수: {len(similarities)}")
5763

5864
# 수익률 계산
5965
returns = BacktestService._calculate_returns(idxes, closes, timestamps, unit, value, len(pattern_obj.points))
@@ -130,10 +136,39 @@ def _fetch_timeseries_by_unit(
130136
if not rows:
131137
raise APIException(ErrorStatus.STOCK_OHLCV_NOT_FOUND)
132138

133-
# (timestamp, close) 형태로 분리 후 리스트 반환
139+
# (timestamp, close) 형태로 분리
134140
timestamps, closes = zip(*rows)
141+
142+
# 노이즈 제거
143+
closes = BacktestService._preprocess_series(closes)
144+
145+
# 리스트 반환
135146
return list(timestamps), list(closes)
136147

148+
@staticmethod
149+
def _preprocess_series(
150+
closes: List[float],
151+
window: int = 5
152+
) -> List[float]:
153+
"""
154+
노이즈를 제거하고 정규화 과정을 진행시킵니다.
155+
"""
156+
157+
# 노이즈 제거
158+
series = pd.Series(closes).rolling(window=window, center=True).mean().bfill().ffill()
159+
160+
# 정규화
161+
normed = (series - series.mean()) / (series.std() + 1e-8)
162+
return normed.to_list()
163+
164+
@staticmethod
165+
def _convert_distance_to_similarity(distance: float) -> float:
166+
"""
167+
DTW 거리값을 0~1 사이 유사도로 변환합니다.
168+
- formula: similarity = 1 / (1 + distance)
169+
"""
170+
return round(1 / (1 + distance), 4)
171+
137172
@staticmethod
138173
def _calculate_returns(
139174
idxes: List[int],
@@ -187,9 +222,22 @@ def _calculate_returns(
187222
elif pos > 0 and (timestamps[pos] - tgt) > (tgt - timestamps[pos - 1]):
188223
pos -= 1
189224

190-
# 진입가, 청산가 활용하여 수익률 계산
191-
entry_p, exit_p = closes[entry_i], closes[pos]
192-
ret = (exit_p - entry_p) / entry_p * 100
225+
# 진입가
226+
entry_p = closes[entry_i]
227+
228+
# 평균가
229+
segment_prices = closes[entry_i:pos+1]
230+
if len(segment_prices) < 2:
231+
continue
232+
avg_price = sum(segment_prices) / len(segment_prices)
233+
234+
# 청산가
235+
exit_p = segment_prices[-1]
236+
237+
# 하이브리드 수익률 계산 (진입가, 평균가, 청산가 활용)
238+
ret_avg = (avg_price - entry_p) / entry_p * 100
239+
ret_exit = (exit_p - entry_p) / entry_p * 100
240+
ret = (ret_avg + ret_exit) / 2
193241

194242
# 매칭 구간 저장
195243
match_start = timestamps[idx]

0 commit comments

Comments
 (0)