Skip to content

Commit 30cbb5d

Browse files
authored
Merge pull request #37 from SynergyX-AI-Pattern/feat/#36_add_highlight_range_to_backtest_response
Feat/#36 add highlight range to backtest response
2 parents 62d44f8 + 2582239 commit 30cbb5d

File tree

2 files changed

+48
-13
lines changed

2 files changed

+48
-13
lines changed

app/schemas/backtest.py

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1+
from typing import Optional
12
from pydantic import BaseModel
2-
from datetime import date
3+
from datetime import date, datetime
34

45
class BacktestRequest(BaseModel):
56
startDate: date
@@ -14,6 +15,19 @@ class BacktestRequest(BaseModel):
1415
}
1516
}
1617

18+
class HighlightRange(BaseModel):
19+
fromDate: datetime
20+
toDate: datetime
21+
22+
model_config = {
23+
"json_schema_extra": {
24+
"example": {
25+
"fromDate": "2024-02-01T09:00:00",
26+
"toDate": "2024-03-15T15:00:00"
27+
}
28+
}
29+
}
30+
1731
class BacktestResponse(BaseModel):
1832
matchedCount: int
1933
winRate: float
@@ -25,6 +39,7 @@ class BacktestResponse(BaseModel):
2539
totalReturn: float
2640
lastMatchedDate: date
2741
lastMatchedReturn: float
42+
highlightRange: Optional[HighlightRange] = None
2843

2944
model_config = {
3045
"json_schema_extra": {
@@ -38,7 +53,11 @@ class BacktestResponse(BaseModel):
3853
"minReturnDate": "2024-02-10",
3954
"totalReturn": 88.3,
4055
"lastMatchedDate": "2024-04-20",
41-
"lastMatchedReturn": 9.3
56+
"lastMatchedReturn": 9.3,
57+
"highlightRange": {
58+
"fromDate": "2024-02-01T09:00:00",
59+
"toDate": "2024-03-15T15:00:00"
60+
}
4261
}
4362
}
4463
}

app/services/backtest_service.py

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from app.crud.stock import get_stock_by_id
77
from app.crud.pattern import get_pattern_by_id
88
from app.crud.stock_timeseries import get_stock_timeseries_by_unit
9-
from app.schemas.backtest import BacktestResponse, BacktestRequest
9+
from app.schemas.backtest import BacktestResponse, BacktestRequest, HighlightRange
1010
from app.exceptions.base import APIException
1111
from app.api_payload.code.status_code import ErrorStatus
1212
from app.utils.timeseries_calculator import (
@@ -110,7 +110,7 @@ def _calculate_returns(
110110
unit: str,
111111
value: int,
112112
pat_len: int
113-
) -> List[Tuple[datetime, float]]:
113+
) -> List[Tuple[datetime, float, datetime, datetime]]:
114114
"""
115115
각 매칭 구간의 수익률 계산합니다.
116116
@@ -122,7 +122,7 @@ def _calculate_returns(
122122
pat_len: 패턴 길이
123123
124124
Returns:
125-
(진입 시점, 수익률) 리스트
125+
(진입 시점, 수익률, 구간 시작 지점, 구간 종료 지점) 리스트
126126
"""
127127
returns = []
128128

@@ -150,26 +150,35 @@ def _calculate_returns(
150150
# 범위 초과 시 마지막 종가로 설정
151151
if pos >= len(closes):
152152
pos = len(closes) - 1
153+
153154
# 이전 시점이 더 가까우면 뒤로 이동
154155
elif pos > 0 and (timestamps[pos] - tgt) > (tgt - timestamps[pos - 1]):
155156
pos -= 1
157+
156158
# 진입가, 청산가 활용하여 수익률 계산
157159
entry_p, exit_p = closes[entry_i], closes[pos]
158-
# 수익률
159-
returns.append((entry_time, (exit_p - entry_p) / entry_p * 100))
160+
ret = (exit_p - entry_p) / entry_p * 100
161+
162+
# 매칭 구간 저장
163+
match_start = timestamps[idx]
164+
match_end = timestamps[entry_i]
165+
166+
# 수익률 포함 결과 매칭 구간 저장
167+
returns.append((entry_time, ret, match_start, match_end))
168+
160169
return returns
161170

162171
@staticmethod
163172
def _aggregate_results(
164-
returns: List[Tuple[datetime, float]],
173+
returns: List[Tuple[datetime, float, datetime, datetime]],
165174
start_date: date,
166175
match_count: int
167176
) -> BacktestResponse:
168177
"""
169178
최종 응답을 반환합니다.
170179
171180
Parameters:
172-
returns: (진입 시점, 수익률) 리스트
181+
returns: (진입 시점, 수익률, 구간 시작 지점, 구간 종료 지점) 리스트
173182
start_date : 시작 날짜
174183
match_count: 매칭된 구간 수
175184
@@ -187,20 +196,27 @@ def _aggregate_results(
187196
minReturnDate=start_date,
188197
totalReturn=0.0,
189198
lastMatchedDate=start_date,
190-
lastMatchedReturn=0.0
199+
lastMatchedReturn=0.0,
200+
highlightRange=None
191201
)
192-
dates, vals = zip(*returns)
202+
dates, vals, match_starts, match_ends = zip(*returns)
193203
wins = [v for v in vals if v > 0]
194204

205+
max_idx = vals.index(max(vals))
206+
195207
return BacktestResponse(
196208
matchedCount=match_count,
197209
winRate=len(wins)/len(vals) * 100,
198210
averageReturn=sum(vals)/len(vals),
199211
maxReturn=max(vals),
200-
maxReturnDate=dates[vals.index(max(vals))].date(),
212+
maxReturnDate=dates[max_idx].date(),
201213
minReturn=min(vals),
202214
minReturnDate=dates[vals.index(min(vals))].date(),
203215
totalReturn=sum(vals),
204216
lastMatchedDate=dates[-1].date(),
205-
lastMatchedReturn=vals[-1]
217+
lastMatchedReturn=vals[-1],
218+
highlightRange=HighlightRange(
219+
fromDate=match_starts[max_idx].isoformat(),
220+
toDate=match_ends[max_idx].isoformat()
221+
)
206222
)

0 commit comments

Comments
 (0)