-
Notifications
You must be signed in to change notification settings - Fork 2
[AI] SISC-195 계산 프로세스 피쳐 코드 수정 리펙토링 #201
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
WalkthroughIndexDataCollector 제거 및 데이터 수집 모듈 리팩토링, 특성 엔지니어링 구조 개편, 신호/모델 파이프라인 업데이트, 새로운 위험 관리자 추가. 여러 파일에서 기술 지표 함수 import 경로를 legacy 모듈로 통합 변경. Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 8
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (4)
AI/modules/finder/evaluator.py (1)
46-60: 존재하지 않는 컬럼 참조로 인한 런타임 에러 발생.
add_technical_indicators함수는ma20_ratio,ma60_ratio,macd_ratio컬럼을 생성하지만, 이 코드에서는ma20,ma60,macd,signal_line컬럼을 참조하고 있습니다. 이로 인해KeyError가 발생합니다.
AI/modules/features/legacy/technical_features.py의 구현을 확인하면:
- 이평선:
ma{window}_ratio형태로 생성 (예:ma20_ratio)- MACD:
macd_ratio로 생성되며,signal_line은 생성되지 않음🐛 컬럼명 수정 제안
# 1. 정배열 점수 - if last_row['close'] > last_row['ma20'] > last_row['ma60']: + ma20 = df['close'].rolling(window=20).mean().iloc[-1] + ma60 = df['close'].rolling(window=60).mean().iloc[-1] + if last_row['close'] > ma20 > ma60: score += 30 - elif last_row['close'] > last_row['ma20']: + elif last_row['close'] > ma20: score += 10 # 2. RSI 점수 rsi = last_row['rsi'] if rsi <= 30: score += 20 elif 30 < rsi < 70: score += 10 # 3. MACD 모멘텀 - if last_row['macd'] > last_row['signal_line']: + # MACD ratio가 양수이면 상승 모멘텀으로 판단 + if last_row['macd_ratio'] > 0: score += 20AI/modules/signal/workflows/optimize_thresholds.py (1)
78-80: 중복 데이터 로드 호출.
load_data_from_db(start_date="2022-01-01")가 연속으로 두 번 호출되고 있습니다. 불필요한 DB 조회로 인해 성능 저하가 발생합니다.🐛 중복 호출 제거
# 3. 데이터 로드 (최근 2년치) print(">> 데이터 로드 중 (최근 2년)...") df = loader.load_data_from_db(start_date="2022-01-01") - # 검증용이므로 최근 데이터만 가져옴 (너무 옛날 데이터로 최적화하면 안 맞을 수 있음) - df = loader.load_data_from_db(start_date="2022-01-01")AI/modules/signal/core/data_loader.py (1)
198-211: 이동평균 피처 컬럼명 불일치.
potential_features목록에서 이동평균 컬럼명이ma_5_ratio,ma_20_ratio,ma_60_ratio로 정의되어 있지만,add_technical_indicators함수는ma5_ratio,ma20_ratio,ma60_ratio(언더스코어 없음)를 생성합니다.
available_cols필터링으로 인해 에러는 발생하지 않지만, 이동평균 관련 피처가 모델 학습에서 누락됩니다.🐛 컬럼명 수정
potential_features = [ # 1. Technical 'log_return', 'open_ratio', 'high_ratio', 'low_ratio', 'vol_change', - 'ma_5_ratio', 'ma_20_ratio', 'ma_60_ratio', + 'ma5_ratio', 'ma20_ratio', 'ma60_ratio', 'rsi', 'macd_ratio', 'bb_position',AI/modules/trader/backtest/run_backtrader_single.py (1)
203-209: SQL 인젝션 취약점.f-string을 사용한 SQL 쿼리 조합은 SQL 인젝션 공격에 취약합니다. 악의적인
ticker값이 전달될 경우 데이터 유출이나 손상이 발생할 수 있습니다.🔒 파라미터화된 쿼리 사용 제안
from AI.libs.database.connection import get_db_conn + from sqlalchemy import text conn = get_db_conn() - query = f""" + query = text(""" SELECT date, open, high, low, close, volume, adjusted_close, ticker FROM price_data - WHERE ticker = '{ticker}' AND date >= '2022-01-01' + WHERE ticker = :ticker AND date >= '2022-01-01' ORDER BY date ASC - """ - df = pd.read_sql(query, conn) + """) + df = pd.read_sql(query, conn, params={"ticker": ticker}) conn.close()
🤖 Fix all issues with AI agents
In `@AI/modules/data_collector/market_breadth_data.py`:
- Around line 158-163: The except block currently calls conn.rollback() without
guaranteeing conn exists which can raise NameError; update the error handling in
the try/except around the yf.download flow so you either initialize conn = None
before the try or guard the rollback with a check (e.g., if 'conn' in locals()
or if conn is not None) and only call conn.rollback() when a connection object
is present; also keep the existing finally guards for cursor/conn closure
(cursor and conn) as-is to ensure safe cleanup.
In `@AI/modules/data_collector/run.py`:
- Around line 200-213: The printed step numbers for the market breadth blocks
are swapped: update the log strings so MarketBreadthCollector prints "Step 8"
and MarketBreadthStatsCollector prints "Step 9" (or otherwise ensure the printed
step numbers follow execution order); locate the print calls in run.py near the
MarketBreadthCollector and MarketBreadthStatsCollector blocks and correct the
step number text in those print statements.
In `@AI/modules/features/event_features.py`:
- Around line 12-18: The event window flags in add_event_window_flags lose true
values at the DataFrame edges because rolling(window=3, center=True) uses the
default min_periods=3; change the rolling call in add_event_window_flags to
specify min_periods=1 so the 3-row centered expansion treats partial windows at
start/end as valid, e.g. update the expression that computes
df[f'event_window_flag_{col_name}'] to use rolling(window=3, center=True,
min_periods=1) before .max().fillna(0).astype(bool) to preserve edge events.
In `@AI/modules/features/market_derived.py`:
- Line 4: The import line uses an incorrect absolute path and references a
missing symbol: change the import in market_derived.py to a relative import
(e.g., from .technical import ...) and resolve the absent compute_atr by either
replacing it with the existing compute_atr_rank (update any call sites in
market_derived.py to use compute_atr_rank) or implement a new compute_atr in
technical.py and export it; ensure you keep compute_rsi, compute_macd, and
compute_bollinger_bands names intact when updating the import.
In `@AI/modules/features/processor.py`:
- Line 19: The execute_pipeline method defines a sector_df parameter that is not
used; either remove sector_df from the signature of execute_pipeline or, if you
plan to use it later, keep the parameter and add a clear TODO comment inside the
execute_pipeline function (e.g., "# TODO: use sector_df for relative strength
calculations") so future readers know it is intentionally reserved; update the
method signature or add the TODO in the execute_pipeline definition to resolve
the unused-parameter issue.
In `@AI/modules/features/technical.py`:
- Around line 1-4: AI/modules/features/technical.py is missing the compute_atr
function which causes ImportError when AI/modules/features/market_derived.py
imports it; add a compute_atr implementation to technical.py (consistent with
the style of existing helpers like compute_z_score, compute_atr_rank,
compute_rsi, compute_bollinger_bands, compute_macd) that computes the Average
True Range from high/low/close series, returns the expected type/shape used by
market_derived.py, and ensure the function name export/signature matches what
market_derived.py imports (compute_atr) so the module can be imported without
error.
In `@AI/modules/signal/workflows/inference.py`:
- Line 28: 현재 사용 중인 add_technical_indicators 함수가 ma5/ma20/ma60, macd,
signal_line, upper_band, lower_band 등 일부 피처를 생성하지 않아 feature_cols 기반 전처리(변수
feature_cols)와 scaler 적용 시 KeyError 또는 스케일러 불일치가 발생할 수 있습니다; 이를 고치려면
add_technical_indicators를 호출하는 부분(함수 add_technical_indicators)을 수정해 누락된 지표들을 동일한
네이밍으로 생성하거나, 아니면 feature_cols 리스트를 현재 legacy 함수가 생성하는 컬럼 목록으로 동기화하고 scaler를 그에
맞춰 재학습/로드하도록 변경하며, 호출부에서 누락 컬럼에 대해 안전하게 처리(예: 컬럼 존재 여부 검사 및 적절한 기본값/보간 적용)하도록
구현하세요.
- Around line 37-39: The current base_dir construction and filenames are
incorrect: change base_dir (variable name base_dir) to build path with
model_type before "prod" (i.e., os.path.join(project_root, "AI", "data",
"weights", model_type, "prod")) and update model_path (variable model_path) to
point to the actual artifact "multi_horizon_model_prod.keras" and update
scaler_path (variable scaler_path) to "multi_horizon_scaler_prod.pkl" (single
underscore), ensuring you only adjust these three variables: base_dir,
model_path, and scaler_path.
🧹 Nitpick comments (15)
AI/modules/data_collector/macro_data.py (1)
174-200: to_py는 루프 밖으로 이동하고 불필요한 float 캐스팅을 피하세요.
매 반복마다 함수 정의가 발생하고, float() 강제 변환은 정밀도 손실 가능성이 있습니다.♻️ 제안 수정안
try: data_to_insert = [] - for date_idx, row in combined_df.iterrows(): - # [수정] Numpy 타입을 Python Native Type으로 변환하는 헬퍼 함수 - def to_py(val): - # NaN 또는 None 체크 - if pd.isna(val) or val is None: - return None - # Numpy 숫자 타입이면 .item()으로 변환 - if hasattr(val, 'item'): - return val.item() - return float(val) + # [수정] Numpy 타입을 Python Native Type으로 변환하는 헬퍼 함수 + def to_py(val): + if pd.isna(val) or val is None: + return None + if isinstance(val, np.generic): + return val.item() + return val + + for date_idx, row in combined_df.iterrows():AI/modules/data_collector/market_data.py (4)
82-111: DB 연결 관리 로직은 적절하나, 기존 연결 재사용 검토 필요PER/PBR 계산을 위해
fetch_ohlcv내에서 새로운 DB 연결을 생성하고 있습니다.finally블록에서 적절히 정리되고 있어 리소스 누수는 없지만, 대량의 티커를 처리할 때 매번 새 연결을 생성하는 것은 비효율적일 수 있습니다.향후 connection pooling 또는 클래스 레벨에서 연결을 관리하는 방식을 고려해 볼 수 있습니다.
103-105: 한 줄에 여러 문장 배치는 가독성을 저하시킴Decimal 타입 변환 로직이 한 줄에 조건문과 할당을 함께 작성되어 있습니다. 가독성과 코드 일관성을 위해 분리하는 것을 권장합니다.
♻️ 제안하는 수정
- if equity is not None: equity = float(equity) - if shares_issued is not None: shares_issued = float(shares_issued) - if eps is not None: eps = float(eps) + if equity is not None: + equity = float(equity) + if shares_issued is not None: + shares_issued = float(shares_issued) + if eps is not None: + eps = float(eps)
174-183: 루프 변수row캡처로 인한 late binding 이슈 가능성
get_val함수가 루프 내부에서 정의되어row변수를 캡처하고 있습니다. 현재 코드에서는get_val이 정의된 직후 동일 반복 내에서만 호출되므로 실질적인 버그는 없지만, 함수가 루프 외부로 전달되거나 지연 호출되면 late binding 문제가 발생할 수 있습니다.
row를 함수 인자로 명시적으로 전달하는 것이 더 안전합니다.♻️ 제안하는 수정
- # 안전한 값 추출 헬퍼 함수 - def get_val(col, default=0): - val = row.get(col, default) + # 안전한 값 추출 헬퍼 함수 (루프 외부로 이동 권장) + def get_val(row, col, default=0): + val = row.get(col, default) # Pandas Series 객체인 경우 처리 if hasattr(val, 'iloc'): val = val.iloc[0] # None 체크 if val is None or pd.isna(val): return None return float(val) - open_val = get_val('Open') or 0 - high_val = get_val('High') or 0 + open_val = get_val(row, 'Open') or 0 + high_val = get_val(row, 'High') or 0 # ... 나머지 호출도 동일하게 수정
185-190:or 0패턴 사용 시0.0값이 손실될 수 있음
get_val('Open') or 0패턴은get_val이0.0을 반환해도0으로 대체합니다. 현재 OHLCV 데이터에서0.0은 실제로 의미 없는 값이므로 큰 문제는 아니지만,None체크를 명시적으로 하는 것이 의도를 더 명확히 합니다.♻️ 명시적 None 체크 예시
open_val = get_val(row, 'Open') open_val = open_val if open_val is not None else 0AI/modules/features/technical.py (3)
41-47:compute_rsi함수가 legacy 모듈과 중복됨
AI/modules/features/legacy/technical_features.py에 동일한 구현의compute_rsi함수가 존재합니다. 코드 중복을 방지하기 위해 하나의 canonical 구현을 유지하고 다른 곳에서 import하는 것을 권장합니다.
49-53:compute_bollinger_bands반환 타입 힌트 누락함수 시그니처에 반환 타입 힌트가 없습니다. 튜플 반환임을 명시하면 사용 시 혼동을 줄일 수 있습니다.
♻️ 타입 힌트 추가
-def compute_bollinger_bands(series: pd.Series, window: int = 20): +def compute_bollinger_bands(series: pd.Series, window: int = 20) -> tuple[pd.Series, pd.Series]: """볼린저 밴드 상단, 하단 계산"""
55-61:compute_macd반환 타입 힌트 추가 권장
compute_bollinger_bands와 마찬가지로 반환 타입을 명시하면 코드 가독성이 향상됩니다.♻️ 타입 힌트 추가
-def compute_macd(series: pd.Series): +def compute_macd(series: pd.Series) -> tuple[pd.Series, pd.Series]: """MACD 및 시그널 라인 계산"""AI/modules/features/market_derived.py (3)
62-64: 한 줄에 여러 문장 배치 (E701)
if df.empty: return df는 한 줄에 조건문과 return을 함께 작성했습니다. 가독성을 위해 분리하는 것을 권장합니다.♻️ 제안하는 수정
- if df.empty: return df + if df.empty: + return df
62-103:add_multi_timeframe_features함수가 legacy 모듈과 중복됨
AI/modules/features/legacy/technical_features.py의add_multi_timeframe_features와 거의 동일한 로직입니다. 코드 유지보수를 위해 하나의 canonical 구현을 유지하고 다른 곳에서 재사용하는 것을 권장합니다.
100-101:join사용 시 컬럼명 충돌 가능성
df_origin에 이미week_ma20_ratio등의 컬럼이 존재하면join이 예기치 않은 동작을 할 수 있습니다.join은 기본적으로 같은 이름의 컬럼이 있으면 오류를 발생시킵니다. 방어 코드 추가를 고려해 보세요.♻️ 기존 컬럼 제거 후 join
# 기존 컬럼이 있으면 제거 for col in weekly_cols + monthly_cols: if col in df_origin.columns: df_origin = df_origin.drop(columns=[col]) df_origin = df_origin.join(df_weekly[weekly_cols].reindex(df_origin.index, method='ffill')) df_origin = df_origin.join(df_monthly[monthly_cols].reindex(df_origin.index, method='ffill'))AI/modules/features/processor.py (2)
4-5: 사용되지 않는 import 제거 권장
compute_correlation_spike와compute_recent_loss_ema가 import되었지만 이 파일에서 사용되지 않습니다. 불필요한 import는 제거하는 것이 좋습니다.♻️ 사용하지 않는 import 제거
from .market_derived import add_standard_technical_features, add_multi_timeframe_features from .event_features import add_event_features -from .technical import compute_correlation_spike, compute_recent_loss_ema
37-41: numpy를 메서드 내부에서 import하는 것은 비효율적
finalize_data메서드 내에서numpy를 import하고 있습니다. 모듈이 호출될 때마다 import 문이 실행되어 미세한 오버헤드가 발생합니다. 파일 상단에서 한 번만 import하는 것이 좋습니다.♻️ import를 파일 상단으로 이동
# AI/modules/features/processor.py import pandas as pd +import numpy as np from .market_derived import add_standard_technical_features, add_multi_timeframe_featuresdef finalize_data(self): """무한대 값 제거 및 결측치 0 채움 (수치 안정성 확보)""" - import numpy as np self.df.replace([np.inf, -np.inf], np.nan, inplace=True)AI/modules/data_collector/market_breadth_data.py (1)
162-163: 한 줄에 여러 문장 배치 (E701)
finally블록의 cleanup 코드가 한 줄에 조건문과 함수 호출을 함께 작성했습니다. 가독성을 위해 분리하는 것을 권장합니다.♻️ 제안하는 수정
finally: - if 'cursor' in locals(): cursor.close() - if 'conn' in locals(): conn.close() + if 'cursor' in locals(): + cursor.close() + if 'conn' in locals(): + conn.close()AI/modules/signal/workflows/optimize_thresholds.py (1)
106-109: Bareexcept절 사용 지양.특정 예외 타입 없이 모든 예외를 무시하면 디버깅이 어렵고 예상치 못한 오류를 숨길 수 있습니다.
♻️ 예외 처리 개선 제안
try: sub_df = add_multi_timeframe_features(sub_df) - except: + except (KeyError, ValueError) as e: + # 데이터 부족 등으로 멀티타임프레임 피처 생성 실패 continue # 데이터 부족 등으로 실패 시 패스
| except Exception as e: | ||
| conn.rollback() | ||
| print(f" [Error] 섹터 데이터 처리 중 오류: {e}") | ||
| print(f" [Error] 섹터/지수 데이터 처리 중 오류: {e}") | ||
| finally: | ||
| if 'cursor' in locals(): cursor.close() | ||
| if 'conn' in locals(): conn.close() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
예외 처리 시 conn이 정의되지 않았을 수 있음 - NameError 발생 가능
yf.download (line 72) 이후에 예외가 발생하면 conn이 아직 정의되지 않은 상태에서 conn.rollback()이 호출될 수 있습니다. finally 블록에서는 'conn' in locals() 체크를 하지만, except 블록에서는 체크 없이 conn.rollback()을 호출합니다.
🐛 예외 처리 수정
except Exception as e:
- conn.rollback()
+ if 'conn' in locals():
+ conn.rollback()
print(f" [Error] 섹터/지수 데이터 처리 중 오류: {e}")
finally:
if 'cursor' in locals(): cursor.close()
if 'conn' in locals(): conn.close()🧰 Tools
🪛 Ruff (0.14.13)
158-158: Do not catch blind exception: Exception
(BLE001)
162-162: Multiple statements on one line (colon)
(E701)
163-163: Multiple statements on one line (colon)
(E701)
🤖 Prompt for AI Agents
In `@AI/modules/data_collector/market_breadth_data.py` around lines 158 - 163, The
except block currently calls conn.rollback() without guaranteeing conn exists
which can raise NameError; update the error handling in the try/except around
the yf.download flow so you either initialize conn = None before the try or
guard the rollback with a check (e.g., if 'conn' in locals() or if conn is not
None) and only call conn.rollback() when a connection object is present; also
keep the existing finally guards for cursor/conn closure (cursor and conn) as-is
to ensure safe cleanup.
| # (7) 시장 폭 및 섹터 데이터 (Market Breadth - Sector Returns) | ||
| if not args.skip_breadth: | ||
| try: | ||
| print("\n>>> [Step 8] 시장 폭 및 섹터 데이터(Sector Returns) 업데이트") | ||
| print("\n>>> [Step 9] 시장 폭 및 섹터 데이터(Sector Returns) 업데이트") | ||
| collector = MarketBreadthCollector(db_name=args.db) | ||
| collector.run(repair_mode=args.repair) | ||
| except Exception as e: | ||
| print(f"[Error] Sector Data 수집 중단: {e}") | ||
|
|
||
| # (9) 시장 통계 계산 (Market Breadth Stats - Internal Aggregation) | ||
| # (8) 시장 통계 계산 (Market Breadth Stats - Internal Aggregation) | ||
| if not args.skip_stats: | ||
| try: | ||
| print("\n>>> [Step 9] 시장 통계(NH-NL, MA200%) 계산 및 저장") | ||
| print("\n>>> [Step 8] 시장 통계(NH-NL, MA200%) 계산 및 저장") | ||
| collector = MarketBreadthStatsCollector(db_name=args.db) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
스텝 번호 로그가 실제 순서와 불일치합니다.
시장 폭 단계가 “Step 9”로 출력되어 순서가 혼동됩니다.
🐛 제안 수정안
- print("\n>>> [Step 9] 시장 폭 및 섹터 데이터(Sector Returns) 업데이트")
+ print("\n>>> [Step 7] 시장 폭 및 섹터 데이터(Sector Returns) 업데이트")🧰 Tools
🪛 Ruff (0.14.13)
206-206: Do not catch blind exception: Exception
(BLE001)
🤖 Prompt for AI Agents
In `@AI/modules/data_collector/run.py` around lines 200 - 213, The printed step
numbers for the market breadth blocks are swapped: update the log strings so
MarketBreadthCollector prints "Step 8" and MarketBreadthStatsCollector prints
"Step 9" (or otherwise ensure the printed step numbers follow execution order);
locate the print calls in run.py near the MarketBreadthCollector and
MarketBreadthStatsCollector blocks and correct the step number text in those
print statements.
| def add_event_window_flags(df: pd.DataFrame, event_dates_list: list, col_name: str) -> pd.DataFrame: | ||
| """FOMC/CPI 전후 1일 여부 (True/False) [명세서 준수]""" | ||
| # 이벤트 당일 플래그 | ||
| df[f'event_window_flag_{col_name}'] = df.index.isin(event_dates_list) | ||
| # 전후 1일로 확장 (rolling max) | ||
| df[f'event_window_flag_{col_name}'] = df[f'event_window_flag_{col_name}'].rolling(window=3, center=True).max().fillna(0).astype(bool) | ||
| return df No newline at end of file |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
# First, check if the file exists and view its content
find . -name "event_features.py" -type fRepository: SISC-IT/sisc-web
Length of output: 99
🏁 Script executed:
# Check the exact content of the file at the specified lines
fd "event_features.py" --exec cat -n {} \;Repository: SISC-IT/sisc-web
Length of output: 1076
🌐 Web query:
pandas rolling window min_periods default behavior documentation
💡 Result:
In pandas, min_periods defaults to None, and pandas interprets that as:
- If the rolling
windowis an offset / time-based window (e.g.,"7D"), thenmin_periodsdefaults to 1. [1] - If the rolling
windowis an integer window (e.g.,window=7), thenmin_periodsdefaults to the window size (e.g., 7). [1]
This is documented in Series.rolling (and similarly in DataFrame.rolling). [1], [2]
경계 구간에서 이벤트 윈도우 플래그가 누락될 수 있습니다.
rolling(window=3, center=True)에서 min_periods를 명시하지 않으면 pandas는 기본값으로 min_periods=window (즉, min_periods=3)를 적용합니다. 이로 인해 시작/끝 행의 이벤트는 window 크기 미만의 값만 존재하여 NaN이 되고, fillna(0)으로 False로 변환되어 이벤트 플래그가 손실됩니다.
🐛 제안 수정안
- df[f'event_window_flag_{col_name}'] = df[f'event_window_flag_{col_name}'].rolling(window=3, center=True).max().fillna(0).astype(bool)
+ df[f'event_window_flag_{col_name}'] = (
+ df[f'event_window_flag_{col_name}']
+ .rolling(window=3, center=True, min_periods=1)
+ .max()
+ .fillna(0)
+ .astype(bool)
+ )🤖 Prompt for AI Agents
In `@AI/modules/features/event_features.py` around lines 12 - 18, The event window
flags in add_event_window_flags lose true values at the DataFrame edges because
rolling(window=3, center=True) uses the default min_periods=3; change the
rolling call in add_event_window_flags to specify min_periods=1 so the 3-row
centered expansion treats partial windows at start/end as valid, e.g. update the
expression that computes df[f'event_window_flag_{col_name}'] to use
rolling(window=3, center=True, min_periods=1) before
.max().fillna(0).astype(bool) to preserve edge events.
| # AI/modules/features/market_derived.py | ||
| import pandas as pd | ||
| import numpy as np | ||
| from features.technical import compute_rsi, compute_atr, compute_macd, compute_bollinger_bands |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
# First, find the project structure
find . -type d -name features -o -name AI | head -20Repository: SISC-IT/sisc-web
Length of output: 86
🏁 Script executed:
# Check if the files mentioned exist
find . -name "market_derived.py" -o -name "technical.py" | head -20Repository: SISC-IT/sisc-web
Length of output: 134
🏁 Script executed:
# List the AI/modules/features directory structure
ls -la AI/modules/features/ 2>/dev/null || echo "Directory not found, searching..."
find . -path "*/AI/modules/features*" -type f -name "*.py" | head -20Repository: SISC-IT/sisc-web
Length of output: 686
🏁 Script executed:
# Check the contents of technical.py to see what functions are defined
cat -n AI/modules/features/technical.pyRepository: SISC-IT/sisc-web
Length of output: 3097
🏁 Script executed:
# Check the import statement and context in market_derived.py
head -20 AI/modules/features/market_derived.pyRepository: SISC-IT/sisc-web
Length of output: 908
🏁 Script executed:
# Check the directory structure - is there an __init__.py in features?
ls -la AI/modules/features/__init__.py 2>/dev/null || echo "No __init__.py found"
ls -la AI/modules/__init__.py 2>/dev/null || echo "No __init__.py in modules"Repository: SISC-IT/sisc-web
Length of output: 106
import 경로 오류 및 compute_atr 함수 부재 - ModuleNotFoundError 및 ImportError 발생
from features.technical import ... 경로가 잘못되었으며, 요청된 함수 중 compute_atr이 technical.py에 정의되어 있지 않습니다. 프로젝트 구조상 상대 import를 사용해야 하며, technical.py에는 compute_atr_rank만 존재합니다.
수정 사항
-from features.technical import compute_rsi, compute_atr, compute_macd, compute_bollinger_bands
+from .technical import compute_rsi, compute_macd, compute_bollinger_bandscompute_atr 함수가 필요한 경우, compute_atr_rank를 사용하거나 compute_atr 함수를 technical.py에 새로 구현해야 합니다.
🤖 Prompt for AI Agents
In `@AI/modules/features/market_derived.py` at line 4, The import line uses an
incorrect absolute path and references a missing symbol: change the import in
market_derived.py to a relative import (e.g., from .technical import ...) and
resolve the absent compute_atr by either replacing it with the existing
compute_atr_rank (update any call sites in market_derived.py to use
compute_atr_rank) or implement a new compute_atr in technical.py and export it;
ensure you keep compute_rsi, compute_macd, and compute_bollinger_bands names
intact when updating the import.
| self.df['date'] = pd.to_datetime(self.df['date']) | ||
| self.df = self.df.sort_values('date') | ||
|
|
||
| def execute_pipeline(self, event_info=None, sector_df=None): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sector_df 파라미터가 사용되지 않음
execute_pipeline 메서드에 sector_df 파라미터가 정의되어 있지만 메서드 내에서 사용되지 않습니다. 향후 구현 예정이라면 # TODO 주석을 추가하고, 그렇지 않다면 제거하세요.
💡 TODO 주석 추가 또는 파라미터 제거
- def execute_pipeline(self, event_info=None, sector_df=None):
+ def execute_pipeline(self, event_info=None):
"""전체 파생 피처 생성 파이프라인 실행"""또는 향후 사용 예정인 경우:
def execute_pipeline(self, event_info=None, sector_df=None):
"""전체 파생 피처 생성 파이프라인 실행"""
# TODO: sector_df를 사용한 상대강도 계산 추가 예정📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| def execute_pipeline(self, event_info=None, sector_df=None): | |
| def execute_pipeline(self, event_info=None): |
🧰 Tools
🪛 Ruff (0.14.13)
19-19: Unused method argument: sector_df
(ARG002)
🤖 Prompt for AI Agents
In `@AI/modules/features/processor.py` at line 19, The execute_pipeline method
defines a sector_df parameter that is not used; either remove sector_df from the
signature of execute_pipeline or, if you plan to use it later, keep the
parameter and add a clear TODO comment inside the execute_pipeline function
(e.g., "# TODO: use sector_df for relative strength calculations") so future
readers know it is intentionally reserved; update the method signature or add
the TODO in the execute_pipeline definition to resolve the unused-parameter
issue.
| # AI/modules/features/technical.py | ||
| import pandas as pd | ||
| import numpy as np | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check if compute_atr function is defined in technical.py
echo "=== Searching for compute_atr definition ==="
rg -n "def compute_atr\(" --type=py
echo ""
echo "=== Checking market_derived.py imports ==="
# Find and display the imports section of market_derived.py
fd -e py market_derived | head -1 | xargs -I {} sh -c 'echo "File: {}"; head -50 {}'Repository: SISC-IT/sisc-web
Length of output: 2165
🏁 Script executed:
#!/bin/bash
# List all function definitions in technical.py
echo "=== Functions defined in technical.py ==="
rg -n "^def " AI/modules/features/technical.py --type=py
echo ""
echo "=== Functions imported in market_derived.py ==="
rg "from features.technical import" AI/modules/features/market_derived.py --type=pyRepository: SISC-IT/sisc-web
Length of output: 870
compute_atr 함수 정의 누락 - 런타임 ImportError 발생
AI/modules/features/market_derived.py에서 compute_atr을 import하지만, AI/modules/features/technical.py에는 이 함수가 정의되어 있지 않습니다. 현재 정의된 함수는 compute_z_score, compute_atr_rank, compute_ma_trend_score, compute_correlation_spike, compute_recent_loss_ema, compute_rsi, compute_bollinger_bands, compute_macd이며, compute_atr은 존재하지 않습니다.
결과적으로 market_derived.py 모듈을 로드할 때 ImportError가 발생합니다.
🐛 compute_atr 함수 추가 제안
+def compute_atr(high: pd.Series, low: pd.Series, close: pd.Series, window: int = 14) -> pd.Series:
+ """ATR (Average True Range) 계산"""
+ tr = pd.concat([high - low,
+ abs(high - close.shift(1)),
+ abs(low - close.shift(1))], axis=1).max(axis=1)
+ return tr.rolling(window=window).mean()
+
def compute_atr_rank(high: pd.Series, low: pd.Series, close: pd.Series, window: int = 14) -> pd.Series:🤖 Prompt for AI Agents
In `@AI/modules/features/technical.py` around lines 1 - 4,
AI/modules/features/technical.py is missing the compute_atr function which
causes ImportError when AI/modules/features/market_derived.py imports it; add a
compute_atr implementation to technical.py (consistent with the style of
existing helpers like compute_z_score, compute_atr_rank, compute_rsi,
compute_bollinger_bands, compute_macd) that computes the Average True Range from
high/low/close series, returns the expected type/shape used by
market_derived.py, and ensure the function name export/signature matches what
market_derived.py imports (compute_atr) so the module can be imported without
error.
| from AI.libs.database.connection import get_db_conn | ||
| from AI.modules.signal.core.data_loader import DataLoader | ||
| from AI.modules.signal.core.features import add_technical_indicators | ||
| from AI.modules.features.legacy.technical_features import add_technical_indicators |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
feature_cols와 legacy 지표 출력 불일치 가능성
legacy add_technical_indicators는 ma5/ma20/ma60, macd, signal_line, upper_band, lower_band를 생성하지 않으므로 Line 88의 feature_cols 기준 전처리에서 KeyError 또는 스케일러 불일치가 발생할 수 있습니다. 모듈 전환에 맞춰 feature/스케일러를 동기화하거나 기존 모듈을 유지하세요.
🤖 Prompt for AI Agents
In `@AI/modules/signal/workflows/inference.py` at line 28, 현재 사용 중인
add_technical_indicators 함수가 ma5/ma20/ma60, macd, signal_line, upper_band,
lower_band 등 일부 피처를 생성하지 않아 feature_cols 기반 전처리(변수 feature_cols)와 scaler 적용 시
KeyError 또는 스케일러 불일치가 발생할 수 있습니다; 이를 고치려면 add_technical_indicators를 호출하는 부분(함수
add_technical_indicators)을 수정해 누락된 지표들을 동일한 네이밍으로 생성하거나, 아니면 feature_cols 리스트를
현재 legacy 함수가 생성하는 컬럼 목록으로 동기화하고 scaler를 그에 맞춰 재학습/로드하도록 변경하며, 호출부에서 누락 컬럼에 대해
안전하게 처리(예: 컬럼 존재 여부 검사 및 적절한 기본값/보간 적용)하도록 구현하세요.
| base_dir = os.path.join(project_root, "AI", "data", "weights", "prod" , model_type) | ||
| model_path = os.path.join(base_dir, "universal_transformer_prod.keras") | ||
| scaler_path = os.path.join(base_dir, "universal_scaler__prod.pkl") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# prod 아티팩트 존재 여부 확인
echo "=== Checking for universal_transformer artifacts ==="
fd -a "universal_transformer.*\.keras$" AI/data/weights || echo "No matches found"
echo ""
echo "=== Checking for universal_scaler artifacts ==="
fd -a "universal_scaler.*\.pkl$" AI/data/weights || echo "No matches found"
echo ""
echo "=== Listing all files in AI/data/weights/prod directory ==="
ls -la AI/data/weights/prod 2>/dev/null || echo "Directory not found or empty"Repository: SISC-IT/sisc-web
Length of output: 251
🏁 Script executed:
#!/bin/bash
# Check if AI/data/weights directory exists and list contents
echo "=== Checking AI/data/weights directory structure ==="
if [ -d "AI/data/weights" ]; then
ls -la AI/data/weights
echo ""
echo "=== Contents of prod directory if it exists ==="
[ -d "AI/data/weights/prod" ] && ls -la AI/data/weights/prod || echo "prod directory doesn't exist"
else
echo "AI/data directory structure:"
ls -la AI/data 2>/dev/null || echo "AI/data not found"
fi
echo ""
echo "=== Checking .gitignore for weight/artifact patterns ==="
grep -r "universal_transformer\|universal_scaler\|\.keras\|\.pkl\|weights/prod" .gitignore .gitignore_* 2>/dev/null || echo "No patterns found in .gitignore"
echo ""
echo "=== Checking if any .keras or .pkl files exist in repository ==="
fd "\.(keras|pkl)$" . 2>/dev/null | head -20Repository: SISC-IT/sisc-web
Length of output: 779
🏁 Script executed:
#!/bin/bash
# Read the inference.py file around lines 37-39
echo "=== Code context in inference.py ==="
sed -n '30,50p' AI/modules/signal/workflows/inference.py | cat -n
echo ""
echo "=== Full function signature ==="
sed -n '1,50p' AI/modules/signal/workflows/inference.py | head -50 | cat -nRepository: SISC-IT/sisc-web
Length of output: 3430
경로 및 파일명 구조 수정 필요
경로 구조가 잘못되었습니다. 현재 코드는 AI/data/weights/prod/{model_type}/ 형태지만 실제 디렉토리는 AI/data/weights/{model_type}/prod/입니다. 또한 파일명도 수정이 필요합니다:
- 현재:
universal_transformer_prod.keras→ 실제:multi_horizon_model_prod.keras - 현재:
universal_scaler__prod.pkl(이중 밑줄) → 실제:multi_horizon_scaler_prod.pkl(단일 밑줄)
line 37: os.path.join(project_root, "AI", "data", "weights", model_type, "prod") 로 변경하고, lines 38-39의 파일명을 실제 산출물명으로 수정하세요.
🤖 Prompt for AI Agents
In `@AI/modules/signal/workflows/inference.py` around lines 37 - 39, The current
base_dir construction and filenames are incorrect: change base_dir (variable
name base_dir) to build path with model_type before "prod" (i.e.,
os.path.join(project_root, "AI", "data", "weights", model_type, "prod")) and
update model_path (variable model_path) to point to the actual artifact
"multi_horizon_model_prod.keras" and update scaler_path (variable scaler_path)
to "multi_horizon_scaler_prod.pkl" (single underscore), ensuring you only adjust
these three variables: base_dir, model_path, and scaler_path.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 6
🤖 Fix all issues with AI agents
In `@AI/modules/finder/legacy/evaluator.py`:
- Line 21: add_technical_indicators를 legacy 구현으로 바꾼 뒤 evaluator에서 참조하는 ma20,
ma60, macd, signal_line 컬럼이 없으면 KeyError가 발생합니다; 수정 방법은 두 가지 중 하나로 고르세요: 1) 현재
사용 중인 평가 코드(평가 로직이 ma20/ma60/macd/signal_line을 참조하는 함수/블록)를 유지하려면 이전(비-legacy)
add_technical_indicators 구현을 그대로 임포트/호출하도록 복원하거나, 2) 혹은
AI.modules.finder.legacy.evaluator 내에서 호출되는 함수(예: 평가 함수 이름 또는 블록) 직전에 필요한 컬럼을
생성하도록 보완하세요(명시적 계산 루틴 또는 안전한 파생 칼럼 생성 로직을 추가해 ma20, ma60, macd, signal_line이 항상
존재하도록 함). 확실히 하기 위해 수정 대상 함수/심볼은 add_technical_indicators와 evaluator에서
ma20/ma60/macd/signal_line을 사용하는 해당 평가 블록을 찾아 적용하세요.
In `@AI/modules/finder/TFT_Risk_Manager.py`:
- Around line 16-37: The predict_crash_probability method currently slices
feature_df with primary_keys + secondary_keys and will raise a KeyError if
required columns are missing; add defensive validation in
predict_crash_probability that computes missing = set(primary_keys +
secondary_keys) - set(feature_df.columns) and if missing is non-empty raise a
clear, explicit exception (ValueError or custom) that lists which columns are
missing and mentions the function name; perform this check before the line that
creates input_data and keep using self._calculate_tft_inference(input_data)
afterwards.
In `@AI/modules/signal/models/PatchTST/wrapper.py`:
- Around line 12-13: The constructor sets config=None by default but later calls
self.config.get('seq_len', 120), which can raise AttributeError; in __init__
(method __init__) ensure self.config is a dict when config is omitted (e.g., if
config is None set to {} or copy provided config) so that self.config.get(...)
is safe, or alternatively add defensive access in preprocess where
self.config.get('seq_len', 120) is used to handle a None config before calling
get.
- Around line 39-40: The code creates CPU tensors with torch.FloatTensor(x)
which will fail if self.model is on GPU; update the tensor creation to place
tensors on the model/device dynamically: determine device via the model (e.g.,
device = next(self.model.parameters()).device or fallback to getattr(self,
"device", torch.device("cpu"))) and create the tensor with torch.as_tensor or
torch.tensor specifying dtype=torch.float32 and device=device; change both
occurrences that use torch.FloatTensor(x) (the block using seq_len,
self.feature_cols and the similar block at lines 50-53) so inputs are moved to
the correct device before passing to the model.
- Around line 50-55: The wrapper imports and initialization are wrong: replace
the missing BaseModel import with the correct BaseSignalModel from
AI/modules/signal/core/base_model.py and update the constructor call so you call
super().__init__(config) (remove model_path); then ensure self.model is
created/loaded—either instantiate the PatchTST model inside __init__ or
implement a load(self, model_path) that loads weights and assigns to self.model
before any calls to self.model.eval() or self.model(x); update any places that
call super().__init__(model_path, config) to use only config and make sure
methods reference the initialized self.model.
In `@AI/modules/signal/models/TCN/wrapper.py`:
- Around line 11-12: The constructor (__init__) passes config through but does
not guard against config being None, causing self.config.get('seq_len', 30)
(used later) to raise AttributeError; update the __init__ in this class so that
if config is None you replace it with an empty dict (e.g., config = {} or
self.config = {} before calling or after super().__init__), ensuring subsequent
calls to self.config.get(...) succeed; reference the constructor (__init__) and
the later call self.config.get('seq_len', 30') to locate the fix.
🧹 Nitpick comments (5)
AI/modules/data_collector/run.py (1)
149-216: 광범위한 Exception 캐치 패턴에 대한 고려 사항정적 분석 도구(Ruff BLE001)에서 여러 곳의
except Exception사용을 경고하고 있습니다. 파이프라인 연속성을 위한 의도적인 설계임을 이해하지만, 더 구체적인 예외 타입을 사용하면 디버깅이 용이해질 수 있습니다.예를 들어, DB 연결 오류(
psycopg2.Error), 네트워크 오류(requests.RequestException), 데이터 처리 오류(ValueError,KeyError) 등을 구분하면 로그 분석 시 원인 파악이 빨라집니다. 현재 구조를 유지하면서도 로깅 레벨을 조정하는 방식도 고려할 수 있습니다.♻️ 선택적 개선 예시
- except Exception as e: - print(f"[Error] Macro Data 수집 중단: {e}") + except (ConnectionError, TimeoutError) as e: + print(f"[Error] Macro Data 네트워크 오류: {e}") + except Exception as e: + print(f"[Error] Macro Data 수집 중단 (예상치 못한 오류): {e}")AI/modules/signal/models/PatchTST/wrapper.py (1)
26-28: 필수 컬럼 존재 여부 검증 누락
df[self.feature_cols]접근 시 DataFrame에 필요한 컬럼이 없으면KeyError가 발생합니다. 입력 데이터 검증을 추가하는 것이 좋습니다.♻️ 컬럼 검증 추가 제안
# 1. 명세서 키 기반 피처 추출 # PatchTST는 Long Lookback이 특징이므로 충분한 시계열 데이터 필요 + missing_cols = set(self.feature_cols) - set(df.columns) + if missing_cols: + raise ValueError(f"Missing required columns: {missing_cols}") data = df[self.feature_cols].valuesAI/modules/signal/models/TCN/wrapper.py (2)
21-38: PatchTSTWrapper와 중복 코드 - 공통 로직 추출 고려
preprocess와predict메서드가 PatchTSTWrapper와 거의 동일합니다.BaseModel이나 별도 믹스인으로 공통 로직을 추출하면 유지보수성이 향상됩니다.♻️ 리팩토링 예시
BaseModel에 공통 메서드 추가:# base_model.py class BaseModel: def _create_tensor(self, df: pd.DataFrame, seq_len: int): """공통 텐서 생성 로직""" missing_cols = set(self.feature_cols) - set(df.columns) if missing_cols: raise ValueError(f"Missing required columns: {missing_cols}") data = df[self.feature_cols].values data = np.nan_to_num(data, nan=0.0) if len(data) < seq_len: return None x = data[-seq_len:].reshape(1, seq_len, len(self.feature_cols)) return torch.FloatTensor(x) def _predict_signal(self, df: pd.DataFrame, seq_len: int): """공통 예측 로직""" x = self._create_tensor(df, seq_len) if x is None: return 0.0 self.model.eval() with torch.no_grad(): output = self.model(x) return torch.sigmoid(output).item()Also applies to: 40-54
25-26: 필수 컬럼 존재 여부 검증 누락PatchTSTWrapper와 동일하게 컬럼 검증이 누락되어 있습니다.
♻️ 컬럼 검증 추가
# 1. 명세서 키 기반 피처 추출 + missing_cols = set(self.feature_cols) - set(df.columns) + if missing_cols: + raise ValueError(f"Missing required columns: {missing_cols}") data = df[self.feature_cols].valuesAI/modules/finder/TFT_Risk_Manager.py (1)
39-48: 입력 DataFrame을 직접 변경하지 않도록 복사 권장Line 43에서
df를 in-place로 수정해 호출자 데이터가 변형됩니다. 의도된 부작용이 아니라면 복사본에서 계산하거나, 최소한 docstring에 명시해 주세요.♻️ 수정 제안
- df['crash_prob'] = self.predict_crash_probability(df) + df = df.copy() + df['crash_prob'] = self.predict_crash_probability(df)
|
|
||
| from AI.libs.database.fetcher import fetch_price_data | ||
| from AI.modules.signal.core.features import add_technical_indicators | ||
| from AI.modules.features.legacy.technical_features import add_technical_indicators |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
legacy add_technical_indicators로 전환 시 필수 컬럼 누락 위험
Line 21의 import 변경 이후 Line 46-60에서 사용하는 ma20, ma60, macd, signal_line 컬럼이 legacy 구현에 없으면 KeyError로 평가가 항상 실패합니다. 이전 구현을 유지하거나, 여기서 필요한 컬럼을 생성하도록 조정해 주세요.
🤖 Prompt for AI Agents
In `@AI/modules/finder/legacy/evaluator.py` at line 21, add_technical_indicators를
legacy 구현으로 바꾼 뒤 evaluator에서 참조하는 ma20, ma60, macd, signal_line 컬럼이 없으면
KeyError가 발생합니다; 수정 방법은 두 가지 중 하나로 고르세요: 1) 현재 사용 중인 평가 코드(평가 로직이
ma20/ma60/macd/signal_line을 참조하는 함수/블록)를 유지하려면 이전(비-legacy)
add_technical_indicators 구현을 그대로 임포트/호출하도록 복원하거나, 2) 혹은
AI.modules.finder.legacy.evaluator 내에서 호출되는 함수(예: 평가 함수 이름 또는 블록) 직전에 필요한 컬럼을
생성하도록 보완하세요(명시적 계산 루틴 또는 안전한 파생 칼럼 생성 로직을 추가해 ma20, ma60, macd, signal_line이 항상
존재하도록 함). 확실히 하기 위해 수정 대상 함수/심볼은 add_technical_indicators와 evaluator에서
ma20/ma60/macd/signal_line을 사용하는 해당 평가 블록을 찾아 적용하세요.
| def predict_crash_probability(self, feature_df: pd.DataFrame): | ||
| """ | ||
| 입력된 파생 피처를 바탕으로 급락 확률 산출 [명세서 3번 준수] | ||
| """ | ||
| # 1. 핵심 입력 데이터 추출 (Primary) | ||
| primary_keys = [ | ||
| 'vix_z_score', 'us10y_chg', 'yield_spread', 'dxy_chg', | ||
| 'wti_price', 'debt_ratio', 'interest_coverage', 'atr_rank', 'ret_1d' | ||
| ] | ||
|
|
||
| # 2. 보조 입력 데이터 추출 (Secondary) | ||
| secondary_keys = [ | ||
| 'credit_spread_hy', 'mkt_breadth_nh_nl', 'surprise_earnings', 'btc_close' | ||
| ] | ||
|
|
||
| input_data = feature_df[primary_keys + secondary_keys] | ||
|
|
||
| # 3. 리스크 스코어링 알고리즘 (TFT 모델 추론부 - 예시 로직) | ||
| # 실제 구현 시 학습된 TFT 가중치를 로드하여 연산합니다. | ||
| risk_score = self._calculate_tft_inference(input_data) | ||
|
|
||
| return risk_score |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
필수 피처 누락 시 즉시 실패합니다.
Line 31에서 컬럼 슬라이싱을 바로 수행해 누락 시 KeyError가 발생합니다. 필수 컬럼 체크 후 명확한 오류를 내도록 방어 로직을 추가해 주세요.
🛠️ 수정 제안
- input_data = feature_df[primary_keys + secondary_keys]
+ required_keys = primary_keys + secondary_keys
+ missing = [k for k in required_keys if k not in feature_df.columns]
+ if missing:
+ raise ValueError(f"필수 피처 누락: {missing}")
+ input_data = feature_df[required_keys]🤖 Prompt for AI Agents
In `@AI/modules/finder/TFT_Risk_Manager.py` around lines 16 - 37, The
predict_crash_probability method currently slices feature_df with primary_keys +
secondary_keys and will raise a KeyError if required columns are missing; add
defensive validation in predict_crash_probability that computes missing =
set(primary_keys + secondary_keys) - set(feature_df.columns) and if missing is
non-empty raise a clear, explicit exception (ValueError or custom) that lists
which columns are missing and mentions the function name; perform this check
before the line that creates input_data and keep using
self._calculate_tft_inference(input_data) afterwards.
| def __init__(self, model_path=None, config=None): | ||
| super().__init__(model_path, config) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
config=None일 때 self.config.get() 호출 시 AttributeError 발생 가능
__init__에서 config=None이 기본값인데, Line 35에서 self.config.get('seq_len', 120)을 호출합니다. config가 None이면 AttributeError가 발생합니다.
🐛 수정 제안
def __init__(self, model_path=None, config=None):
super().__init__(model_path, config)
self.model_name = "PatchTST_Trend_Specialist"
+ self.config = config or {}또는 preprocess에서 방어적으로 처리:
- seq_len = self.config.get('seq_len', 120)
+ seq_len = (self.config or {}).get('seq_len', 120)Also applies to: 35-35
🤖 Prompt for AI Agents
In `@AI/modules/signal/models/PatchTST/wrapper.py` around lines 12 - 13, The
constructor sets config=None by default but later calls
self.config.get('seq_len', 120), which can raise AttributeError; in __init__
(method __init__) ensure self.config is a dict when config is omitted (e.g., if
config is None set to {} or copy provided config) so that self.config.get(...)
is safe, or alternatively add defensive access in preprocess where
self.config.get('seq_len', 120) is used to handle a None config before calling
get.
| x = data[-seq_len:].reshape(1, seq_len, len(self.feature_cols)) | ||
| return torch.FloatTensor(x) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
텐서 디바이스 불일치 가능성
torch.FloatTensor(x)는 CPU 텐서를 생성합니다. 만약 self.model이 GPU에 있다면 RuntimeError가 발생합니다.
🛡️ 디바이스 처리 추가 제안
- x = data[-seq_len:].reshape(1, seq_len, len(self.feature_cols))
- return torch.FloatTensor(x)
+ x = data[-seq_len:].reshape(1, seq_len, len(self.feature_cols))
+ tensor = torch.FloatTensor(x)
+ # 모델과 동일한 디바이스로 이동
+ if hasattr(self, 'model') and self.model is not None:
+ tensor = tensor.to(next(self.model.parameters()).device)
+ return tensorAlso applies to: 50-53
🤖 Prompt for AI Agents
In `@AI/modules/signal/models/PatchTST/wrapper.py` around lines 39 - 40, The code
creates CPU tensors with torch.FloatTensor(x) which will fail if self.model is
on GPU; update the tensor creation to place tensors on the model/device
dynamically: determine device via the model (e.g., device =
next(self.model.parameters()).device or fallback to getattr(self, "device",
torch.device("cpu"))) and create the tensor with torch.as_tensor or torch.tensor
specifying dtype=torch.float32 and device=device; change both occurrences that
use torch.FloatTensor(x) (the block using seq_len, self.feature_cols and the
similar block at lines 50-53) so inputs are moved to the correct device before
passing to the model.
| self.model.eval() | ||
| with torch.no_grad(): | ||
| # 모델 내부적으로 RevIN 정규화가 수행됨 | ||
| output = self.model(x) | ||
| # 예측값은 Trader 표준 스키마(prob_up)로 변환 | ||
| signal_patch = torch.sigmoid(output).item() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
# First, find the wrapper.py file and understand its class structure
find . -name "wrapper.py" -path "*/signal/models/PatchTST/*" -type fRepository: SISC-IT/sisc-web
Length of output: 106
🏁 Script executed:
# Check the wrapper.py file structure
cat -n AI/modules/signal/models/PatchTST/wrapper.py | head -80Repository: SISC-IT/sisc-web
Length of output: 2354
🏁 Script executed:
# Search for BaseModel class definition
find . -name "*.py" -path "*/signal/*" -type f | xargs grep -l "class BaseModel"Repository: SISC-IT/sisc-web
Length of output: 44
🏁 Script executed:
# Search for model initialization patterns in the project
rg -n "self\.model\s*=" --type py -g "*/signal/models/**" -A 2Repository: SISC-IT/sisc-web
Length of output: 208
🏁 Script executed:
# Find base_model.py file
find . -name "base_model.py" -path "*/signal/models/*" -type fRepository: SISC-IT/sisc-web
Length of output: 42
🏁 Script executed:
# Also check in the immediate parent directory
find . -name "base_model.py" -path "*/signal/*" -type fRepository: SISC-IT/sisc-web
Length of output: 98
🏁 Script executed:
# Look for BaseModel class definition
rg -n "class BaseModel" --type py -A 30Repository: SISC-IT/sisc-web
Length of output: 42
🏁 Script executed:
# Read the BaseModel class implementation
cat -n AI/modules/signal/core/base_model.pyRepository: SISC-IT/sisc-web
Length of output: 2012
🏁 Script executed:
# Check if there's also a base_model.py in the models directory
find AI/modules/signal/models -name "base_model.py" -type fRepository: SISC-IT/sisc-web
Length of output: 42
🏁 Script executed:
# Check the import path in wrapper.py more carefully
ls -la AI/modules/signal/models/ | grep -i baseRepository: SISC-IT/sisc-web
Length of output: 42
🏁 Script executed:
# Search for BaseModel class definition
rg -n "class BaseModel" --type pyRepository: SISC-IT/sisc-web
Length of output: 42
🏁 Script executed:
# Also check what's actually being imported in wrapper.py
rg -n "BaseModel" AI/modules/signal/models/PatchTST/wrapper.pyRepository: SISC-IT/sisc-web
Length of output: 134
🏁 Script executed:
# Search for model loading/initialization in wrapper and parent classes
rg -n "def load|self\.model\s*=" --type py -A 2 -B 1 AI/modules/signal/core/Repository: SISC-IT/sisc-web
Length of output: 833
🏁 Script executed:
# Check if there's a base_model.py in the models directory
ls -la AI/modules/signal/models/base_model.py 2>/dev/null || echo "File not found"Repository: SISC-IT/sisc-web
Length of output: 74
🏁 Script executed:
# List the structure of the models directory
ls -la AI/modules/signal/models/Repository: SISC-IT/sisc-web
Length of output: 337
🏁 Script executed:
# Search for any file that defines "BaseModel" (not BaseSignalModel)
rg -n "^class BaseModel" --type pyRepository: SISC-IT/sisc-web
Length of output: 42
🏁 Script executed:
# Check what __init__.py files might do
cat -n AI/modules/signal/models/__init__.py 2>/dev/null || echo "No __init__.py in models"
cat -n AI/modules/signal/core/__init__.py 2>/dev/null || echo "No __init__.py in core"Repository: SISC-IT/sisc-web
Length of output: 1091
🏁 Script executed:
# Check if PatchTSTWrapper has a load method or model loading code
cat -n AI/modules/signal/models/PatchTST/wrapper.py | tail -20Repository: SISC-IT/sisc-web
Length of output: 769
🏁 Script executed:
# Search for any model loading in the PatchTST directory
rg -n "load|torch.load" --type py AI/modules/signal/models/PatchTST/Repository: SISC-IT/sisc-web
Length of output: 481
🏁 Script executed:
# Check if there's supposed to be a build or setup method
rg -n "def build|PatchTST_Model\(" --type py AI/modules/signal/models/PatchTST/Repository: SISC-IT/sisc-web
Length of output: 257
🏁 Script executed:
# Get the complete wrapper.py file to see full class implementation
wc -l AI/modules/signal/models/PatchTST/wrapper.pyRepository: SISC-IT/sisc-web
Length of output: 107
🏁 Script executed:
# Check the full wrapper.py content
cat -n AI/modules/signal/models/PatchTST/wrapper.pyRepository: SISC-IT/sisc-web
Length of output: 2354
🏁 Script executed:
# Check architecture.py to see if PatchTST_Model is instantiated somewhere
cat -n AI/modules/signal/models/PatchTST/architecture.py | head -40Repository: SISC-IT/sisc-web
Length of output: 1543
🏁 Script executed:
# Verify the import path - check what happens with ..base_model in wrapper context
python3 << 'EOF'
import os
# wrapper.py is at: AI/modules/signal/models/PatchTST/wrapper.py
# From there, .. goes to: AI/modules/signal/models/
# So ..base_model would look for: AI/modules/signal/models/base_model.py
wrapper_dir = "AI/modules/signal/models/PatchTST"
parent_dir = "AI/modules/signal/models"
expected_path = "AI/modules/signal/models/base_model.py"
actual_path = "AI/modules/signal/core/base_model.py"
print(f"From wrapper location: {wrapper_dir}")
print(f"Import ..base_model looks for: {expected_path}")
print(f"But actual file is at: {actual_path}")
EOFRepository: SISC-IT/sisc-web
Length of output: 246
🏁 Script executed:
# Check if BaseModel alias exists in models/__init__.py
cat -n AI/modules/signal/models/__init__.py | grep -i "basemodel\|alias"Repository: SISC-IT/sisc-web
Length of output: 42
🏁 Script executed:
# Search entire codebase for "class BaseModel" (exact match)
rg "class BaseModel\b" --type pyRepository: SISC-IT/sisc-web
Length of output: 42
클래스 상속 및 모델 초기화 구조 완전 재작업 필요
from ..base_model import BaseModelimport는 실패합니다.BaseModel클래스가 존재하지 않으며, 실제 클래스는AI/modules/signal/core/base_model.py의BaseSignalModel입니다. import 경로 또는 클래스명을 수정하세요.super().__init__(model_path, config)호출이 잘못되었습니다.BaseSignalModel.__init__()은config파라미터만 수용합니다.- Critical:
self.model은 초기화되지 않은 상태로 유지되며, 모델 가중치를 로드하는 메커니즘이 없습니다. 라인 50-55에서self.model.eval()과self.model(x)호출 시AttributeError: 'NoneType' object has no attribute 'eval'이 발생합니다. __init__메서드에서 모델 인스턴스를 생성하거나, 명시적으로load()메서드를 구현하여self.model을 초기화해야 합니다.
🤖 Prompt for AI Agents
In `@AI/modules/signal/models/PatchTST/wrapper.py` around lines 50 - 55, The
wrapper imports and initialization are wrong: replace the missing BaseModel
import with the correct BaseSignalModel from
AI/modules/signal/core/base_model.py and update the constructor call so you call
super().__init__(config) (remove model_path); then ensure self.model is
created/loaded—either instantiate the PatchTST model inside __init__ or
implement a load(self, model_path) that loads weights and assigns to self.model
before any calls to self.model.eval() or self.model(x); update any places that
call super().__init__(model_path, config) to use only config and make sure
methods reference the initialized self.model.
| def __init__(self, model_path=None, config=None): | ||
| super().__init__(model_path, config) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
config=None일 때 self.config.get() 호출 시 AttributeError 발생 가능
PatchTSTWrapper와 동일한 이슈입니다. config가 None일 때 Line 33의 self.config.get('seq_len', 30) 호출이 실패합니다.
🐛 수정 제안
def __init__(self, model_path=None, config=None):
super().__init__(model_path, config)
self.model_name = "TCN_Local_Pattern"
+ self.config = config or {}Also applies to: 33-33
🤖 Prompt for AI Agents
In `@AI/modules/signal/models/TCN/wrapper.py` around lines 11 - 12, The
constructor (__init__) passes config through but does not guard against config
being None, causing self.config.get('seq_len', 30) (used later) to raise
AttributeError; update the __init__ in this class so that if config is None you
replace it with an empty dict (e.g., config = {} or self.config = {} before
calling or after super().__init__), ensuring subsequent calls to
self.config.get(...) succeed; reference the constructor (__init__) and the later
call self.config.get('seq_len', 30') to locate the fix.
계획표에 있는 모든 계산 코드 작성완료. Finder 구현 시작.
Summary by CodeRabbit
릴리스 노트
새로운 기능
버그 수정
리팩토링