Skip to content

Conversation

@twq110
Copy link
Contributor

@twq110 twq110 commented Jan 12, 2026

전반적인 리펙토링 진행 완료
주요 변경 사항 요약

  1. libs (공통 라이브러리) 재구성
    목적: 여기저기 흩어져 있던 유틸리티성 코드들을 중앙에서 관리.

변경:

utils/get_db_conn.py → libs/database/connection.py (DB 연결 표준화)

utils/fetch_ohlcv.py → libs/database/fetcher.py (데이터 조회 전담)

save_..._to_db.py 통합 → libs/database/repository.py (저장 로직 통합)

libs/llm/ 패키지 신설: BaseLLMClient 인터페이스를 만들고 GroqClient, OllamaClient 구현체 추가.

  1. modules/signal (AI 모델링 프레임워크) 구축
    목적: 새로운 모델(LSTM 등) 추가 시 기존 코드를 건드리지 않는 확장성 확보.

변경:

core/base_model.py: 모든 모델이 따라야 할 인터페이스(Interface) 정의.

core/data_loader.py: 데이터 로드, 지표 계산(features.py), 시퀀스 변환 로직 통합.

models/transformer/: Transformer 모델의 구조(architecture.py)와 실행기(wrapper.py) 분리.

workflows/: 학습(train.py), 추론(inference.py) 스크립트 분리. 특히 inference.py는 글로벌 모델 로드 및 로컬 스케일링 로직 적용.

  1. modules/trader (매매 실행) 분리
    목적: 백테스트 로직과 매매 전략 로직 분리.

변경:

engine.py: 데이터 → 추론 → 주문 루프를 담당하는 백테스트 엔진.

policy.py: AI 점수에 따라 매수/매도/손절 여부를 결정하는 순수 로직.

  1. modules/analysis (XAI 분석) 고도화
    목적: 단순 텍스트 생성이 아닌, 금융 지표 해석을 포함한 고품질 리포트 생성.

변경:

report_builder.py: RSI 과매수, 정배열 등 지표 상태를 텍스트로 해석.

generator.py: 해석된 정보를 바탕으로 LLM에게 리포트 작성 요청.

  1. pipelines (통합 실행) 구성
    목적: 위 모든 모듈을 조립하여 매일 자동으로 실행할 스크립트.

변경:

daily_routine.py: 데이터 수집 → 글로벌 모델 추론 → 매매 판단 → XAI 리포트 생성 → DB 저장을 원스톱으로 처리.

  1. tests (검증 및 실험) 정비
    변경:

optimize_hyperparameter.py: Optuna를 이용한 하이퍼파라미터 최적화 (개발용).

evaluate_model.py: 글로벌 모델을 로드하여 전체 시장에 대한 시그널 성과(승률, 수익률)를 종합 평가.

Summary by CodeRabbit

  • 새 기능

    • 모듈화된 거래 플랫폼 추가: 시장·뉴스 수집, 신호 모델(학습·추론), 트레이드 엔진 및 일일 파이프라인, LLM 기반 리포트 생성기 제공
    • LLM 클라이언트(Groq/Ollama) 표준 인터페이스 및 리포트 생성 워크플로 추가
  • 제거

    • 기존의 통합 백테스트·트랜스포머 학습/추론 파이프라인, 기존 뉴스 요약·XAI 리포트 및 일괄 데이터 백필 스크립트 삭제
  • 리팩터

    • 코드베이스를 기능별 모듈로 재구성하여 책임 분리 및 인터페이스 표준화

✏️ Tip: You can customize this high-level summary in your review settings.

@twq110 twq110 requested a review from Kosw6 as a code owner January 12, 2026 09:49
@coderabbitai
Copy link

coderabbitai bot commented Jan 12, 2026

Walkthrough

대규모 리팩토링: 기존의 여러 모듈(백테스터·트랜스포머·XAI·파이프라인 등)을 삭제하고, LLM 추상화·DB 리포지토리·신호·트레이더·파이프라인 중심의 새 모듈군(AI/modules, AI/libs, AI/pipelines 등)을 추가했습니다. (요약 50단어 이내)

Changes

코호트 / 파일(s) 변경 요약
삭제 - Backtrade
AI/backtrade/__init__.py, AI/backtrade/main.py, AI/backtrade/order_policy.py
단순 백테스트 구현 및 주문 정책 모듈 전체 삭제.
삭제 - Daily data collection
AI/daily_data_collection/__init__.py, AI/daily_data_collection/main.py
가격·지표·매크로·펀더멘털 수집 및 백필 파이프라인 전부 삭제.
삭제 - Finder / 뉴스 / 평가
AI/finder/__init__.py, AI/finder/main.py, AI/finder/financial_eval.py, AI/finder/ticker_selector.py, AI/libs/utils/news_processing*.py
뉴스 수집·요약, 안전성 평가, 티커 선별 관련 로직 전부 제거.
삭제 - Transformer 기존 구현
AI/transformer/** (main, modules, training 등)
기존 특성공학·모델·추론·학습 파이프라인 전면 삭제.
삭제 - XAI 기존 구현
AI/xai/**, AI/xai/modules/*
XAI 보고서 생성·프롬프트·Groq 클라이언트 등 관련 코드 삭제.
삭제 - 기존 유틸/파이프라인
AI/libs/core/pipeline.py, AI/libs/llm_clients/ollama_client.py, AI/libs/utils/* (evaluate_transformer, optimize_hyperparameter, save_*.py 등)
주간 오케스트레이션 및 복수 유틸/저장 스크립트 삭제.
이동/경로 변경 - DB/Fetcher
AI/libs/database/connection.py (was get_db_conn), AI/libs/database/fetcher.py
DB 연결·페처 파일명/경로 조정 및 임포트 경로 변경.
추가 - DB 리포지토리
AI/libs/database/repository.py
실행·리포트 저장 및 현재 포지션 조회 기능을 통합한 리포지토리 추가.
추가 - LLM 추상화 및 구현
AI/libs/llm/__init__.py, AI/libs/llm/base_client.py, AI/libs/llm/groq.py, AI/libs/llm/ollama.py
BaseLLMClient 추상화와 Groq/Ollama 클라이언트 구현 및 재내보내기 추가.
추가 - Signal 모듈군
AI/modules/signal/core/*, AI/modules/signal/models/*, AI/modules/signal/workflows/*
BaseSignalModel, SignalDataLoader, 기술지표 함수, Transformer 래퍼, 학습/추론 워크플로우 추가.
추가 - Trader / Policy / Analysis
AI/modules/trader/*, AI/modules/analysis/*
BacktestEngine, 거래정책(decide_order), LLM 기반 리포트 제너레이터·빌더 추가.
추가 - Collector / Finder modules
AI/modules/collector/*, AI/modules/finder/*
yfinance 기반 시세 수집, 뉴스 수집/요약, 티커 평가·선별 기능 추가.
추가 - Pipelines
AI/pipelines/daily_routine.py, AI/pipelines/__init__.py
일일 파이프라인(run_daily_pipeline) 도입: 수집→신호→트레이드→XAI→DB 저장 흐름 구현.
테스트·도구 변경
AI/tests/* (evaluate_model, optimization, quick_db_check, test_transformer_backtrader 등)
평가·최적화·통합 테스트 및 DB 체크 리팩터링, 통합 테스트용 목 데이터 추가.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Pipeline as "Daily Pipeline\nrgba(30,144,255,0.5)"
    participant Collector as "Data Collector\nrgba(34,139,34,0.5)"
    participant Signal as "Signal Generator\nrgba(255,165,0,0.5)"
    participant Trader as "Backtest/Trader\nrgba(178,34,34,0.5)"
    participant Analysis as "Report Generator\nrgba(148,0,211,0.5)"
    participant DB as "Database\nrgba(128,128,128,0.5)"

    User->>Pipeline: run_daily_pipeline(tickers)
    Pipeline->>Collector: update_market_data(tickers)
    Collector->>DB: upsert price data
    DB-->>Collector: ack

    Pipeline->>Signal: load_data(ticker)
    Signal->>DB: fetch OHLCV
    DB-->>Signal: OHLCV
    Signal->>Signal: add_technical_indicators / inference
    Signal-->>Pipeline: score

    Pipeline->>Trader: decide_order(score, price, position)
    Trader-->>Pipeline: action, qty, reason
    Pipeline->>Analysis: generate_report(ticker, data)
    Analysis->>DB: save report (optional)
    Pipeline->>DB: save_executions(reports, fills)
    DB-->>Pipeline: ack
    Pipeline-->>User: summary (trades + reports)
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120+ minutes

Possibly related PRs

Suggested reviewers

  • Kosw6

Poem

"나는 토끼, 키보드 사이를 껑충 뛰며 노래하네 🐇
옛 코드 정리하고 새 뿌리 심었지요,
신호는 솟고 데이터는 단단히 쌓여,
모델은 생각하고 보고서는 노래하네,
도약하자, 새로운 들판으로!"

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 67.74% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive PR 제목 'Refact/ai part refactoring'은 전체 변경사항의 주요 부분을 반영하지만, 구체성이 부족하고 다소 모호함. 제목을 더 구체적으로 수정하여 핵심 변경사항을 명확히 표현하세요. 예: 'Refactor AI pipeline: restructure signal models, trading engine, and LLM clients' 또는 'Refactor AI modules: consolidate database layer and establish modular trading pipeline'
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 17

🤖 Fix all issues with AI agents
In @AI/libs/database/repository.py:
- Around line 93-96: The except block in Repository.catch (the except Exception
as e: block) uses "raise e", which can lose the original traceback; change it to
use a bare "raise" after conn.rollback() and the print call so the original
exception stack is preserved (update the except block handling around
conn.rollback() and the print statement to re-raise with "raise" instead of
"raise e").
- Around line 145-149: The current use of execute_values(insert_query,
data_to_insert, fetch=True) can split inserts into pages and only return the
last page's RETURNING rows, causing generated_ids (constructed from
cursor.fetchall()) to be incomplete when len(data_to_insert) > default
page_size; fix by explicitly accumulating RETURNING rows for all pages: either
call execute_values with an explicitly large page_size (e.g.,
len(data_to_insert)) to force a single page, or perform manual paging by
splitting data_to_insert and calling execute_values per chunk (with fetch=True)
and extend a cumulative generated_ids list from each cursor.fetchall() result;
update the code around execute_values, insert_query, data_to_insert,
generated_ids and cursor.fetchall() to implement one of these approaches so all
created IDs are collected.

In @AI/libs/llm/ollama.py:
- Around line 44-49: The get_health method currently uses a bare except and no
timeout; change the requests.get call to include a timeout (e.g., timeout=5) and
replace the bare except with an explicit except Exception as e so only regular
exceptions are caught; keep the behavior of returning True only when
res.status_code == 200 and returning False on exception, and optionally (if
available) log or include the exception in a debug message using the existing
logger rather than suppressing it silently.

In @AI/modules/analysis/report_builder.py:
- Around line 66-82: The build_context_string function declares a ticker
parameter but never uses it; either remove ticker from the signature or include
it in the generated context string. Locate build_context_string in
report_builder.py (it calls ReportBuilder.analyze_indicators) and either (A)
delete the ticker parameter from the function definition and any call sites that
pass it, or (B) prepend or embed the ticker into the context variable (e.g., add
a "[심볼]" or "티커: {ticker}" line in the f-string) so the parameter is actually
used.

In @AI/modules/collector/news_data.py:
- Around line 42-44: The XML parser call in news_data.py uses BeautifulSoup(...,
features="xml") which requires the lxml dependency; either add "lxml" to
AI/requirements.txt alongside beautifulsoup4, or change the parser to use the
built-in HTML parser by replacing features="xml" with features="html.parser"
where soup is created (the BeautifulSoup call that assigns to the variable soup
and is followed by items = soup.findAll('item')).

In @AI/modules/finder/__init__,py:
- Around line 1-14: The package init file is misnamed "__init__,py" (comma
instead of underscore) so Python won't treat the folder as a package; rename the
file to "__init__.py" so imports like "from AI.modules.finder import
get_target_tickers, evaluate_ticker, load_all_tickers_from_db" work, and verify
any packaging/CI references to "__init__,py" are updated accordingly.

In @AI/modules/signal/core/features.py:
- Around line 31-38: The RSI calculation treats rs.fillna(0) which makes periods
with gain==0 and loss==0 produce rsi=0; instead detect the flat-price edge case
using the computed gain and loss (from delta.where... rolling(...).mean()), set
rs to NaN when both gain and loss are zero (or equivalently mark those df rows),
compute df['rsi'] as 100 - (100 / (1 + rs)), and then assign 50 for the rows
where gain==0 and loss==0 (i.e. flat periods) so flat periods yield neutral
RSI=50 rather than 0.

In @AI/modules/signal/models/transformer/architecture.py:
- Around line 37-46: The function build_transformer_model uses a mutable default
argument mlp_units=[128], which can lead to shared-state bugs across calls;
change the signature to mlp_units=None (or mlp_units=(128,) if you prefer
immutability) and inside build_transformer_model set mlp_units = [128] (or
list(mlp_units)) when mlp_units is None (or convert the tuple to a list),
ensuring each call gets a fresh list instance and avoiding accidental mutation
of a shared default.

In @AI/modules/signal/models/transformer/wrapper.py:
- Around line 66-79: The code may pass "verbose" twice to self.model.fit because
verbose is both explicitly passed and still present in fit_kwargs; remove
"verbose" from fit_kwargs (similar to how "callbacks" is excluded) before
calling self.model.fit so only the explicit verbose argument is supplied —
update the creation of fit_kwargs (and any filtering logic) to exclude the
"verbose" key and keep the rest intact, ensuring self.model.fit receives
callbacks, verbose, and the filtered fit_kwargs without duplicates.

In @AI/modules/signal/workflows/inference.py:
- Around line 94-98: The code calls a non-existent method
model.load_weights(...) which will raise AttributeError; replace that call with
model.load(weights_path) so TransformerSignalModel and BaseSignalModel use their
defined load(...) method (keep the preceding model.build(...) call as-is),
ensuring you update the reference to weights_path accordingly and remove any
remaining load_weights usages.

In @AI/modules/trader/__init__.py:
- Line 1: The file __init__.py contains a UTF-8 BOM character at the very start
which can break imports or shebang handling; open the module's __init__.py,
remove the leading BOM character (the invisible U+FEFF) so the file begins with
the first source character (or a shebang if present), and save the file encoded
as UTF-8 without BOM.

In @AI/modules/trader/engine.py:
- Around line 100-105: The call to model.predict(..., verbose=0) passes an
unsupported verbose argument to TransformerWrapper.predict and raises a
TypeError; remove the verbose=0 arg and call model.predict(input_seq) (keeping
the input_seq expansion logic) or, if you must support both Keras and the
wrapper, guard the call by detecting if the model has a predict signature that
accepts verbose (e.g., inspect.signature or hasattr) and only pass verbose for
Keras-style models; update the code around model.predict, model_weights_path,
input_seq, and score accordingly.

In @AI/modules/trader/policy.py:
- Around line 46-54: Guard against division by zero before computing pnl_rate:
check that avg_price is non-zero (and positive if appropriate) before executing
pnl_rate = (current_price - avg_price) / avg_price in the risk-management block
that uses position_qty, avg_price, current_price and STOP_LOSS_PCT; if avg_price
is 0, skip the stop-loss check or set pnl_rate to a safe sentinel and avoid
returning SELL, ensuring the logic in this section (the 손절/익절 우선 체크) cannot
raise ZeroDivisionError.

In @AI/tests/optimize_hyperparameter.py:
- Around line 22-26: The project root path is computed too far up: update the
project_root calculation in optimize_hyperparameter.py by changing the
os.path.join call that builds project_root from os.path.join(current_dir,
"../../../..") to os.path.join(current_dir, "../..") so it moves two levels up
(matching the file's depth); keep the existing current_dir, project_root
variables and the conditional sys.path.append(project_root) logic unchanged.
🧹 Nitpick comments (41)
AI/modules/trader/policy.py (2)

10-18: 미사용 파라미터 정리 권장

tickertotal_asset 파라미터가 현재 사용되지 않습니다. 향후 확장을 위해 의도적으로 남겨둔 것이라면, 언더스코어 prefix(_ticker, _total_asset)를 사용하거나 docstring에 명시하면 린터 경고를 방지하고 의도를 명확히 할 수 있습니다.


35-40: 정책 상수를 모듈 레벨로 분리 검토

BUY_THRESHOLD, SELL_THRESHOLD 등의 정책 상수가 함수 내부에 정의되어 있습니다. 테스트나 환경별 설정 변경을 위해 모듈 레벨 상수 또는 config 파라미터로 분리하면 유연성이 높아집니다.

♻️ 제안하는 구조
# 모듈 레벨 상수 (또는 별도 config.py에서 import)
BUY_THRESHOLD = 0.65
SELL_THRESHOLD = 0.40
STOP_LOSS_PCT = 0.05
TAKE_PROFIT_PCT = 0.10
MAX_INVEST_RATIO = 0.95

def decide_order(
    ticker: str,
    score: float,
    ...
) -> Tuple[str, int, str]:
    # 함수 내에서 모듈 상수 사용
    ...
AI/tests/evaluate_model.py (2)

50-53: 평가 기간이 미래 날짜를 포함

eval_end_date"2025-10-20"으로 설정되어 있습니다. 현재 날짜(2026년 1월)를 기준으로 하면 과거 데이터지만, 코드가 작성된 시점에서는 미래 날짜일 수 있습니다. 동적으로 날짜를 설정하거나 주석으로 의도를 명시하면 혼란을 방지할 수 있습니다.


201-201: 코드 스타일: 한 줄에 여러 문장

린터 권장 사항에 따라 조건문과 continue를 별도 줄로 분리하면 가독성이 향상됩니다.

♻️ 제안하는 수정안
-            if len(X_test) == 0: continue
+            if len(X_test) == 0:
+                continue
AI/modules/trader/engine.py (1)

46-46: 타입 힌트: 암묵적 Optional 사용

PEP 484에 따르면 None을 기본값으로 사용할 때 타입을 명시적으로 str | None 또는 Optional[str]로 선언해야 합니다.

♻️ 제안하는 수정안
-    def run(self, model_weights_path: str = None):
+    def run(self, model_weights_path: str | None = None):
AI/modules/collector/__init__.py (1)

1-8: LGTM!

패키지 초기화 파일이 올바르게 구성되었습니다. 두 함수가 적절히 re-export 되고 있습니다.

참고로 Ruff에서 __all__ 정렬을 권장하고 있지만, 현재 순서도 기능상 문제는 없습니다.

♻️ (선택) isort 스타일 정렬
 __all__ = [
-    "update_market_data",
     "collect_news",
+    "update_market_data",
 ]
AI/modules/signal/core/features.py (1)

54-56: fillna(method=...) 구문은 pandas 2.1+에서 deprecated

pandas 2.1.0(2023년 8월)부터 fillna() 메서드의 method 파라미터가 deprecated되었습니다. bfill() 메서드를 직접 사용하는 것이 공식적으로 권장됩니다.

♻️ 권장하는 수정
 # 6. 결측치 처리 (지표 계산 초반 구간)
-df = df.fillna(method='bfill')
+df = df.bfill()
 df = df.fillna(0) # 앞부분 bfill로도 안 채워지는 경우 0 처리
AI/libs/llm/base_client.py (1)

8-9: 미사용 import 정리 권장

DictAny가 import되었지만 이 파일에서 사용되지 않습니다.

♻️ 제안된 수정
 from abc import ABC, abstractmethod
-from typing import Dict, Any, Optional
+from typing import Optional
AI/modules/collector/market_data.py (5)

16-22: sys.path 조작 대신 패키지 구조 활용 권장

sys.path 직접 조작은 유지보수성을 저하시킬 수 있습니다. 프로젝트 루트에 pyproject.toml 또는 setup.py를 통해 패키지를 설치 가능한 형태로 구성하면 이러한 조작 없이 import가 가능합니다.


45-49: 날짜 비교를 datetime 객체로 수행 권장

문자열 비교가 ISO 날짜 형식(YYYY-MM-DD)에서는 동작하지만, datetime 객체 비교가 더 명시적이고 안전합니다.

♻️ 제안된 수정
-            today = datetime.now().strftime("%Y-%m-%d")
-            
-            if start_date > today:
+            today = datetime.now().date()
+            start_date_obj = datetime.strptime(start_date, "%Y-%m-%d").date()
+            
+            if start_date_obj > today:
                 print(f"   [{ticker}] 이미 최신 데이터입니다.")
                 continue
+            
+            today_str = today.strftime("%Y-%m-%d")

69-81: 데이터 추출 로직 간소화 가능

isinstance 체크 대신 pandas의 .item() 또는 .squeeze()를 사용하면 코드가 더 간결해집니다.

♻️ 제안된 수정
             data_to_insert = []
             for index, row in df.iterrows():
                 date_val = index.date()
-                open_val = float(row['Open'].iloc[0] if isinstance(row['Open'], pd.Series) else row['Open'])
-                high_val = float(row['High'].iloc[0] if isinstance(row['High'], pd.Series) else row['High'])
-                low_val = float(row['Low'].iloc[0] if isinstance(row['Low'], pd.Series) else row['Low'])
-                close_val = float(row['Close'].iloc[0] if isinstance(row['Close'], pd.Series) else row['Close'])
-                vol_val = int(row['Volume'].iloc[0] if isinstance(row['Volume'], pd.Series) else row['Volume'])
-                adj_close_val = float(row['Adj Close'].iloc[0] if isinstance(row['Adj Close'], pd.Series) else row['Adj Close'])
+                # squeeze()로 다차원 데이터를 스칼라로 변환
+                open_val = float(pd.Series(row['Open']).squeeze())
+                high_val = float(pd.Series(row['High']).squeeze())
+                low_val = float(pd.Series(row['Low']).squeeze())
+                close_val = float(pd.Series(row['Close']).squeeze())
+                vol_val = int(pd.Series(row['Volume']).squeeze())
+                adj_close_val = float(pd.Series(row['Adj Close']).squeeze())

88-90: 예외 처리 범위 축소 및 로깅 개선 권장

Exception을 포괄적으로 catch하면 프로그래밍 오류(TypeError, AttributeError 등)가 숨겨질 수 있습니다. 더 구체적인 예외 타입을 지정하고, print 대신 logging 모듈 사용을 권장합니다.

♻️ 제안된 수정
+import logging
+from psycopg2 import Error as Psycopg2Error
+
+logger = logging.getLogger(__name__)
+
 # ... in the function ...
-    except Exception as e:
+    except (Psycopg2Error, ValueError) as e:
         conn.rollback()
-        print(f"[Collector][Error] 시장 데이터 업데이트 실패: {e}")
+        logger.error(f"[Collector] 시장 데이터 업데이트 실패: {e}", exc_info=True)

55-60: yf.download()에 타임아웃 설정 추가 권장

네트워크 문제 시 yf.download()가 무기한 대기할 수 있습니다. 전체 파이프라인 중단을 방지하기 위해 timeout 파라미터를 설정하세요. 예: yf.download(ticker, start=start_date, progress=False, timeout=30)

AI/libs/llm/ollama.py (1)

35-42: requests 호출에 timeout 설정 필요

네트워크 요청 시 timeout이 없으면 서버 응답 지연/무응답 시 무한 대기가 발생할 수 있습니다. Ollama 서버가 로컬이라도 timeout 설정을 권장합니다.

♻️ 권장 수정
         try:
-            response = requests.post(url, json=payload)
+            response = requests.post(url, json=payload, timeout=30)
             response.raise_for_status()
             result = response.json()
             return result.get("response", "")
         except Exception as e:
             print(f"[OllamaClient][Error] 텍스트 생성 실패: {e}")
             return ""
AI/modules/analysis/generator.py (1)

22-78: 구현이 잘 되어 있습니다.

LLM 클라이언트 선택, health check, 프롬프트 구성, 에러 처리가 깔끔하게 분리되어 있습니다.

다만 향후 확장성을 위해 temperature, max_tokens 등을 생성자 파라미터나 config로 받는 것을 고려해 보세요.

AI/modules/signal/workflows/inference.py (1)

41-41: 불필요한 f-string prefix 제거

Line 41의 f-string에 플레이스홀더가 없습니다.

♻️ 수정
-        print(f"[Err] 모델 파일이 존재하지 않습니다.")
+        print("[Err] 모델 파일이 존재하지 않습니다.")
AI/modules/finder/selector.py (2)

51-64: 기본 티커 리스트 상수화 권장

["AAPL", "TSLA", "MSFT", "GOOGL", "NVDA"]가 4번 반복됩니다. 상수로 추출하여 유지보수성을 높이세요.

♻️ 권장 수정

파일 상단에 상수 정의:

DEFAULT_TICKERS = ["AAPL", "TSLA", "MSFT", "GOOGL", "NVDA"]

그 후 모든 하드코딩된 리스트를 DEFAULT_TICKERS로 대체.


62-64: finally 블록의 한 줄 if문 스타일

한 줄에 여러 statement를 작성하는 것은 가독성을 떨어뜨립니다. 다만 이 경우 간단한 리소스 정리이므로 context manager(with 문) 사용을 권장합니다.

♻️ Context manager 사용 권장

psycopg2with 문을 지원하므로 다음과 같이 리팩터링할 수 있습니다:

with get_db_conn() as conn:
    with conn.cursor() as cursor:
        cursor.execute(query, (min_price, limit))
        rows = cursor.fetchall()
        # ...

이렇게 하면 finally 블록이 필요 없어집니다.

AI/modules/signal/core/data_loader.py (1)

73-75: Scaler 관련 주석이 적절합니다. 향후 개선 방안을 제안드립니다.

주석에서 언급된 것처럼 fit_transform은 학습 시에만 사용해야 하며, 테스트/추론 시에는 학습된 scaler로 transform만 호출해야 합니다. 현재 구조에서는 scaler가 인스턴스 변수로 저장되어 있어 외부에서 접근 가능하지만, 명시적인 transform_only 플래그나 별도 메서드를 추가하면 더 안전합니다.

AI/modules/analysis/report_builder.py (1)

44-50: MACD 중립 상태 처리를 고려해 보세요.

현재 MACD와 Signal이 동일한 경우 "매도 신호"로 분류됩니다. 중립 상태를 추가하면 더 정확한 해석이 가능합니다.

♻️ 중립 상태 추가 예시
         if macd > signal:
             analysis["macd_status"] = "매수 신호 (MACD > Signal)"
+        elif macd == signal:
+            analysis["macd_status"] = "중립 (MACD = Signal)"
         else:
             analysis["macd_status"] = "매도 신호 (MACD < Signal)"
AI/libs/database/repository.py (1)

154-158: 두 함수 간 에러 처리 방식이 일관되지 않습니다.

save_executions_to_db는 예외를 re-raise하고, save_reports_to_db는 빈 리스트를 반환합니다. 호출자가 에러 발생 여부를 구분하기 어렵습니다. 일관된 에러 처리 전략을 권장합니다.

♻️ 옵션 1: 둘 다 예외 발생
     except Exception as e:
         conn.rollback()
         print(f"[Repository][Error] XAI 리포트 저장 중 오류 발생: {e}")
-        # 오류 시 빈 리스트 반환 (호출 측에서 처리)
-        return []
+        raise
♻️ 옵션 2: 커스텀 Result 타입 사용
from dataclasses import dataclass
from typing import Generic, TypeVar

T = TypeVar('T')

@dataclass
class Result(Generic[T]):
    success: bool
    data: T | None = None
    error: str | None = None
AI/libs/llm/groq.py (1)

45-47: get_health 메서드가 항상 True를 반환합니다.

현재 구현은 실제 헬스 체크를 수행하지 않아 서비스 상태 모니터링에 도움이 되지 않습니다. Groq SDK에서 모델 리스트 조회 등 간단한 API 호출로 연결 상태를 확인하는 것을 권장합니다.

♻️ 실제 헬스 체크 구현 예시
     def get_health(self) -> bool:
-        # Groq SDK에는 명시적인 헬스 체크가 없으므로 간단한 모델 리스트 조회 등으로 대체 가능
-        return True
+        try:
+            # 간단한 API 호출로 연결 상태 확인
+            self.client.models.list()
+            return True
+        except Exception:
+            return False
AI/tests/quick_db_check.py (1)

31-48: cursor도 finally 블록에서 닫아주세요.

connection은 닫히지만 cursor가 명시적으로 닫히지 않습니다. 리소스 정리를 위해 cursor도 함께 닫는 것이 좋습니다.

♻️ cursor 정리 추가
     conn = None
+    cursor = None
     try:
         # DB 연결 시도
         conn = get_db_conn()
         cursor = conn.cursor()
         
         # 간단한 쿼리 실행
         cursor.execute("SELECT 1")
         result = cursor.fetchone()
         
         if result and result[0] == 1:
             print("[Success] DB 연결 성공! (Query Result: 1)")
         else:
             print("[Fail] 쿼리 실행 결과가 예상과 다릅니다.")
             
     except Exception as e:
         print(f"[Error] DB 연결 중 오류 발생: {e}")
         
     finally:
+        if cursor:
+            cursor.close()
         if conn:
             conn.close()
             print("=== DB 연결 종료 ===")
AI/modules/signal/models/transformer/wrapper.py (1)

99-100: filepath에 디렉토리가 없는 경우 처리가 필요합니다.

os.path.dirname("model.h5")는 빈 문자열을 반환하며, os.makedirs("", exist_ok=True)는 예외를 발생시킬 수 있습니다.

♻️ 안전한 디렉토리 생성
-        os.makedirs(os.path.dirname(filepath), exist_ok=True)
+        parent_dir = os.path.dirname(filepath)
+        if parent_dir:
+            os.makedirs(parent_dir, exist_ok=True)
         self.model.save(filepath)
AI/tests/test_transformer_backtrader.py (3)

34-41: 테스트 재현성을 위해 random seed를 고정하세요.

np.random.randnp.random.randint가 seed 없이 사용되어 테스트 결과가 실행마다 달라질 수 있습니다. 디버깅과 재현성을 위해 seed를 고정하는 것이 좋습니다.

♻️ seed 고정 추가
 def create_mock_data(ticker, start_date, end_date):
     """테스트용 가상 OHLCV 데이터 생성 (인덱스 = Date)"""
+    np.random.seed(42)  # 재현성을 위한 seed 고정
     dates = pd.date_range(start=start_date, end=end_date)
     n = len(dates)

35-39: Mock OHLCV 데이터가 현실적인 제약조건을 만족하지 않을 수 있습니다.

현재 구현에서 high가 항상 low보다 크다는 보장이 없고, open/closehigh/low 범위 내에 있다는 보장도 없습니다. 이로 인해 일부 기술적 지표 계산에서 예상치 못한 결과가 나올 수 있습니다.

♻️ 현실적인 OHLCV 생성 예시
base = np.random.rand(n) * 100 + 100
volatility = np.random.rand(n) * 5
df = pd.DataFrame({
    'open': base + np.random.randn(n) * 2,
    'high': base + volatility,
    'low': base - volatility,
    'close': base + np.random.randn(n) * 2,
    'volume': np.random.randint(1000, 10000, n),
    'ticker': ticker
}, index=dates)
# Ensure OHLCV constraints
df['high'] = df[['open', 'high', 'close']].max(axis=1)
df['low'] = df[['open', 'low', 'close']].min(axis=1)

49-141: 테스트 프레임워크(pytest/unittest) 사용을 권장합니다.

현재 스크립트 스타일 테스트는 동작하지만, CI/CD 통합과 테스트 리포팅을 위해 pytest나 unittest를 사용하면 더 효과적입니다. assert 문과 적절한 fixture를 활용하면 실패 원인 파악도 용이해집니다.

AI/pipelines/daily_routine.py (3)

87-96: 시뮬레이션 포트폴리오 상태가 항상 초기화됨

현재 구현에서는 각 티커마다 포트폴리오 상태가 초기화됩니다. 이로 인해:

  1. my_qty = 0이므로 기존 보유 종목에 대한 손절/익절 로직이 작동하지 않음
  2. 모든 종목이 독립적으로 처리되어 실제 자산 배분이 반영되지 않음

실제 시뮬레이션 모드에서는 DB나 상태 저장소에서 포트폴리오 상태를 로드하는 것이 필요합니다.

또한, reason 변수가 사용되지 않으므로 언더스코어 접두사를 추가하세요:

-            action, qty, reason = decide_order(
+            action, qty, _reason = decide_order(

126-135: HOLD 액션에 대한 position_qty 계산 누락

Line 130에서 HOLD 액션이 else 분기로 처리되어 my_qty - qty가 됩니다. HOLD일 때 qty=0이므로 현재는 문제없지만, 의도를 명확히 하기 위해 수정을 권장합니다:

-                "position_qty": my_qty + qty if action == 'BUY' else my_qty - qty,
+                "position_qty": my_qty + qty if action == 'BUY' else (my_qty - qty if action == 'SELL' else my_qty),

75-75: 불필요한 f-string 접두사 제거

여러 줄에서 플레이스홀더 없이 f-string을 사용하고 있습니다. 일반 문자열로 변경하세요:

-                print(f"   [Skip] 데이터 없음")
+                print("   [Skip] 데이터 없음")

동일한 수정이 필요한 라인: 84, 102, 169

AI/modules/signal/workflows/train.py (4)

27-27: PEP 484 타입 힌트 수정 필요

tickers 매개변수가 None을 기본값으로 가지지만 타입 힌트에 명시되지 않았습니다:

-def run_global_training(tickers: list = None, model_type: str = "transformer", epochs: int = 30):
+def run_global_training(tickers: list | None = None, model_type: str = "transformer", epochs: int = 30):

81-86: 학습 재현성을 위한 랜덤 시드 설정 권장

np.random.shuffle이 시드 없이 호출되어 학습 결과가 실행마다 달라질 수 있습니다. 재현 가능한 실험을 위해 랜덤 시드를 설정하세요:

+    # 재현성을 위한 랜덤 시드 설정
+    np.random.seed(42)
+    
     # 학습/검증 데이터 분리 (Shuffle)
     indices = np.arange(len(X_train_concat))
     np.random.shuffle(indices)

TensorFlow 시드도 함께 설정하면 더 좋습니다:

import tensorflow as tf
tf.random.set_seed(42)

67-69: 예외 처리 시 실패 건수 추적 권장

현재 예외가 발생해도 조용히 무시되어 얼마나 많은 티커가 실패했는지 파악하기 어렵습니다. 최소한 실패 건수를 추적하세요:

     valid_ticker_count = 0
+    failed_ticker_count = 0
     
     for ticker in tqdm(tickers):
         try:
             # ...
             valid_ticker_count += 1
             
         except Exception as e:
-            # print(f"   [Error] {ticker}: {e}")
-            pass
+            failed_ticker_count += 1
+            # 디버깅 필요 시 주석 해제: print(f"   [Error] {ticker}: {e}")
+
+    if failed_ticker_count > 0:
+        print(f"   - {failed_ticker_count}개 종목 데이터 로드 실패")

109-109: 사용하지 않는 history 변수

학습 히스토리를 사용하지 않으므로 언더스코어 접두사를 추가하거나, 학습 결과 로깅에 활용하세요:

-    history = model.train(X_train, y_train, X_val, y_val)
+    _history = model.train(X_train, y_train, X_val, y_val)

또는 학습 결과를 로깅:

history = model.train(X_train, y_train, X_val, y_val)
print(f"   - Final Val Accuracy: {history.history['val_accuracy'][-1]:.4f}")
AI/modules/signal/core/base_model.py (1)

8-11: 사용하지 않는 pandas import

pandas가 import되었지만 이 파일에서 사용되지 않습니다:

 from abc import ABC, abstractmethod
 from typing import Dict, Any, Optional
-import pandas as pd
 import numpy as np
AI/modules/collector/news_data.py (2)

74-84: 본문 추출 로직 개선 가능성

단순 <p> 태그 수집 방식은 일부 사이트에서 실패할 수 있습니다. article, main, content 클래스를 우선 탐색하면 정확도가 향상됩니다:

# 우선 article/main 영역 탐색
article = soup.find('article') or soup.find('main') or soup.find(class_=['content', 'article-body'])
if article:
    paragraphs = article.find_all('p')
else:
    paragraphs = soup.find_all('p')

현재 구현도 기능적으로 동작하며, 주석에 한계가 명시되어 있어 허용 가능합니다.


140-157: Rate Limiting 고려 권장

기본 limit=3에서는 문제없지만, 더 많은 기사를 수집할 경우 요청 간 딜레이 추가를 권장합니다:

import time

for item in news_items:
    # ... 처리 로직 ...
    time.sleep(0.5)  # 요청 간 0.5초 딜레이

또한, requests.get 실패 시 재시도 로직(예: tenacity 라이브러리)을 고려하세요.

AI/tests/optimize_hyperparameter.py (4)

67-78: 라벨링 컨벤션 주의 및 배열 수정 방식

라벨링이 비표준 컨벤션(0=BUY, 1=HOLD)을 사용합니다. 일반적으로 1이 Positive Class이므로 혼란을 줄 수 있습니다. 코드 내 일관성은 있지만, 주석을 더 명확히 하세요.

또한 Line 77에서 직접 배열을 수정하는 대신 복사본을 사용하는 것이 안전합니다:

-    labels = np.where(future_ret > threshold, 0, 1)
-    
-    # 마지막 horizon 기간은 NaN 처리 (미래 데이터 없음)
-    labels[-horizon:] = -1 
-    return pd.Series(labels, index=close_prices.index), future_ret
+    labels = np.where(future_ret > threshold, 0, 1).copy()
+    
+    # 마지막 horizon 기간은 NaN 처리 (미래 데이터 없음)
+    labels[-horizon:] = -1
+    return pd.Series(labels, index=close_prices.index), future_ret

144-148: 루프 내부 import 문 이동 필요

MinMaxScaler import가 objective 함수의 루프 내부에 있어 매 trial, 매 ticker마다 반복됩니다. 파일 상단으로 이동하세요:

 from sklearn.utils.class_weight import compute_class_weight
+from sklearn.preprocessing import MinMaxScaler
 from tensorflow.keras.callbacks import EarlyStopping

그리고 Line 146을 제거합니다.


181-182: zipstrict=True 추가

클래스 수와 가중치 수가 일치하지 않을 경우 조용히 잘리는 것을 방지합니다:

-    class_weight_dict = {i: w for i, w in zip(classes, weights)}
+    class_weight_dict = {i: w for i, w in zip(classes, weights, strict=True)}

37-60: 모듈 import 시 데이터 로딩 주의

전역 데이터 로딩이 모듈 import 시점에 실행됩니다. 이 파일이 다른 곳에서 import되면 의도치 않게 DB 접속과 출력이 발생합니다.

if __name__ == "__main__": 블록 내부로 이동하거나, lazy loading 패턴을 사용하세요:

_grouped_data = None

def get_grouped_data():
    global _grouped_data
    if _grouped_data is None:
        print("[AutoML] 데이터 메모리 로딩 중...")
        # ... 로딩 로직 ...
        _grouped_data = grouped_data
    return _grouped_data

현재는 독립 실행 스크립트이므로 큰 문제는 아닙니다.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ccee132 and 474341c.

⛔ Files ignored due to path filters (9)
  • AI/data/input_data/news_summary_20250913.csv is excluded by !**/*.csv
  • AI/data/input_data/news_summary_20250914.csv is excluded by !**/*.csv
  • AI/data/input_data/news_summary_20250915.csv is excluded by !**/*.csv
  • AI/data/input_data/news_summary_20250916.csv is excluded by !**/*.csv
  • AI/data/input_data/news_summary_20250917.csv is excluded by !**/*.csv
  • AI/data/input_data/news_summary_20250918.csv is excluded by !**/*.csv
  • AI/data/stability_score_2025.csv is excluded by !**/*.csv
  • AI/data/weights/transformer/universal_scaler.pkl is excluded by !**/*.pkl
  • AI/transformer/scaler/scaler.pkl is excluded by !**/*.pkl
📒 Files selected for processing (78)
  • AI/backtrade/__init__.py
  • AI/backtrade/main.py
  • AI/backtrade/order_policy.py
  • AI/daily_data_collection/__init__.py
  • AI/daily_data_collection/main.py
  • AI/data/weights/transformer/universal_transformer.keras
  • AI/finder/__init__.py
  • AI/finder/financial_eval.py
  • AI/finder/main.py
  • AI/finder/ticker_selector.py
  • AI/libs/core/__init__.py
  • AI/libs/core/pipeline.py
  • AI/libs/database/connection.py
  • AI/libs/database/fetcher.py
  • AI/libs/database/repository.py
  • AI/libs/llm/__init__.py
  • AI/libs/llm/base_client.py
  • AI/libs/llm/groq.py
  • AI/libs/llm/ollama.py
  • AI/libs/llm_clients/__init__.py
  • AI/libs/llm_clients/ollama_client.py
  • AI/libs/utils/__init__.py
  • AI/libs/utils/evaluate_transformer.py
  • AI/libs/utils/news_processing.py
  • AI/libs/utils/news_processing_requests.py
  • AI/libs/utils/optimize_hyperparameter.py
  • AI/libs/utils/save_executions_to_db.py
  • AI/libs/utils/save_reports_to_db.py
  • AI/modules/analysis/__init__.py
  • AI/modules/analysis/generator.py
  • AI/modules/analysis/prompt.py
  • AI/modules/analysis/report_builder.py
  • AI/modules/collector/__init__.py
  • AI/modules/collector/market_data.py
  • AI/modules/collector/news_data.py
  • AI/modules/finder/__init__,py
  • AI/modules/finder/evaluator.py
  • AI/modules/finder/selector.py
  • AI/modules/signal/core/__init__.py
  • AI/modules/signal/core/base_model.py
  • AI/modules/signal/core/data_loader.py
  • AI/modules/signal/core/features.py
  • AI/modules/signal/models/__init__.py
  • AI/modules/signal/models/transformer/__init__.py
  • AI/modules/signal/models/transformer/architecture.py
  • AI/modules/signal/models/transformer/wrapper.py
  • AI/modules/signal/workflows/inference.py
  • AI/modules/signal/workflows/train.py
  • AI/modules/trader/__init__.py
  • AI/modules/trader/engine.py
  • AI/modules/trader/policy.py
  • AI/pipelines/__init__.py
  • AI/pipelines/daily_routine.py
  • AI/tests/evaluate_model.py
  • AI/tests/optimize_hyperparameter.py
  • AI/tests/quick_db_check.py
  • AI/tests/test_transformer_backtrader.py
  • AI/transformer/__init__.py
  • AI/transformer/main.py
  • AI/transformer/modules/__init__.py
  • AI/transformer/modules/features.py
  • AI/transformer/modules/inference.py
  • AI/transformer/modules/models.py
  • AI/transformer/scaler/.gitkeep
  • AI/transformer/training/__init__.py
  • AI/transformer/training/training_transformer.py
  • AI/transformer/weights/initial.weights.h5
  • AI/xai/.gitkeep
  • AI/xai/__init__.py
  • AI/xai/jobs/.gitkeep
  • AI/xai/modules/.gitkeep
  • AI/xai/modules/analysis.py
  • AI/xai/modules/fetcher.py
  • AI/xai/modules/generate.py
  • AI/xai/modules/groq_client.py
  • AI/xai/modules/prompt.py
  • AI/xai/run_xai.py
  • AI/xai/templates/.gitkeep
💤 Files with no reviewable changes (35)
  • AI/libs/utils/init.py
  • AI/transformer/modules/models.py
  • AI/libs/core/init.py
  • AI/xai/init.py
  • AI/xai/modules/groq_client.py
  • AI/xai/modules/generate.py
  • AI/libs/utils/news_processing_requests.py
  • AI/libs/utils/save_reports_to_db.py
  • AI/transformer/modules/features.py
  • AI/libs/llm_clients/ollama_client.py
  • AI/transformer/training/training_transformer.py
  • AI/libs/utils/save_executions_to_db.py
  • AI/finder/ticker_selector.py
  • AI/libs/utils/news_processing.py
  • AI/daily_data_collection/init.py
  • AI/xai/modules/analysis.py
  • AI/transformer/main.py
  • AI/xai/run_xai.py
  • AI/transformer/modules/inference.py
  • AI/transformer/modules/init.py
  • AI/libs/core/pipeline.py
  • AI/xai/modules/prompt.py
  • AI/transformer/init.py
  • AI/finder/main.py
  • AI/backtrade/main.py
  • AI/backtrade/order_policy.py
  • AI/finder/init.py
  • AI/libs/utils/evaluate_transformer.py
  • AI/xai/modules/fetcher.py
  • AI/finder/financial_eval.py
  • AI/libs/utils/optimize_hyperparameter.py
  • AI/libs/llm_clients/init.py
  • AI/backtrade/init.py
  • AI/daily_data_collection/main.py
  • AI/transformer/training/init.py
🧰 Additional context used
🧬 Code graph analysis (28)
AI/modules/signal/models/transformer/__init__.py (2)
AI/modules/signal/models/transformer/wrapper.py (1)
  • TransformerSignalModel (18-108)
AI/modules/signal/models/transformer/architecture.py (1)
  • build_transformer_model (37-81)
AI/libs/llm/ollama.py (2)
AI/libs/llm/base_client.py (3)
  • BaseLLMClient (11-36)
  • generate_text (19-31)
  • get_health (34-36)
AI/libs/llm/groq.py (2)
  • generate_text (23-43)
  • get_health (45-47)
AI/modules/trader/__init__.py (2)
AI/modules/trader/engine.py (1)
  • BacktestEngine (27-190)
AI/modules/trader/policy.py (1)
  • decide_order (10-91)
AI/modules/finder/evaluator.py (2)
AI/libs/database/fetcher.py (1)
  • fetch_ohlcv (10-68)
AI/modules/signal/core/features.py (1)
  • add_technical_indicators (11-58)
AI/modules/signal/workflows/inference.py (4)
AI/modules/signal/core/data_loader.py (2)
  • SignalDataLoader (24-89)
  • load_data (30-64)
AI/modules/signal/models/__init__.py (1)
  • get_model (12-21)
AI/modules/signal/core/base_model.py (2)
  • build (26-32)
  • predict (47-55)
AI/modules/signal/models/transformer/wrapper.py (2)
  • build (23-51)
  • predict (82-91)
AI/pipelines/daily_routine.py (6)
AI/modules/signal/workflows/inference.py (1)
  • run_inference (31-113)
AI/modules/trader/policy.py (1)
  • decide_order (10-91)
AI/modules/analysis/generator.py (2)
  • ReportGenerator (22-80)
  • generate_report (30-80)
AI/modules/collector/market_data.py (1)
  • update_market_data (24-93)
AI/libs/database/repository.py (2)
  • save_executions_to_db (20-99)
  • save_reports_to_db (102-163)
AI/modules/signal/core/data_loader.py (2)
  • SignalDataLoader (24-89)
  • load_data (30-64)
AI/modules/collector/__init__.py (2)
AI/modules/collector/market_data.py (1)
  • update_market_data (24-93)
AI/modules/collector/news_data.py (1)
  • collect_news (107-160)
AI/modules/signal/core/data_loader.py (2)
AI/libs/database/fetcher.py (1)
  • fetch_ohlcv (10-68)
AI/modules/signal/core/features.py (1)
  • add_technical_indicators (11-58)
AI/modules/signal/workflows/train.py (5)
AI/modules/signal/core/data_loader.py (3)
  • SignalDataLoader (24-89)
  • load_data (30-64)
  • create_sequences (66-89)
AI/modules/signal/models/__init__.py (1)
  • get_model (12-21)
AI/modules/finder/selector.py (1)
  • load_all_tickers_from_db (66-100)
AI/modules/signal/core/base_model.py (3)
  • build (26-32)
  • train (35-44)
  • save (58-60)
AI/modules/signal/models/transformer/wrapper.py (3)
  • build (23-51)
  • train (53-80)
  • save (93-101)
AI/libs/database/repository.py (3)
AI/libs/database/connection.py (1)
  • get_db_conn (120-138)
frontend/src/utils/TimeUtils.js (1)
  • now (24-24)
frontend/src/utils/backtestingFormat.js (1)
  • rows (66-80)
AI/modules/collector/news_data.py (3)
AI/libs/llm/groq.py (2)
  • GroqClient (13-47)
  • generate_text (23-43)
AI/libs/llm/base_client.py (1)
  • generate_text (19-31)
AI/libs/llm/ollama.py (1)
  • generate_text (18-42)
AI/modules/collector/market_data.py (1)
AI/libs/database/connection.py (1)
  • get_db_conn (120-138)
AI/modules/analysis/__init__.py (1)
AI/modules/analysis/generator.py (1)
  • ReportGenerator (22-80)
AI/modules/signal/models/transformer/wrapper.py (2)
AI/modules/signal/core/base_model.py (6)
  • BaseSignalModel (13-65)
  • build (26-32)
  • train (35-44)
  • predict (47-55)
  • save (58-60)
  • load (63-65)
AI/modules/signal/models/transformer/architecture.py (1)
  • build_transformer_model (37-81)
AI/tests/evaluate_model.py (3)
AI/modules/signal/models/__init__.py (1)
  • get_model (12-21)
AI/modules/signal/core/data_loader.py (1)
  • load_data (30-64)
AI/modules/finder/selector.py (1)
  • load_all_tickers_from_db (66-100)
AI/libs/database/fetcher.py (1)
AI/libs/database/connection.py (1)
  • get_engine (144-148)
AI/libs/llm/__init__.py (3)
AI/libs/llm/base_client.py (1)
  • BaseLLMClient (11-36)
AI/libs/llm/groq.py (1)
  • GroqClient (13-47)
AI/libs/llm/ollama.py (1)
  • OllamaClient (13-49)
AI/modules/signal/models/__init__.py (2)
AI/modules/signal/core/base_model.py (1)
  • BaseSignalModel (13-65)
AI/modules/signal/models/transformer/wrapper.py (1)
  • TransformerSignalModel (18-108)
AI/tests/quick_db_check.py (1)
AI/libs/database/connection.py (1)
  • get_db_conn (120-138)
AI/modules/finder/selector.py (1)
AI/libs/database/connection.py (1)
  • get_db_conn (120-138)
AI/pipelines/__init__.py (1)
AI/pipelines/daily_routine.py (1)
  • run_daily_pipeline (29-169)
AI/modules/trader/engine.py (6)
AI/modules/signal/core/data_loader.py (2)
  • SignalDataLoader (24-89)
  • load_data (30-64)
AI/modules/signal/models/__init__.py (1)
  • get_model (12-21)
AI/modules/trader/policy.py (1)
  • decide_order (10-91)
AI/libs/database/repository.py (1)
  • save_executions_to_db (20-99)
AI/modules/signal/core/base_model.py (2)
  • load (63-65)
  • predict (47-55)
AI/modules/signal/models/transformer/wrapper.py (2)
  • load (103-108)
  • predict (82-91)
AI/tests/test_transformer_backtrader.py (6)
AI/modules/signal/core/data_loader.py (1)
  • SignalDataLoader (24-89)
AI/modules/signal/models/__init__.py (1)
  • get_model (12-21)
AI/modules/trader/engine.py (2)
  • BacktestEngine (27-190)
  • run (46-132)
AI/modules/signal/core/features.py (1)
  • add_technical_indicators (11-58)
AI/modules/signal/core/base_model.py (2)
  • build (26-32)
  • predict (47-55)
AI/modules/signal/models/transformer/wrapper.py (2)
  • build (23-51)
  • predict (82-91)
AI/modules/signal/core/__init__.py (3)
AI/modules/signal/core/base_model.py (1)
  • BaseSignalModel (13-65)
AI/modules/signal/core/data_loader.py (1)
  • SignalDataLoader (24-89)
AI/modules/signal/core/features.py (1)
  • add_technical_indicators (11-58)
AI/libs/llm/base_client.py (2)
AI/libs/llm/groq.py (2)
  • generate_text (23-43)
  • get_health (45-47)
AI/libs/llm/ollama.py (2)
  • generate_text (18-42)
  • get_health (44-49)
AI/modules/signal/core/base_model.py (1)
AI/modules/signal/models/transformer/wrapper.py (5)
  • build (23-51)
  • train (53-80)
  • predict (82-91)
  • save (93-101)
  • load (103-108)
AI/libs/llm/groq.py (2)
AI/libs/llm/base_client.py (3)
  • BaseLLMClient (11-36)
  • generate_text (19-31)
  • get_health (34-36)
AI/libs/llm/ollama.py (2)
  • generate_text (18-42)
  • get_health (44-49)
AI/tests/optimize_hyperparameter.py (4)
AI/modules/signal/core/data_loader.py (2)
  • SignalDataLoader (24-89)
  • load_data (30-64)
AI/modules/signal/models/__init__.py (1)
  • get_model (12-21)
AI/modules/signal/core/base_model.py (3)
  • build (26-32)
  • train (35-44)
  • predict (47-55)
AI/modules/signal/models/transformer/wrapper.py (3)
  • build (23-51)
  • train (53-80)
  • predict (82-91)
🪛 Ruff (0.14.10)
AI/modules/trader/policy.py

11-11: Unused function argument: ticker

(ARG001)


17-17: Unused function argument: total_asset

(ARG001)

AI/libs/llm/ollama.py

36-36: Probable use of requests call without timeout

(S113)


40-40: Do not catch blind exception: Exception

(BLE001)


46-46: Probable use of requests call without timeout

(S113)


47-47: Consider moving this statement to an else block

(TRY300)


48-48: Do not use bare except

(E722)

AI/modules/signal/workflows/inference.py

41-41: f-string without any placeholders

Remove extraneous f prefix

(F541)


55-55: Do not catch blind exception: Exception

(BLE001)


107-107: Consider moving this statement to an else block

(TRY300)


109-109: Do not catch blind exception: Exception

(BLE001)

AI/pipelines/daily_routine.py

47-47: Do not catch blind exception: Exception

(BLE001)


55-55: Do not catch blind exception: Exception

(BLE001)


75-75: f-string without any placeholders

Remove extraneous f prefix

(F541)


84-84: f-string without any placeholders

Remove extraneous f prefix

(F541)


93-93: Unpacked variable reason is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


102-102: f-string without any placeholders

Remove extraneous f prefix

(F541)


137-137: Do not catch blind exception: Exception

(BLE001)


157-157: zip() without an explicit strict= parameter

Add explicit value for parameter strict=

(B905)


169-169: f-string without any placeholders

Remove extraneous f prefix

(F541)

AI/modules/collector/__init__.py

5-8: __all__ is not sorted

Apply an isort-style sorting to __all__

(RUF022)

AI/modules/signal/workflows/train.py

27-27: PEP 484 prohibits implicit Optional

Convert to T | None

(RUF013)


67-69: try-except-pass detected, consider logging the exception

(S110)


67-67: Do not catch blind exception: Exception

(BLE001)


67-67: Local variable e is assigned to but never used

Remove assignment to unused variable e

(F841)


109-109: Local variable history is assigned to but never used

Remove assignment to unused variable history

(F841)


130-130: f-string without any placeholders

Remove extraneous f prefix

(F541)

AI/libs/database/repository.py

96-96: Use raise without specifying exception name

Remove exception name

(TRY201)


154-154: Do not catch blind exception: Exception

(BLE001)

AI/modules/collector/news_data.py

55-55: Consider moving this statement to an else block

(TRY300)


56-56: Do not catch blind exception: Exception

(BLE001)


83-83: Do not catch blind exception: Exception

(BLE001)


103-103: Consider moving this statement to an else block

(TRY300)


104-104: Do not catch blind exception: Exception

(BLE001)


134-134: Do not catch blind exception: Exception

(BLE001)

AI/modules/signal/models/transformer/architecture.py

43-43: Do not use mutable data structures for argument defaults

Replace with None; initialize within function

(B006)

AI/modules/collector/market_data.py

88-88: Do not catch blind exception: Exception

(BLE001)

AI/modules/signal/models/transformer/wrapper.py

32-32: Avoid specifying long messages outside the exception class

(TRY003)


61-61: Avoid specifying long messages outside the exception class

(TRY003)


85-85: Avoid specifying long messages outside the exception class

(TRY003)


106-106: Avoid specifying long messages outside the exception class

(TRY003)

AI/tests/evaluate_model.py

85-85: f-string without any placeholders

Remove extraneous f prefix

(F541)


90-90: f-string without any placeholders

Remove extraneous f prefix

(F541)


112-112: Do not catch blind exception: Exception

(BLE001)


201-201: Multiple statements on one line (colon)

(E701)


215-215: Do not catch blind exception: Exception

(BLE001)


247-247: f-string without any placeholders

Remove extraneous f prefix

(F541)


265-265: f-string without any placeholders

Remove extraneous f prefix

(F541)


269-269: f-string without any placeholders

Remove extraneous f prefix

(F541)

AI/modules/analysis/report_builder.py

66-66: Unused static method argument: ticker

(ARG004)

AI/modules/signal/models/__init__.py

19-19: Avoid specifying long messages outside the exception class

(TRY003)

AI/tests/quick_db_check.py

42-42: Do not catch blind exception: Exception

(BLE001)

AI/modules/finder/selector.py

55-55: Consider moving this statement to an else block

(TRY300)


57-57: Do not catch blind exception: Exception

(BLE001)


63-63: Multiple statements on one line (colon)

(E701)


64-64: Multiple statements on one line (colon)

(E701)


88-88: Multiple statements on one line (colon)

(E701)


91-91: Consider moving this statement to an else block

(TRY300)


93-93: Do not catch blind exception: Exception

(BLE001)


99-99: Multiple statements on one line (colon)

(E701)


100-100: Multiple statements on one line (colon)

(E701)

AI/modules/trader/engine.py

46-46: PEP 484 prohibits implicit Optional

Convert to T | None

(RUF013)


124-124: f-string without any placeholders

Remove extraneous f prefix

(F541)

AI/tests/test_transformer_backtrader.py

64-64: Do not catch blind exception: Exception

(BLE001)


102-102: Do not catch blind exception: Exception

(BLE001)


135-135: Do not catch blind exception: Exception

(BLE001)

AI/libs/llm/groq.py

18-18: Avoid specifying long messages outside the exception class

(TRY003)


41-41: Do not catch blind exception: Exception

(BLE001)

AI/tests/optimize_hyperparameter.py

57-57: Do not catch blind exception: Exception

(BLE001)


182-182: zip() without an explicit strict= parameter

Add explicit value for parameter strict=

(B905)


203-203: Local variable history is assigned to but never used

Remove assignment to unused variable history

(F841)

🔇 Additional comments (23)
AI/tests/evaluate_model.py (2)

63-68: LGTM!

라벨링 로직이 올바르게 구현되어 있습니다. 미래 수익률 기반 라벨과 마지막 horizon 개 샘플을 -1로 마킹하여 나중에 필터링하는 방식이 명확합니다.


84-114: LGTM!

모델 및 스케일러 로드 로직이 적절한 에러 핸들링과 함께 구현되어 있습니다. 필수 파일 존재 확인 후 로드를 시도하고, 실패 시 명확한 에러 메시지를 출력합니다.

AI/modules/trader/engine.py (1)

134-166: LGTM!

주문 체결 로직이 올바르게 구현되어 있습니다. 수수료 계산, 현금/포지션 검증, 평단가 갱신(이동평균법), 실현 손익 계산이 적절히 처리됩니다.

AI/modules/trader/__init__.py (1)

2-8: LGTM!

패키지 초기화 파일이 올바르게 구성되어 있습니다. BacktestEnginedecide_order를 re-export하여 from AI.modules.trader import BacktestEngine, decide_order 형태로 깔끔하게 import할 수 있습니다.

AI/libs/database/connection.py (1)

1-1: LGTM!

파일 경로 변경(utils/get_db_conn.pylibs/database/connection.py)에 따른 헤더 주석 업데이트입니다. DB 연결 유틸리티가 새로운 libs/database 패키지 구조에 맞게 재배치되었습니다.

AI/pipelines/__init__.py (1)

1-6: LGTM!

pipelines 패키지 초기화 파일이 올바르게 구성되어 있습니다. run_daily_pipeline을 re-export하여 from AI.pipelines import run_daily_pipeline 형태로 간결하게 import할 수 있습니다.

AI/libs/database/fetcher.py (1)

1-8: LGTM!

파일 위치 변경에 따른 임포트 경로 업데이트가 올바르게 적용되었습니다. get_engineconnection.py에 정의된 것과 일치합니다.

AI/modules/analysis/__init__.py (1)

1-6: LGTM!

패키지 초기화 파일이 깔끔하게 구성되었습니다. ReportGenerator를 퍼블릭 API로 적절히 노출하고 있습니다.

AI/modules/signal/core/__init__.py (1)

1-10: LGTM!

signal/core 패키지의 퍼블릭 API가 깔끔하게 정의되었습니다. BaseSignalModel, SignalDataLoader, add_technical_indicators 세 가지 핵심 컴포넌트를 적절히 노출하고 있습니다.

AI/libs/llm/__init__.py (1)

1-10: LGTM!

패키지 구조가 잘 구성되어 있습니다. 추상 기본 클래스와 구현체를 명확하게 re-export하고 __all__로 public API를 명시적으로 정의한 점이 좋습니다.

AI/modules/signal/models/transformer/__init__.py (1)

1-8: LGTM!

wrapper(모델 인터페이스)와 architecture(아키텍처 빌더)의 분리가 적절하며, public API가 명확하게 정의되어 있습니다.

AI/libs/llm/base_client.py (1)

11-36: 잘 설계된 추상 인터페이스

LLM 클라이언트의 공통 인터페이스가 명확하게 정의되어 있습니다. 이를 통해 Groq, Ollama 등 다양한 LLM 서비스를 일관된 방식으로 교체 가능하게 한 점이 좋습니다.

AI/modules/analysis/prompt.py (1)

7-37: LGTM!

프롬프트 템플릿이 체계적으로 구성되어 있으며, 기술적 지표와 AI 예측 정보를 명확하게 포함하고 있습니다. 포맷 지정자(.2f, .4f 등)가 금융 데이터에 적합하게 설정되어 있습니다.

USER_PROMPT_TEMPLATE.format()을 호출하는 generator.py에서 None 값에 대한 방어 로직이 있는지 확인하세요. None이 전달될 경우 포맷 에러가 발생할 수 있습니다.

AI/modules/signal/models/transformer/architecture.py (1)

41-41: ff_dim=4가 의도된 값인지 확인 필요

Feed-Forward Network의 hidden dimension이 4로 매우 작습니다. 일반적인 Transformer에서는 모델 차원의 4배(예: 512, 1024) 정도를 사용합니다. 만약 ff_dim이 배수(multiplier)를 의미한다면 변수명을 ff_multiplier로 변경하거나, 실제 dimension이라면 더 큰 값을 고려해 보세요.

AI/modules/signal/models/__init__.py (1)

7-21: 모델 레지스트리 및 팩토리 패턴 구현 승인

확장 가능한 모델 레지스트리와 case-insensitive 조회가 잘 구현되어 있습니다. 에러 메시지에 사용 가능한 모델 목록을 포함한 점도 좋습니다.

AI/modules/signal/core/data_loader.py (2)

15-22: sys.path 조작 패턴이 PR 전체에서 일관되게 사용되고 있습니다.

현재 방식은 동작하지만, 장기적으로는 패키지를 pip install -e .로 설치하거나 PYTHONPATH 환경변수를 사용하는 것이 더 견고합니다.


61-62: tz_localize(None) 대신 tz_convert(None)을 사용해야 합니다.

이미 timezone이 있는 DatetimeIndex에 tz_localize(None)을 호출하면 TypeError가 발생합니다. timezone 정보를 제거하려면 tz_convert(None)을 사용해야 합니다.

🐛 수정 제안
         if df.index.tz is not None:
-            df.index = df.index.tz_localize(None)
+            df.index = df.index.tz_convert(None)

Likely an incorrect or invalid review comment.

AI/libs/llm/groq.py (1)

41-43: 에러 시 빈 문자열 반환이 OllamaClient와 일관됩니다.

두 클라이언트 모두 예외 발생 시 빈 문자열을 반환하는 패턴을 사용합니다. 일관성은 좋지만, 호출자가 실제 빈 응답과 에러를 구분하기 어렵습니다. 향후 Optional[str] 반환 또는 커스텀 Result 타입 도입을 고려해 보세요.

AI/pipelines/daily_routine.py (1)

41-56: 견고한 오류 처리 패턴 적용 - LGTM

XAI 초기화 실패 시 enable_xai=False로 설정하여 계속 진행하고, 데이터 수집 실패 시에도 기존 데이터로 진행하는 패턴이 일일 자동화 파이프라인에 적합합니다. Graceful degradation이 잘 구현되어 있습니다.

AI/modules/signal/workflows/train.py (1)

119-128: Local Scaling 설계 결정이 잘 문서화됨

스케일러 저장에 대한 주석이 명확하게 설계 의도를 설명하고 있습니다. 추론 시 각 종목별로 새로 스케일링해야 한다는 점이 inference.py의 구현과 일치합니다.

AI/modules/signal/core/base_model.py (1)

13-65: 잘 설계된 추상 인터페이스 - LGTM

BaseSignalModel 추상 클래스가 명확한 계약을 정의하고 있으며, TransformerSignalModel(wrapper.py)에서 올바르게 구현되어 있습니다. 이 인터페이스를 통해 다양한 모델(Transformer, LSTM, XGBoost 등)을 쉽게 교체할 수 있는 구조입니다.

다만, train 메서드의 반환 타입을 명시하면 더 좋습니다:

     @abstractmethod
     def train(self, 
               X_train: np.ndarray, 
               y_train: np.ndarray, 
               X_val: Optional[np.ndarray] = None, 
               y_val: Optional[np.ndarray] = None, 
-              **kwargs):
+              **kwargs) -> Any:
AI/modules/collector/news_data.py (1)

125-149: LLM 폴백 처리가 잘 구현됨 - LGTM

LLM 초기화 실패 시에도 뉴스 수집이 계속 진행되며, 요약 기능만 비활성화됩니다. 사용자에게 명확한 상태 메시지("요약 기능 비활성화", "본문 접근 불가")를 제공하는 것도 좋습니다.

AI/tests/optimize_hyperparameter.py (1)

102-245: Optuna objective 함수 구조 - LGTM

하이퍼파라미터 탐색 공간이 잘 정의되어 있고, 시계열 데이터에 맞게 셔플 없이 시간순 분할을 사용합니다. 클래스 가중치 적용과 거래 점수 기반 평가 메트릭도 적절합니다.

history 변수가 사용되지 않으므로 언더스코어를 추가하세요:

-    history = model_wrapper.train(
+    _history = model_wrapper.train(

Comment on lines +93 to +96
except Exception as e:
conn.rollback()
print(f"[Repository][Error] 체결 내역 저장 중 오류 발생: {e}")
raise e
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

raise e 대신 raise를 사용하세요.

raise e는 예외의 원래 traceback을 손실시킬 수 있습니다. raise만 사용하면 전체 스택 트레이스가 유지됩니다.

🐛 수정 제안
     except Exception as e:
         conn.rollback()
         print(f"[Repository][Error] 체결 내역 저장 중 오류 발생: {e}")
-        raise e
+        raise
📝 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.

Suggested change
except Exception as e:
conn.rollback()
print(f"[Repository][Error] 체결 내역 저장 중 오류 발생: {e}")
raise e
except Exception as e:
conn.rollback()
print(f"[Repository][Error] 체결 내역 저장 중 오류 발생: {e}")
raise
🧰 Tools
🪛 Ruff (0.14.10)

96-96: Use raise without specifying exception name

Remove exception name

(TRY201)

🤖 Prompt for AI Agents
In @AI/libs/database/repository.py around lines 93 - 96, The except block in
Repository.catch (the except Exception as e: block) uses "raise e", which can
lose the original traceback; change it to use a bare "raise" after
conn.rollback() and the print call so the original exception stack is preserved
(update the except block handling around conn.rollback() and the print statement
to re-raise with "raise" instead of "raise e").

Comment on lines +145 to +149
execute_values(cursor, insert_query, data_to_insert, fetch=True)

# RETURNING id 결과 가져오기
rows = cursor.fetchall()
generated_ids = [row[0] for row in rows]
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cd AI/libs/database && head -160 repository.py | tail -20

Repository: SISC-IT/sisc-web

Length of output: 641


🏁 Script executed:

cat AI/libs/database/repository.py | head -150 | tail -10

Repository: SISC-IT/sisc-web

Length of output: 317


🏁 Script executed:

fd -t py . --exec grep -l "requirements" {} \; | head -5

Repository: SISC-IT/sisc-web

Length of output: 229


🏁 Script executed:

find . -name "requirements*.txt" -o -name "setup.py" -o -name "pyproject.toml" -o -name "poetry.lock" | head -10

Repository: SISC-IT/sisc-web

Length of output: 81


🏁 Script executed:

rg "execute_values" --type=py -B2 -A5

Repository: SISC-IT/sisc-web

Length of output: 3085


🏁 Script executed:

cat AI/requirements.txt

Repository: SISC-IT/sisc-web

Length of output: 244


🏁 Script executed:

rg "RETURNING" --type py -B3 -A1

Repository: SISC-IT/sisc-web

Length of output: 956


🏁 Script executed:

sed -n '130,150p' AI/libs/database/repository.py

Repository: SISC-IT/sisc-web

Length of output: 693


🏁 Script executed:

rg -A 10 "def execute_values" --type py

Repository: SISC-IT/sisc-web

Length of output: 42


🏁 Script executed:

web_search
psycopg2 execute_values fetch parameter RETURNING clause behavior

Repository: SISC-IT/sisc-web

Length of output: 157


🌐 Web query:

psycopg2 execute_values fetch=True RETURNING clause documentation

💡 Result:

Short answer: psycopg2.extras.execute_values supports a fetch parameter (fetch=True) to return results (useful for INSERT ... RETURNING). But execute_values splits work into pages (page_size); if it executes multiple statements (more than one page) you will only get the results for the final executed statement/page unless you adjust page_size or otherwise page yourself. Sources and example below.

Key points

  • execute_values(..., fetch=True) returns the query results as a list (intended for use with RETURNING). [1]
  • execute_values sends the args in pages (page_size). If more than one page is used, only the final page’s returned rows are available via the single fetch result (so you can miss earlier RETURNING rows). See the GitHub issue reporting this behavior and discussion. [2]
  • Common workaround: increase page_size to cover all rows in one statement or manually page and accumulate returned rows. A StackOverflow answer shows using fetch=True with a large page_size to get all ids. [3]

Minimal example

  • Using execute_values to INSERT and RETURNING ids:
    ids = psycopg2.extras.execute_values(cur,
    "INSERT INTO mytable (a,b) VALUES %s RETURNING id",
    rows, page_size=10000, fetch=True)

    ids will be a list of returned rows (but only for the final page if multiple pages used). [1][3]

Sources

  1. psycopg2.extras.execute_values docs (extras documentation). [1]
  2. GitHub issue discussing execute_values + RETURNING and paging. [2]
  3. StackOverflow thread showing fetch=True + larger page_size as workaround. [5]

🏁 Script executed:

python3 << 'EOF'
# Check psycopg2 execute_values behavior documentation
# execute_values is from psycopg2.extras module
print("psycopg2.extras.execute_values documentation:")
print("=" * 60)
print("""
execute_values signature:
  execute_values(cursor, query, argslist, template=None, fetch=False)

When fetch=True:
- execute_values will call cursor.fetchall() internally
- Returns the result of fetchall() if fetch=True
- The return value is assigned to cursor.description

The correct usage pattern with RETURNING:
1. execute_values(cursor, query, args, fetch=True)
   - This returns results directly if fetch=True
2. OR: execute_values(cursor, query, args) + cursor.fetchall()
   - When fetch=False (default), must call fetchall() separately

The code uses both:
  execute_values(cursor, insert_query, data_to_insert, fetch=True)
  rows = cursor.fetchall()

This is problematic if fetch=True because execute_values with fetch=True
already exhausts the cursor results.
""")
EOF

Repository: SISC-IT/sisc-web

Length of output: 146


🏁 Script executed:

# Check if there are any examples of execute_values usage in psycopg2 docs
python3 << 'EOF'
import sys
try:
    import psycopg2
    from psycopg2 import extras
    print(f"psycopg2 version available: {psycopg2.__version__}")
    print("\nexecute_values docstring:")
    print(extras.execute_values.__doc__)
except Exception as e:
    print(f"Could not import: {e}")
    print("\nBased on psycopg2 documentation:")
    print("- When fetch=True is used, execute_values returns cursor.fetchall()")
    print("- When fetch=False (default), cursor.fetchall() must be called separately")
    print("- Using both fetch=True AND cursor.fetchall() may cause issues")
EOF

Repository: SISC-IT/sisc-web

Length of output: 146


🏁 Script executed:

sed -n '120,160p' AI/libs/database/repository.py

Repository: SISC-IT/sisc-web

Length of output: 1299


execute_values의 페이지네이션으로 인한 RETURNING 결과 손실 가능성

execute_values는 기본적으로 약 100개 단위(page_size)로 데이터를 나누어 처리합니다. 여러 페이지가 필요한 경우 마지막 페이지의 RETURNING 결과만 반환되므로, data_to_insert의 크기가 기본 page_size를 초과하면 대부분의 생성된 ID가 누락됩니다. 현재 코드에서 len(data_to_insert) > 100일 경우 generated_ids는 완전하지 않으며, print 메시지의 ID 개수 불일치로 이를 감지할 수 있습니다.

해결책: page_size 파라미터를 충분히 큰 값으로 지정하거나, fetch=True 대신 cursor.fetchall()을 사용하되 수동 페이징으로 모든 페이지의 결과를 누적하세요.

🤖 Prompt for AI Agents
In @AI/libs/database/repository.py around lines 145 - 149, The current use of
execute_values(insert_query, data_to_insert, fetch=True) can split inserts into
pages and only return the last page's RETURNING rows, causing generated_ids
(constructed from cursor.fetchall()) to be incomplete when len(data_to_insert) >
default page_size; fix by explicitly accumulating RETURNING rows for all pages:
either call execute_values with an explicitly large page_size (e.g.,
len(data_to_insert)) to force a single page, or perform manual paging by
splitting data_to_insert and calling execute_values per chunk (with fetch=True)
and extend a cumulative generated_ids list from each cursor.fetchall() result;
update the code around execute_values, insert_query, data_to_insert,
generated_ids and cursor.fetchall() to implement one of these approaches so all
created IDs are collected.

Comment on lines +66 to +82
def build_context_string(ticker: str, ai_score: float, action: str, data: Dict[str, float]) -> str:
"""
LLM에게 전달할 최종 분석 요약 문자열 생성
"""
interpretations = ReportBuilder.analyze_indicators(data)

context = f"""
[상세 분석 데이터]
1. 추세: {interpretations.get('trend')}
2. 모멘텀(RSI): {interpretations.get('rsi_status')} (RSI: {data.get('rsi', 0):.2f})
3. 신호(MACD): {interpretations.get('macd_status')}
4. 변동성(Bollinger): {interpretations.get('bb_status')}

[AI 모델 판단]
- 상승 예측 확률: {ai_score*100:.1f}%
- 제안 포지션: {action}
"""
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

미사용 파라미터 ticker 제거 또는 활용이 필요합니다.

build_context_string 함수에서 ticker 파라미터가 정의되어 있지만 함수 본문에서 사용되지 않습니다. 의도적으로 context 문자열에 티커 정보를 포함하려 했다면 추가하고, 그렇지 않다면 시그니처에서 제거하세요.

💡 ticker를 context에 포함하는 예시
         context = f"""
         [상세 분석 데이터]
+        종목: {ticker}
         1. 추세: {interpretations.get('trend')}
🧰 Tools
🪛 Ruff (0.14.10)

66-66: Unused static method argument: ticker

(ARG004)

🤖 Prompt for AI Agents
In @AI/modules/analysis/report_builder.py around lines 66 - 82, The
build_context_string function declares a ticker parameter but never uses it;
either remove ticker from the signature or include it in the generated context
string. Locate build_context_string in report_builder.py (it calls
ReportBuilder.analyze_indicators) and either (A) delete the ticker parameter
from the function definition and any call sites that pass it, or (B) prepend or
embed the ticker into the context variable (e.g., add a "[심볼]" or "티커: {ticker}"
line in the f-string) so the parameter is actually used.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
AI/requirements.txt (1)

1-18: 의존성 버전을 고정하여 재현성을 보장하세요.

현재 모든 의존성이 버전 고정 없이 선언되어 있어, 환경 간 일관성 문제와 예상치 못한 breaking change가 발생할 수 있습니다. 특히 프로덕션 환경에서는 의존성 버전을 명시하는 것이 필수적입니다.

📌 버전 고정 권장사항

현재 설치된 정확한 버전을 확인하여 고정하는 것을 권장합니다:

# 현재 설치된 버전 확인
pip freeze > requirements-pinned.txt

또는 주요 버전만 고정하는 방식도 고려할 수 있습니다:

-pandas
+pandas>=2.0.0,<3.0.0
-tensorflow
+tensorflow>=2.15.0,<3.0.0
-scikit-learn
+scikit-learn>=1.3.0,<2.0.0

특히 tensorflow, scikit-learn, numpy 등 머신러닝 라이브러리는 버전 간 호환성 문제가 자주 발생하므로 버전 고정이 중요합니다.

🤖 Fix all issues with AI agents
In `@AI/modules/signal/models/transformer/wrapper.py`:
- Around line 98-106: The save method may call os.makedirs on an empty string
when filepath contains no directory; update
AI/modules/signal/models/transformer/wrapper.py's save(self, filepath: str) to
compute dirpath = os.path.dirname(filepath) and only call os.makedirs(dirpath,
exist_ok=True) if dirpath is non-empty (or default to '.'/os.getcwd()), then
proceed to call self.model.save(filepath); ensure the no-model early return
remains.
- Around line 87-96: The predict method in transformer/wrapper.py (predict)
doesn't accept a verbose kwarg causing a TypeError when callers (e.g.,
inference.py) call model.predict(input_tensor, verbose=0); update the predict
signature to accept either verbose: Optional[int]=None or **kwargs and forward
that through to self.model.predict (e.g., call self.model.predict(X_input,
verbose=verbose) or self.model.predict(X_input, **kwargs)) while preserving the
existing input-shape handling and the early None-model ValueError.

In `@AI/modules/signal/workflows/inference.py`:
- Around line 100-103: The call passes an unsupported verbose kwarg to
TransformerSignalModel.predict causing a TypeError; remove the verbose=0 from
the call in inference.py (change the expression using prediction =
model.predict(input_tensor, verbose=0) to call model.predict(input_tensor)) or
alternatively add a verbose parameter to TransformerSignalModel.predict in
wrapper.py and forward it to self.model.predict if you need logging — reference
the TransformerSignalModel.predict wrapper and the prediction assignment to
locate the code to change.

In `@AI/modules/trader/engine.py`:
- Around line 89-92: The loop in engine.py uses df.iloc[i]['date'] which raises
KeyError because SignalDataLoader.load_data() sets the date as the DataFrame
index; instead, retrieve the date from the index (e.g., use df.index[i] or
df.index[i].date()/strftime(...) depending on expected type) in the for-loop
where today_date is assigned, leaving other uses like current_close =
df.iloc[i]['close'] unchanged.

In `@AI/pipelines/daily_routine.py`:
- Around line 25-27: There are duplicate imports of save_executions_to_db and
save_reports_to_db; remove the redundant line and consolidate into a single
import from AI.libs.database.repository that includes save_executions_to_db,
save_reports_to_db and get_current_position, leaving the existing import of
SignalDataLoader untouched (refer to the import statements that mention
save_executions_to_db, save_reports_to_db, get_current_position and
SignalDataLoader).
- Around line 140-159: Update the dict construction in execution_results.append
so the fields reflect post-trade state: compute "cash_after" as my_cash -
current_price * qty for action == 'BUY', my_cash + current_price * qty for
action == 'SELL', and my_cash for 'HOLD'; compute "position_qty" as my_qty + qty
for 'BUY', my_qty - qty for 'SELL', and my_qty for 'HOLD'; compute "avg_price"
by updating the existing position average only on buys (e.g., use a stored
previous avg like prev_avg_price or my_avg_price and set avg_price =
(prev_avg_price * my_qty + current_price * qty) / (my_qty + qty) when BUY, keep
prev_avg_price when HOLD, and if SELL reduces to zero set avg_price to None or
0), and ensure the dict uses these computed values instead of unconditionally
using my_cash, my_qty, or current_price.

In `@AI/requirements.txt`:
- Line 18: Remove the unused dependency or pin it to a secure version: if `lxml`
is not imported/used anywhere, delete the `lxml` line from requirements.txt; if
it is required, change it to a fixed secure version such as `lxml==6.0.2` (or at
minimum `lxml>=5.3.2`) and update the file so every dependency in
`requirements.txt` has an explicit version specifier to ensure reproducible,
secure installs.
♻️ Duplicate comments (3)
AI/modules/finder/evaluator.py (1)

36-37: 반환 딕셔너리 키 불일치 — 소비자 코드에서 KeyError 발생 가능

에러 시 반환값에 rsi, trend 키가 누락되어 있습니다. 정상 반환(Lines 70-74)과 키 구조를 맞춰야 호출 측에서 일관된 처리가 가능합니다.

🐛 권장 수정
     if df.empty or len(df) < 60:
-        return {'total_score': 0.0, 'reason': '데이터 부족'}
+        return {'total_score': 0.0, 'rsi': 0.0, 'trend': 'Unknown', 'reason': '데이터 부족'}
AI/libs/database/repository.py (2)

93-96: raise e 대신 raise를 사용하세요

raise e는 예외의 원래 traceback을 손실시킬 수 있습니다. bare raise를 사용하면 전체 스택 트레이스가 유지됩니다.

🐛 수정 제안
     except Exception as e:
         conn.rollback()
         print(f"[Repository][Error] 체결 내역 저장 중 오류 발생: {e}")
-        raise e
+        raise

145-149: execute_valuesRETURNING의 페이지네이션 이슈

execute_values는 기본 page_size(약 100)로 데이터를 나누어 처리합니다. fetch=True와 함께 사용 시:

  1. fetch=True는 내부적으로 fetchall()을 호출하여 결과를 반환합니다
  2. 이후 cursor.fetchall()을 다시 호출하면 빈 결과가 반환될 수 있습니다
  3. 데이터가 page_size를 초과하면 마지막 페이지의 ID만 반환됩니다

page_size를 데이터 크기 이상으로 설정하고, fetch=True의 반환값을 직접 사용하세요.

🐛 수정 제안
-        execute_values(cursor, insert_query, data_to_insert, fetch=True)
-        
-        # RETURNING id 결과 가져오기
-        rows = cursor.fetchall()
-        generated_ids = [row[0] for row in rows]
+        # page_size를 데이터 크기 이상으로 설정하여 단일 페이지로 처리
+        rows = execute_values(
+            cursor, insert_query, data_to_insert, 
+            fetch=True, 
+            page_size=max(len(data_to_insert), 100)
+        )
+        generated_ids = [row[0] for row in rows] if rows else []
🧹 Nitpick comments (15)
AI/modules/finder/evaluator.py (1)

14-18: sys.path 조작 대신 패키지 구조 활용 권장

런타임에 sys.path를 수정하는 방식은 IDE 지원 및 타입 검사에 불리하고, 실행 순서에 따라 예기치 않은 import 실패가 발생할 수 있습니다. 프로젝트 루트에 pyproject.toml 또는 setup.py로 패키지를 설치 가능하게 구성하면 이 코드를 제거할 수 있습니다.

AI/modules/finder/__init__.py (1)

10-14: __all__ 정렬 권장 (Ruff RUF022)

isort 스타일에 따라 __all__을 알파벳순으로 정렬하면 일관성이 향상됩니다. 기능적 문제는 아니므로 선택 사항입니다.

♻️ 정렬 예시
 __all__ = [
+    "evaluate_ticker",
     "get_target_tickers",
     "load_all_tickers_from_db",
-    "evaluate_ticker",
 ]
AI/tests/optimize_hyperparameter.py (6)

28-30: 미사용 변수 current_dir 존재

current_dir가 정의되었지만 사용되지 않습니다. project_root 계산에서 직접 os.path.dirname(__file__)을 다시 호출하고 있습니다.

♻️ 제안 수정
 current_dir = os.path.dirname(os.path.abspath(__file__))
-project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), "../.."))
+project_root = os.path.abspath(os.path.join(current_dir, "../.."))
 sys.path.append(project_root)

52-53: 광범위한 예외 처리 개선 권장

Exception을 포괄적으로 catch하면 예상치 못한 오류가 숨겨질 수 있습니다. 개발/테스트 스크립트이므로 큰 문제는 아니지만, 더 구체적인 예외 타입 지정을 고려해보세요.

♻️ 제안 수정
     try:
         df = loader.load_data(ticker, start_date, end_date)
         if not df.empty and len(df) > 300:
             grouped_data.append(df)
-    except Exception as e:
+    except (ValueError, IOError, KeyError) as e:
         print(f"[Warning] {ticker} 데이터 로드 실패: {e}")

75-84: _build_sequences 성능 최적화 가능

현재 구현은 정확하지만, 대량 데이터 처리 시 리스트 append 후 np.array 변환은 메모리 효율이 낮습니다. NumPy의 stride tricks나 사전 할당을 사용하면 성능이 개선됩니다.

♻️ 최적화된 구현
 def _build_sequences(data: np.ndarray, seq_len: int) -> np.ndarray:
     num_samples = len(data) - seq_len + 1
     if num_samples <= 0:
         return np.array([])
-    
-    X = []
-    for i in range(num_samples):
-        X.append(data[i : i+seq_len])
-        
-    return np.array(X)
+    
+    # 사전 할당으로 메모리 효율 개선
+    X = np.empty((num_samples, seq_len, data.shape[1]), dtype=data.dtype)
+    for i in range(num_samples):
+        X[i] = data[i : i+seq_len]
+    return X

205-207: zip()strict=True 파라미터 추가 권장

Python 3.10+에서 zip()strict=True를 명시하면 길이 불일치 시 즉시 오류를 발생시켜 디버깅이 용이합니다. compute_class_weight가 동일 길이를 보장하지만 명시적으로 지정하는 것이 안전합니다.

♻️ 제안 수정
-        class_weight_dict = {int(c): float(w) for c, w in zip(classes, weights)}
+        class_weight_dict = {int(c): float(w) for c, w in zip(classes, weights, strict=True)}

269-280: return 문을 else 블록으로 이동 고려

try 블록 내 마지막 returnelse 블록으로 이동하면 예외 처리 흐름이 더 명확해집니다. 예외 발생 시와 정상 종료 시의 경로가 분리됩니다.

♻️ 제안 수정
         # 최종 점수: 누적 수익률
         # (옵션) 승률이나 거래 횟수에 따른 가중치를 더 줄 수도 있음
         score = total_profit
-        
-        # 승률 계산 (실제 진입한 거래 기준)
-        # 루프를 다시 돌 필요 없이, 위에서 더할 때 승/패 카운팅 가능하나 간략화
-        # 여기서는 단순 누적 수익을 최우선 지표로 삼음
-        
-        return score

     except Exception as e:
         print(f"[Trial Fail] Error: {e}")
-        # traceback을 보고 싶으면 import traceback; traceback.print_exc() 사용
         return -999.0
+        
+    else:
+        # 승률 계산 (실제 진입한 거래 기준)
+        # 루프를 다시 돌 필요 없이, 위에서 더할 때 승/패 카운팅 가능하나 간략화
+        # 여기서는 단순 누적 수익을 최우선 지표로 삼음
+        return score
         
     finally:

38-42: 하드코딩된 설정값 상수 분리 권장

티커 목록, 날짜 범위 등이 하드코딩되어 있습니다. 개발/테스트 스크립트이므로 큰 문제는 아니지만, 파일 상단에 상수로 분리하거나 설정 파일로 외부화하면 유지보수성이 향상됩니다.

AI/modules/signal/workflows/inference.py (3)

41-41: 불필요한 f-string 접두사 제거

플레이스홀더가 없는 f-string입니다. 일반 문자열로 변경하세요.

♻️ 수정 제안
-        print(f"[Err] 모델 파일이 존재하지 않습니다.")
+        print("[Err] 모델 파일이 존재하지 않습니다.")

94-95: model.build() input_shape가 wrapper와 불일치합니다

TransformerSignalModel.build()(timesteps, features) 형태의 2차원 튜플을 기대합니다. 3차원 (None, timesteps, features)를 전달하면 내부에서 처리는 되지만, 명시적으로 2차원으로 전달하는 것이 더 명확합니다.

♻️ 수정 제안
        # 모델 빌드 (가중치 로드 전 필수)
-       model.build(input_shape=(None, input_tensor.shape[1], input_tensor.shape[2]))
+       model.build(input_shape=(input_tensor.shape[1], input_tensor.shape[2]))

64-113: try 블록 범위가 너무 넓습니다

현재 try 블록이 데이터 전처리부터 모델 로드, 예측까지 모든 로직을 포함하고 있어 디버깅이 어렵습니다. 각 단계별로 try-except를 분리하면 오류 원인 파악이 용이해집니다.

AI/pipelines/daily_routine.py (2)

178-179: zip()strict=True 추가 권장

리포트와 ID의 개수가 일치하지 않을 경우 조용히 데이터가 손실될 수 있습니다. strict=True를 추가하여 불일치 시 명시적 오류가 발생하도록 하세요.

♻️ 수정 제안
         if len(saved_report_ids) == len(report_results):
-            report_map = {r['ticker']: saved_id for r, saved_id in zip(report_results, saved_report_ids)}
+            report_map = {r['ticker']: saved_id for r, saved_id in zip(report_results, saved_report_ids, strict=True)}

70-70: 불필요한 f-string 접두사들

플레이스홀더가 없는 f-string들이 있습니다. 일반 문자열로 변경하세요.

♻️ 수정 제안
-                print(f"   [Skip] 데이터 없음")
+                print("   [Skip] 데이터 없음")
...
-                print(f"   [Skip] 추론 실패")
+                print("   [Skip] 추론 실패")
...
-                print(f"   ...리포트 생성 중...")
+                print("   ...리포트 생성 중...")
...
-    print(f"=== Daily Routine Finished ===\n")
+    print("=== Daily Routine Finished ===\n")

Also applies to: 89-89, 123-123, 191-191

AI/modules/trader/engine.py (2)

46-46: 타입 힌트에 Optional 명시 필요

PEP 484에 따르면 None을 기본값으로 사용할 때 Optional[str] 또는 str | None으로 명시해야 합니다.

♻️ 수정 제안
+from typing import Dict, Any, Optional
+
 class BacktestEngine:
     ...
-    def run(self, model_weights_path: str = None):
+    def run(self, model_weights_path: Optional[str] = None):

71-78: 내부 모델 직접 접근은 캡슐화 위반

model.model.load_weights()는 wrapper의 내부 구현에 의존합니다. .keras 파일이 아닌 .h5 가중치 파일 처리를 위해서라면, wrapper에 별도 메서드를 추가하거나 로드 방식을 통일하는 것이 좋습니다.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 474341c and 3877ab8.

📒 Files selected for processing (11)
  • AI/libs/database/repository.py
  • AI/modules/finder/__init__.py
  • AI/modules/finder/evaluator.py
  • AI/modules/signal/core/features.py
  • AI/modules/signal/models/transformer/wrapper.py
  • AI/modules/signal/workflows/inference.py
  • AI/modules/trader/__init__.py
  • AI/modules/trader/engine.py
  • AI/pipelines/daily_routine.py
  • AI/requirements.txt
  • AI/tests/optimize_hyperparameter.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • AI/modules/signal/core/features.py
  • AI/modules/trader/init.py
🧰 Additional context used
🧬 Code graph analysis (5)
AI/modules/signal/models/transformer/wrapper.py (2)
AI/modules/signal/core/base_model.py (6)
  • BaseSignalModel (13-65)
  • build (26-32)
  • train (35-44)
  • predict (47-55)
  • save (58-60)
  • load (63-65)
AI/modules/signal/models/transformer/architecture.py (1)
  • build_transformer_model (37-81)
AI/pipelines/daily_routine.py (6)
AI/modules/signal/workflows/inference.py (1)
  • run_inference (31-113)
AI/modules/trader/policy.py (1)
  • decide_order (10-91)
AI/modules/analysis/generator.py (2)
  • ReportGenerator (22-80)
  • generate_report (30-80)
AI/modules/collector/market_data.py (1)
  • update_market_data (24-93)
AI/libs/database/repository.py (3)
  • save_executions_to_db (20-99)
  • save_reports_to_db (102-163)
  • get_current_position (165-224)
AI/modules/signal/core/data_loader.py (2)
  • SignalDataLoader (24-89)
  • load_data (30-64)
AI/modules/finder/evaluator.py (2)
AI/libs/database/fetcher.py (1)
  • fetch_ohlcv (10-68)
AI/modules/signal/core/features.py (1)
  • add_technical_indicators (11-69)
AI/libs/database/repository.py (1)
AI/libs/database/connection.py (1)
  • get_db_conn (120-138)
AI/modules/signal/workflows/inference.py (4)
AI/modules/signal/core/data_loader.py (2)
  • SignalDataLoader (24-89)
  • load_data (30-64)
AI/modules/signal/models/__init__.py (1)
  • get_model (12-21)
AI/modules/signal/models/transformer/wrapper.py (3)
  • build (20-48)
  • load (108-113)
  • predict (87-96)
AI/modules/signal/core/base_model.py (3)
  • build (26-32)
  • load (63-65)
  • predict (47-55)
🪛 Ruff (0.14.11)
AI/modules/signal/models/transformer/wrapper.py

29-29: Avoid specifying long messages outside the exception class

(TRY003)


60-60: Avoid specifying long messages outside the exception class

(TRY003)


90-90: Avoid specifying long messages outside the exception class

(TRY003)


111-111: Avoid specifying long messages outside the exception class

(TRY003)

AI/modules/finder/__init__.py

10-14: __all__ is not sorted

Apply an isort-style sorting to __all__

(RUF022)

AI/tests/optimize_hyperparameter.py

52-52: Do not catch blind exception: Exception

(BLE001)


207-207: zip() without an explicit strict= parameter

Add explicit value for parameter strict=

(B905)


275-275: Consider moving this statement to an else block

(TRY300)


277-277: Do not catch blind exception: Exception

(BLE001)

AI/modules/trader/engine.py

46-46: PEP 484 prohibits implicit Optional

Convert to T | None

(RUF013)


73-73: Do not catch blind exception: Exception

(BLE001)


134-134: f-string without any placeholders

Remove extraneous f prefix

(F541)

AI/pipelines/daily_routine.py

27-27: Redefinition of unused save_executions_to_db from line 25: save_executions_to_db redefined here

Remove definition: save_executions_to_db

(F811)


27-27: Redefinition of unused save_reports_to_db from line 25: save_reports_to_db redefined here

Remove definition: save_reports_to_db

(F811)


43-43: Do not catch blind exception: Exception

(BLE001)


51-51: Do not catch blind exception: Exception

(BLE001)


70-70: f-string without any placeholders

Remove extraneous f prefix

(F541)


89-89: f-string without any placeholders

Remove extraneous f prefix

(F541)


123-123: f-string without any placeholders

Remove extraneous f prefix

(F541)


161-161: Do not catch blind exception: Exception

(BLE001)


179-179: zip() without an explicit strict= parameter

Add explicit value for parameter strict=

(B905)


191-191: f-string without any placeholders

Remove extraneous f prefix

(F541)

AI/libs/database/repository.py

96-96: Use raise without specifying exception name

Remove exception name

(TRY201)


154-154: Do not catch blind exception: Exception

(BLE001)

AI/modules/signal/workflows/inference.py

41-41: f-string without any placeholders

Remove extraneous f prefix

(F541)


55-55: Do not catch blind exception: Exception

(BLE001)


107-107: Consider moving this statement to an else block

(TRY300)


109-109: Do not catch blind exception: Exception

(BLE001)

🔇 Additional comments (9)
AI/modules/finder/evaluator.py (1)

43-74: LGTM! 점수 계산 로직이 명확합니다.

정배열, RSI, MACD, 거래량 각 항목별 점수 산정이 잘 구조화되어 있고, 최근 120일 평균 거래량 사용으로 과거 데이터 왜곡을 방지한 점이 좋습니다.

AI/modules/finder/__init__.py (1)

1-8: LGTM! 패키지 초기화 구조가 적절합니다.

상대 import를 사용하여 내부 모듈을 노출하고, __all__로 공개 API를 명시적으로 정의한 점이 좋습니다.

AI/tests/optimize_hyperparameter.py (3)

248-261: Cooldown 로직 구현 적절함

중복 진입 방지를 위한 cooldown 로직이 잘 구현되어 있습니다. last_exit_idx를 활용한 포지션 관리가 현실적인 백테스트 시나리오를 반영합니다.


301-306: Optuna study 설정 적절함

n_jobs=1 설정은 TensorFlow의 GPU 리소스 충돌을 방지하는 올바른 선택입니다. 주석에 언급된 catch_catch=Truecatch_exceptions=True의 오타로 보입니다.

📝 주석 오타 수정
-    # 예외로 죽지 않도록 catch_catch=True 옵션을 고려할 수 있으나,
+    # 예외로 죽지 않도록 catch_exceptions=True 옵션을 고려할 수 있으나,

44-54: SignalDataLoader 초기화의 sequence_length 파라미터는 데이터 로드 단계에서 실제로 사용되지 않습니다.

load_data() 메서드는 데이터베이스에서 원본 데이터를 로드하고 기술적 지표를 추가하며 날짜 인덱스를 정규화하는 역할만 수행하며, sequence_length 파라미터를 사용하지 않습니다. 시퀀스 생성은 objective() 함수 내에서 SEQ_LEN 변수와 함께 _build_sequences() 헬퍼 함수로 독립적으로 처리되므로 시퀀스 길이 불일치 문제는 발생하지 않습니다. 다만, sequence_length 파라미터가 초기화되지만 이 코드 경로에서 사용되지 않는 것은 설계상 혼란을 야기할 수 있습니다.

단, 52번 줄의 except Exception 처리는 너무 광범위하므로 특정 예외 타입(requests.RequestException, pd.errors.DatabaseError 등)으로 좁혀서 처리하는 것이 좋습니다.

AI/modules/signal/models/transformer/wrapper.py (1)

50-82: LGTM: train 메서드의 kwargs 처리가 개선되었습니다

verbosecallbackskwargs.pop()으로 명시적으로 추출하여 중복 전달 문제를 방지했습니다.

AI/libs/database/repository.py (1)

165-224: LGTM: get_current_position 함수의 포지션 계산 로직

이동평균법을 사용한 평단가 계산과 매수/매도 로직이 올바르게 구현되어 있습니다. 커넥션과 커서도 적절히 정리됩니다.

AI/modules/trader/engine.py (2)

83-129: LGTM: Walk-Forward Validation 구현

매 시점마다 과거 데이터로만 스케일러를 학습하여 미래 정보 유출을 방지하는 로직이 올바르게 구현되어 있습니다. 이전 리뷰에서 지적된 데이터 리크 문제가 해결되었습니다.


144-176: LGTM: 주문 체결 및 평단가 계산 로직

이동평균법을 사용한 평단가 갱신, 수수료 처리, 실현 손익 계산이 올바르게 구현되어 있습니다.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment on lines 98 to 106
def save(self, filepath: str):
"""모델 저장"""
if self.model is None:
print("저장할 모델이 없습니다.")
return

os.makedirs(os.path.dirname(filepath), exist_ok=True)
self.model.save(filepath)
# print(f"모델 저장 완료: {filepath}")
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

save 메서드에서 빈 디렉토리 경로 처리 필요

filepath가 디렉토리 없이 파일명만 주어질 경우 os.path.dirname(filepath)는 빈 문자열을 반환하고, os.makedirs("")는 오류를 발생시킬 수 있습니다.

🐛 수정 제안
     def save(self, filepath: str):
         """모델 저장"""
         if self.model is None:
             print("저장할 모델이 없습니다.")
             return
         
-        os.makedirs(os.path.dirname(filepath), exist_ok=True)
+        dir_path = os.path.dirname(filepath)
+        if dir_path:
+            os.makedirs(dir_path, exist_ok=True)
         self.model.save(filepath)
🤖 Prompt for AI Agents
In `@AI/modules/signal/models/transformer/wrapper.py` around lines 98 - 106, The
save method may call os.makedirs on an empty string when filepath contains no
directory; update AI/modules/signal/models/transformer/wrapper.py's save(self,
filepath: str) to compute dirpath = os.path.dirname(filepath) and only call
os.makedirs(dirpath, exist_ok=True) if dirpath is non-empty (or default to
'.'/os.getcwd()), then proceed to call self.model.save(filepath); ensure the
no-model early return remains.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (7)
AI/modules/trader/engine.py (4)

46-46: PEP 484 타입 힌트 수정 필요

model_weights_path 파라미터의 기본값이 None이므로, 타입 힌트를 명시적으로 Optional[str] 또는 str | None으로 지정해야 합니다.

♻️ 수정 제안
-    def run(self, model_weights_path: str = None):
+    def run(self, model_weights_path: str | None = None):

또는 from typing import Optional을 사용:

-    def run(self, model_weights_path: str = None):
+    def run(self, model_weights_path: Optional[str] = None):

71-78: 포괄적 예외 처리(except Exception) 지양 권장

모든 예외를 무차별적으로 잡으면 예상치 못한 오류(예: 메모리 부족, 권한 문제)가 숨겨질 수 있습니다. 구체적인 예외 타입을 명시하거나, 최소한 로깅을 추가하여 디버깅을 용이하게 하세요.

♻️ 수정 제안
             try:
                 model.load(model_weights_path)
-            except Exception:
+            except (ValueError, OSError) as e:
+                # .h5 파일인 경우 구조 빌드 후 가중치 로드
+                print(f"[Info] 전체 모델 로드 실패, 가중치만 로드 시도: {e}")
                 # .h5 파일인 경우 구조 빌드 후 가중치 로드
                 # (입력 형태는 (60, features)로 가정)
                 features = df.select_dtypes(include=[np.number]).columns.tolist()
                 model.build((60, len(features)))
                 model.model.load_weights(model_weights_path)

134-134: 불필요한 f-string 접두사 제거

플레이스홀더가 없는 문자열에서 f 접두사를 제거하세요.

♻️ 수정 제안
-            print(f"=== 백테스트 종료 ===")
+            print("=== 백테스트 종료 ===")

178-201: run_id 고유성 고려

현재 run_id"backtest_run"으로 고정되어 있어, 여러 백테스트 실행 결과를 DB에 저장할 경우 구분이 어려울 수 있습니다. 추후 DB 저장 기능을 활성화할 계획이라면, 타임스탬프나 UUID를 사용한 고유 식별자 생성을 고려해보세요.

💡 예시
import uuid
from datetime import datetime

# __init__에서 설정
self.run_id = f"backtest_{self.ticker}_{datetime.now().strftime('%Y%m%d_%H%M%S')}_{uuid.uuid4().hex[:8]}"
AI/modules/signal/workflows/inference.py (3)

12-12: 사용되지 않는 joblib import

joblib이 import되었지만 파일 내에서 사용되지 않습니다. 불필요한 import를 제거하세요.

-import joblib

41-41: 플레이스홀더 없는 f-string

f-string에 변수 치환이 없으므로 일반 문자열로 변경하세요.

-        print(f"[Err] 모델 파일이 존재하지 않습니다.")
+        print("[Err] 모델 파일이 존재하지 않습니다.")

115-122: CLI에서 오류 시 exit code 반환 고려

run_inference-1.0을 반환할 때 스크립트가 비정상 종료임을 나타내는 exit code를 반환하면 CI/CD 파이프라인 통합 시 유용합니다.

♻️ 선택적 개선안
     args = parser.parse_args()
     
-    run_inference(args.ticker, args.model)
+    result = run_inference(args.ticker, args.model)
+    if result < 0:
+        sys.exit(1)
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3877ab8 and 9edddb9.

📒 Files selected for processing (2)
  • AI/modules/signal/workflows/inference.py
  • AI/modules/trader/engine.py
🧰 Additional context used
🧬 Code graph analysis (2)
AI/modules/signal/workflows/inference.py (4)
AI/modules/signal/core/data_loader.py (2)
  • SignalDataLoader (24-89)
  • load_data (30-64)
AI/modules/signal/models/__init__.py (1)
  • get_model (12-21)
AI/modules/signal/models/transformer/wrapper.py (3)
  • build (20-48)
  • load (108-113)
  • predict (87-96)
AI/modules/signal/core/base_model.py (3)
  • build (26-32)
  • load (63-65)
  • predict (47-55)
AI/modules/trader/engine.py (6)
AI/modules/signal/core/data_loader.py (2)
  • SignalDataLoader (24-89)
  • load_data (30-64)
AI/modules/signal/models/__init__.py (1)
  • get_model (12-21)
AI/modules/trader/policy.py (1)
  • decide_order (10-91)
AI/libs/database/repository.py (1)
  • save_executions_to_db (20-99)
AI/modules/signal/models/transformer/wrapper.py (3)
  • load (108-113)
  • build (20-48)
  • predict (87-96)
AI/modules/signal/core/base_model.py (3)
  • load (63-65)
  • build (26-32)
  • predict (47-55)
🪛 Ruff (0.14.11)
AI/modules/signal/workflows/inference.py

41-41: f-string without any placeholders

Remove extraneous f prefix

(F541)


55-55: Do not catch blind exception: Exception

(BLE001)


107-107: Consider moving this statement to an else block

(TRY300)


109-109: Do not catch blind exception: Exception

(BLE001)

AI/modules/trader/engine.py

46-46: PEP 484 prohibits implicit Optional

Convert to T | None

(RUF013)


73-73: Do not catch blind exception: Exception

(BLE001)


134-134: f-string without any placeholders

Remove extraneous f prefix

(F541)

🔇 Additional comments (4)
AI/modules/trader/engine.py (2)

27-45: LGTM!

초기화 로직이 올바르게 구현되었습니다. 상태 변수들의 초기값이 적절하게 설정되어 있습니다.


144-176: LGTM!

주문 집행 로직이 올바르게 구현되었습니다. 이동평균법을 사용한 평균 단가 계산, 수수료 처리, 실현 손익 계산이 적절합니다.

AI/modules/signal/workflows/inference.py (2)

46-63: LGTM!

데이터 로딩 로직이 적절합니다. 300일 버퍼로 기술적 지표 계산을 위한 충분한 여유를 확보하고, 최소 60행 검증으로 시퀀스 길이 요구사항을 충족합니다.


94-103: 모델 로드 및 예측 로직 검증 완료

이전 리뷰에서 지적된 load_weights()load() 변경 및 verbose 파라미터 제거가 반영되었습니다. wrapper.pypredict() 메서드가 내부적으로 verbose=0을 처리하므로 현재 구현이 올바릅니다.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment on lines 86 to 92
model = get_model(model_type, {
# 입력 형태를 명시하여 모델 구조를 확정합니다.
"input_shape": (input_tensor.shape[1], input_tensor.shape[2]),
# 아래 파라미터는 로드 시 덮어써지거나, build용으로 사용됨
"head_size": 256, "num_heads": 4, "ff_dim": 4,
"num_blocks": 4, "mlp_units": [128], "dropout": 0.1
})
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# train.py에서 모델 설정값 확인
echo "=== train.py 모델 설정 확인 ==="
rg -n "head_size|num_heads|ff_dim|num_blocks|mlp_units|dropout" AI/modules/signal/workflows/train.py

echo ""
echo "=== 모델 config 파일 존재 여부 확인 ==="
find AI/data/weights -type f -name "*config*" 2>/dev/null || echo "config 파일 없음"

echo ""
echo "=== AI/modules/signal/workflows 디렉토리 구조 확인 ==="
ls -la AI/modules/signal/workflows/ 2>/dev/null

echo ""
echo "=== weights 디렉토리 구조 확인 ==="
ls -la AI/data/weights/ 2>/dev/null || echo "weights 디렉토리 없음"

Repository: SISC-IT/sisc-web

Length of output: 674


🏁 Script executed:

#!/bin/bash
# train.py의 전체 모델 설정 구간 확인 (라인 90-110)
echo "=== train.py 모델 설정 전체 (라인 90-110) ==="
sed -n '90,110p' AI/modules/signal/workflows/train.py

echo ""
echo "=== train.py에서 ff_dim, num_blocks, mlp_units 검색 ==="
rg -n "ff_dim|num_blocks|mlp_units" AI/modules/signal/workflows/train.py -A 2 -B 2

echo ""
echo "=== inference.py의 설정값 확인 (라인 80-95) ==="
sed -n '80,95p' AI/modules/signal/workflows/inference.py

Repository: SISC-IT/sisc-web

Length of output: 1467


학습 시 사용된 하이퍼파라미터와 불일치 - 긴급 수정 필요

하드코딩된 설정값이 train.py의 설정과 불일치합니다:

  • dropout: 0.4 (train.py) vs 0.1 (inference.py) ← 중요한 불일치로 예측 결과의 정확성 저하 초래
  • ff_dim, num_blocks, mlp_units: train.py의 모델 설정에는 이 파라미터들이 없음. 이들은 inference.py에만 하드코딩되어 있으며, 코드 주석의 "로드 시 덮어써진다"는 설명과 달리 메타데이터나 설정 파일에서 로드된다는 근거 없음

모델 설정을 train.py에서와 동일하게 통일하거나, 학습된 모델의 설정을 저장 시 함께 저장한 후 추론 시 로드하는 방식으로 변경하세요.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@AI/pipelines/daily_routine.py`:
- Around line 171-172: The P&L fields are hardcoded to zero; compute and
populate them instead: when processing a SELL action, set "pnl_realized" =
(current_price - my_avg_price) * qty (use the existing variables current_price,
my_avg_price, qty) and for remaining positions set "pnl_unrealized" =
(current_price - next_avg_price) * next_qty (use next_avg_price and next_qty),
ensuring these assignments replace the constant 0 values where the result dict
with keys "pnl_realized" and "pnl_unrealized" is created.
♻️ Duplicate comments (1)
AI/pipelines/daily_routine.py (1)

192-198: 리포트 ID 매핑의 순서 보장 문제

execute_valuesRETURNING의 조합은 PostgreSQL에서 삽입 순서대로 ID를 반환하지 않을 수 있습니다. 이로 인해 report_resultssaved_report_ids의 순서가 일치하지 않아 잘못된 ticker-report_id 매핑이 발생할 수 있습니다.

안전한 해결 방법:

  1. repository.pysave_reports_to_db에서 RETURNING ticker, id를 사용하여 {ticker: id} 딕셔너리를 직접 반환
  2. 또는 개별 INSERT 후 ID 수집

추가로 zip()strict=True 파라미터 추가를 권장합니다.

♻️ zip strict 파라미터 추가
-            report_map = {r['ticker']: saved_id for r, saved_id in zip(report_results, saved_report_ids)}
+            report_map = {r['ticker']: saved_id for r, saved_id in zip(report_results, saved_report_ids, strict=True)}
🧹 Nitpick comments (7)
AI/modules/signal/workflows/inference.py (3)

12-12: 사용되지 않는 import를 제거하세요.

joblib이 import되었지만 파일 내에서 사용되지 않습니다.

♻️ 수정 제안
-import joblib

41-41: 불필요한 f-string prefix를 제거하세요.

placeholder가 없는 f-string입니다.

♻️ 수정 제안
-        print(f"[Err] 모델 파일이 존재하지 않습니다.")
+        print("[Err] 모델 파일이 존재하지 않습니다.")

83-98: model.build()와 config 파라미터가 불필요합니다.

wrapper.pyload() 메서드는 tf.keras.models.load_model()을 사용하여 **전체 모델(아키텍처 + 가중치)**을 로드합니다. 따라서:

  1. get_model()에 전달된 config 파라미터 (head_size, num_heads 등)는 사용되지 않음
  2. model.build() 호출로 생성된 아키텍처가 model.load()에서 즉시 덮어써짐

현재 코드는 동작하지만 불필요한 연산이 수행되며, 주석의 설명("로드 시 덮어써진다")과 실제 동작이 다릅니다.

♻️ 수정 제안
        # 4. 모델 로드 및 구조 빌드
-       # Config는 load_weights 시 자동 적용되거나, 저장된 포맷에 따라 다르나
-       # 여기서는 get_model로 껍데기를 만들고 가중치를 입히는 방식을 사용
-       model = get_model(model_type, {
-           # 입력 형태를 명시하여 모델 구조를 확정합니다.
-           "input_shape": (input_tensor.shape[1], input_tensor.shape[2]),
-           # 아래 파라미터는 로드 시 덮어써지거나, build용으로 사용됨
-           "head_size": 256, "num_heads": 4, "ff_dim": 4,
-           "num_blocks": 4, "mlp_units": [128], "dropout": 0.4
-       })
-       
-       # 모델 빌드 (가중치 로드 전 필수)
-       model.build(input_shape=(None, input_tensor.shape[1], input_tensor.shape[2]))
-       
-       # 가중치 로드
+       # 저장된 모델 로드 (load_model은 아키텍처 + 가중치 전체 로드)
+       model = get_model(model_type, {})  # 빈 config로 인스턴스 생성
        model.load(weights_path)
AI/pipelines/daily_routine.py (4)

72-72: 하드코딩된 start_date를 설정 가능하게 변경 권장

start_date="2023-01-01"가 하드코딩되어 있습니다. 모델이 더 긴 학습 기간이 필요하거나 백테스트 범위를 조정하려면 유연성이 제한됩니다. 함수 파라미터나 설정 파일로 분리하는 것을 고려하세요.

♻️ 개선 제안
-def run_daily_pipeline(target_tickers: list, mode: str = "simulation", enable_xai: bool = True):
+def run_daily_pipeline(target_tickers: list, mode: str = "simulation", enable_xai: bool = True, lookback_start: str = "2023-01-01"):
-            df = loader.load_data(ticker, start_date="2023-01-01", end_date=today_str)
+            df = loader.load_data(ticker, start_date=lookback_start, end_date=today_str)

74-74: 불필요한 f-string 접두사 제거

플레이스홀더가 없는 f-string이 사용되었습니다. 일반 문자열로 변경하세요.

♻️ 수정 제안
-                print(f"   [Skip] 데이터 없음")
+                print("   [Skip] 데이터 없음")
-                print(f"   [Skip] 추론 실패")
+                print("   [Skip] 추론 실패")

Also applies to: 88-88


141-141: 불필요한 f-string 접두사

♻️ 수정 제안
-                print(f"   ...리포트 생성 중...")
+                print("   ...리포트 생성 중...")

206-206: 불필요한 f-string 접두사

♻️ 수정 제안
-    print(f"=== Daily Routine Finished ===\n")
+    print("=== Daily Routine Finished ===\n")
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9edddb9 and d2be6a0.

📒 Files selected for processing (2)
  • AI/modules/signal/workflows/inference.py
  • AI/pipelines/daily_routine.py
🧰 Additional context used
🧬 Code graph analysis (2)
AI/pipelines/daily_routine.py (6)
AI/modules/signal/workflows/inference.py (1)
  • run_inference (31-113)
AI/modules/trader/policy.py (1)
  • decide_order (10-91)
AI/modules/analysis/generator.py (2)
  • ReportGenerator (22-80)
  • generate_report (30-80)
AI/modules/collector/market_data.py (1)
  • update_market_data (24-93)
AI/libs/database/repository.py (3)
  • save_executions_to_db (20-99)
  • save_reports_to_db (102-163)
  • get_current_position (165-224)
AI/modules/signal/core/data_loader.py (2)
  • SignalDataLoader (24-89)
  • load_data (30-64)
AI/modules/signal/workflows/inference.py (4)
AI/modules/signal/core/data_loader.py (2)
  • SignalDataLoader (24-89)
  • load_data (30-64)
AI/modules/signal/models/__init__.py (1)
  • get_model (12-21)
AI/modules/signal/models/transformer/wrapper.py (3)
  • build (20-48)
  • load (108-113)
  • predict (87-96)
AI/modules/signal/core/base_model.py (3)
  • build (26-32)
  • load (63-65)
  • predict (47-55)
🪛 Ruff (0.14.11)
AI/pipelines/daily_routine.py

47-47: Do not catch blind exception: Exception

(BLE001)


55-55: Do not catch blind exception: Exception

(BLE001)


74-74: f-string without any placeholders

Remove extraneous f prefix

(F541)


88-88: f-string without any placeholders

Remove extraneous f prefix

(F541)


141-141: f-string without any placeholders

Remove extraneous f prefix

(F541)


176-176: Do not catch blind exception: Exception

(BLE001)


194-194: zip() without an explicit strict= parameter

Add explicit value for parameter strict=

(B905)


206-206: f-string without any placeholders

Remove extraneous f prefix

(F541)

AI/modules/signal/workflows/inference.py

41-41: f-string without any placeholders

Remove extraneous f prefix

(F541)


55-55: Do not catch blind exception: Exception

(BLE001)


107-107: Consider moving this statement to an else block

(TRY300)


109-109: Do not catch blind exception: Exception

(BLE001)

🔇 Additional comments (9)
AI/modules/signal/workflows/inference.py (2)

46-62: LGTM!

300일 데이터를 조회하여 SMA200 등 보조지표 계산 여유분을 확보하고, 최소 60일 데이터 검증을 수행하는 로직이 적절합니다.


115-122: LGTM!

CLI 인터페이스가 명확하게 구성되어 있습니다. 필수 인자(--ticker)와 기본값이 있는 선택 인자(--model)의 구분이 적절합니다.

AI/pipelines/daily_routine.py (7)

15-19: 프로젝트 루트 경로 추가 방식은 적절합니다.

스케줄러(Crontab, Airflow 등)에서 독립적으로 실행될 스크립트에서 절대 경로 import를 위한 sys.path 조작은 일반적인 패턴입니다.


44-49: XAI 초기화 실패 시 graceful degradation 처리가 적절합니다.

초기화 실패 시 enable_xai = False로 설정하여 파이프라인이 계속 진행되도록 하는 것은 좋은 복원력 패턴입니다. 다만, 정확한 원인 파악을 위해 예외 유형을 로깅하는 것을 고려해 보세요.


51-57: 데이터 수집 실패 시 기존 데이터로 진행하는 복원력 패턴이 적절합니다.

네트워크 장애 등으로 데이터 수집이 실패해도 기존 데이터로 분석을 진행하는 것은 일일 파이프라인에서 합리적인 전략입니다.


91-106: DB 기반 잔고 조회 및 매매 정책 결정 로직이 적절합니다.

get_current_position으로 DB에서 현재 포지션을 조회하고, decide_order로 정책 결정을 위임하는 구조가 깔끔합니다. 종목별 독립 예산(10M) 할당 방식은 리스크 분산 측면에서 합리적입니다.


110-137: 거래 후 상태 계산 로직이 올바르게 구현되었습니다.

이전 리뷰에서 지적된 cash_after, position_qty, avg_price 계산 오류가 수정되었습니다:

  • BUY: 가중평균 평단가 계산 적용
  • SELL: 부분 매도 시 평단가 유지, 전량 매도 시 초기화
  • HOLD: 변동 없음

176-180: 예외 발생 시 다음 종목으로 진행하는 복원력 패턴이 적절합니다.

단일 종목 처리 실패가 전체 파이프라인을 중단시키지 않습니다. 디버깅이 필요한 경우 traceback.print_exc() 주석 해제를 고려하세요.


208-217: CLI 인터페이스가 잘 설계되었습니다.

argparse를 활용한 명령줄 인터페이스가 명확하고, 기본값 설정 및 옵션 처리가 적절합니다. 스케줄러에서 호출하기 용이한 구조입니다.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Copy link

@coderabbitai coderabbitai bot left a 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/libs/llm/ollama.py`:
- Line 1: The file AI/libs/llm/ollama.py contains a leading BOM (the ''
character) at the start; remove the BOM so the file is UTF-8 without BOM by
re-saving the file in UTF-8 without BOM (or strip the first byte), ensure the
module import/name (ollama.py) has no hidden characters at the top, and run a
quick grep/validation to confirm the BOM is gone from the file header.
- Around line 44-49: The get_health method's except block has inconsistent
indentation for the "return False" line; open the get_health function and align
"return False" with the "except Exception:" block (remove the extra leading
space) so the return is at the same indentation level as the except body in the
get_health method.
- Around line 35-42: The requests.post call in the Ollama client must include a
timeout to avoid hanging and the exception handling should use the project's
logger instead of print; update the requests.post(...) invocation to pass a
sensible timeout value (e.g., timeout=10) and replace the print in the except
block with a logging call (create or use a module logger via
logging.getLogger(__name__)) and log the error with context and exc_info=True;
ensure you still return "" on failure and keep the existing
response.raise_for_status() and result parsing logic intact.

In `@AI/modules/signal/models/transformer/wrapper.py`:
- Around line 62-80: The current fit wrapper ignores config for verbose and
risks passing validation_data twice; change the verbose assignment to pop from
kwargs with fallback to self.config (e.g. verbose = int(kwargs.pop("verbose",
self.config.get("verbose", 1)))) mirroring epochs/batch_size behavior, and pop
any provided validation_data from kwargs before building the local
validation_data tuple (so remove/consume kwargs["validation_data"] if present)
to avoid duplicate keyword when calling self.model.fit; keep callbacks being
popped as is and ensure the final self.model.fit call uses the local
validation_data, epochs, batch_size, callbacks, verbose, **kwargs.

In `@AI/modules/trader/policy.py`:
- Around line 70-83: The buy branch lacks validation for current_price before
dividing; in the block that computes invest_amount and buy_qty (references:
BUY_THRESHOLD branch, variables current_price, MAX_INVEST_RATIO, invest_amount,
buy_qty, position_qty, action, qty, reason), add a guard that treats
current_price <= 0 as invalid: log or set a clear reason (e.g., "유효하지 않은 현재가"),
do not perform the division, and skip setting action/qty; only compute buy_qty
when current_price > 0 and handle the zero-buy_qty case as already implemented.

In `@AI/pipelines/daily_routine.py`:
- Around line 193-196: report_results is being passed as undefined reports_tuple
to save_reports_to_db; transform report_results into the List[Tuple] shape
expected by save_reports_to_db (each tuple: (ticker, signal, price, date,
report_text)) and pass that variable instead. Locate the block where
report_results is checked (if report_results:) and create a new variable (e.g.,
reports_tuples or report_tuples) by mapping each report in report_results to the
tuple format required, then call save_reports_to_db with that tuple list
(save_reports_to_db(report_tuples)) ensuring the tuple fields match the
repository.save_reports_to_db signature.
♻️ Duplicate comments (2)
AI/pipelines/daily_routine.py (1)

196-196: 리포트 ID 매핑의 순서 의존성 위험 (기존 리뷰 참고)

execute_valuesRETURNING의 조합은 PostgreSQL에서 삽입 순서대로 ID를 반환하지 않을 수 있습니다. 더 안전한 방법은 save_reports_to_db에서 RETURNING ticker, id를 사용하여 {ticker: id} 딕셔너리를 직접 반환하도록 수정하는 것입니다.

현재 구조에서는 zip(..., strict=True)를 사용하여 최소한 길이 불일치 시 예외를 발생시키도록 하세요.

AI/modules/signal/models/transformer/wrapper.py (1)

98-105: 디렉터리 없는 filepath에서 os.makedirs("") 예외가 발생합니다.

파일명만 전달되면 os.path.dirname(filepath)가 빈 문자열이 되어 에러가 납니다.

🐛 수정 제안
-        os.makedirs(os.path.dirname(filepath), exist_ok=True)
+        dir_path = os.path.dirname(filepath)
+        if dir_path:
+            os.makedirs(dir_path, exist_ok=True)
🧹 Nitpick comments (6)
AI/libs/llm/ollama.py (1)

8-9: 사용되지 않는 json import

json 모듈이 import되어 있지만 실제로 사용되지 않습니다. response.json()은 requests 라이브러리의 메서드입니다.

♻️ 권장 수정
 import requests
-import json
 from typing import Optional
AI/pipelines/daily_routine.py (3)

41-49: XAI 초기화 오류 처리 개선 권장

현재 Exception을 광범위하게 잡고 있습니다. 더 구체적인 예외 처리가 디버깅에 도움이 됩니다.

♻️ 제안된 개선
     if enable_xai:
         try:
             xai_generator = ReportGenerator(use_gpu_llm=True)
             print("0. XAI 리포트 생성기 초기화 완료 (Groq)")
-        except Exception as e:
+        except (ConnectionError, RuntimeError) as e:
             print(f"[Warning] XAI 초기화 실패: {e}. 리포트 생성을 건너뜁니다.")
             enable_xai = False

74-74: 불필요한 f-string 접두사 제거

정적 분석 도구(Ruff F541)에서 플레이스홀더 없는 f-string을 감지했습니다.

♻️ 제안된 수정
-                print(f"   [Skip] 데이터 없음")
+                print("   [Skip] 데이터 없음")

동일한 패턴이 라인 88, 150, 209에도 있습니다.


185-188: 예외 발생 시 트레이스백 로깅 활성화 권장

현재 트레이스백이 주석 처리되어 있어 운영 환경에서 디버깅이 어려울 수 있습니다. 로그 레벨에 따라 조건부로 출력하는 것을 고려하세요.

♻️ 제안된 개선
         except Exception as e:
             print(f"   [Error] {ticker} 처리 중 에러: {e}")
-            # import traceback
-            # traceback.print_exc()
+            import traceback
+            traceback.print_exc()
AI/modules/trader/policy.py (2)

10-18: 사용되지 않는 매개변수: ticker, total_asset

정적 분석에서 tickertotal_asset 매개변수가 함수 본문에서 사용되지 않는 것으로 감지되었습니다. 향후 확장(예: 종목별 전략 분기, 포트폴리오 비중 기반 포지션 사이징)을 위해 의도적으로 남겨둔 것이라면 _ prefix를 붙이거나 주석을 추가하는 것이 좋습니다.

♻️ 제안하는 수정안
 def decide_order(
-    ticker: str,
+    _ticker: str,  # 향후 종목별 전략 분기용
     score: float,
     current_price: float,
     cash: float,
     position_qty: int,
     avg_price: float,
-    total_asset: float
+    _total_asset: float  # 향후 포트폴리오 비중 계산용
 ) -> Tuple[str, int, str]:

35-40: 정책 상수를 모듈 수준으로 이동 고려

현재 상수들이 함수 내부에 정의되어 있어 테스트나 전략 변경 시 유연성이 떨어집니다. 모듈 수준으로 이동하면 설정 주입이나 전략 변경이 용이해집니다.

♻️ 제안하는 구조
# 모듈 수준 상수 (또는 config에서 로드)
BUY_THRESHOLD = 0.65
SELL_THRESHOLD = 0.40
STOP_LOSS_PCT = 0.05
TAKE_PROFIT_PCT = 0.10
MAX_INVEST_RATIO = 0.95

def decide_order(...):
    # 함수 내부에서 상수 재정의 불필요
    ...
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d2be6a0 and beebf78.

📒 Files selected for processing (6)
  • AI/config/.gitkeep
  • AI/data/weights/transformer/.gitkeep
  • AI/libs/llm/ollama.py
  • AI/modules/signal/models/transformer/wrapper.py
  • AI/modules/trader/policy.py
  • AI/pipelines/daily_routine.py
🧰 Additional context used
🧬 Code graph analysis (2)
AI/modules/signal/models/transformer/wrapper.py (2)
AI/modules/signal/core/base_model.py (6)
  • BaseSignalModel (13-65)
  • build (26-32)
  • train (35-44)
  • predict (47-55)
  • save (58-60)
  • load (63-65)
AI/modules/signal/models/transformer/architecture.py (1)
  • build_transformer_model (37-81)
AI/pipelines/daily_routine.py (8)
AI/modules/signal/workflows/inference.py (1)
  • run_inference (31-113)
AI/modules/trader/policy.py (1)
  • decide_order (10-97)
AI/modules/analysis/generator.py (2)
  • ReportGenerator (22-80)
  • generate_report (30-80)
AI/modules/collector/market_data.py (1)
  • update_market_data (24-93)
AI/libs/database/repository.py (3)
  • save_executions_to_db (20-99)
  • save_reports_to_db (102-163)
  • get_current_position (165-224)
AI/modules/signal/core/data_loader.py (2)
  • SignalDataLoader (24-89)
  • load_data (30-64)
frontend/src/utils/TimeUtils.js (1)
  • now (24-24)
frontend/src/pages/BackTest.jsx (1)
  • tickers (17-17)
🪛 Ruff (0.14.11)
AI/modules/signal/models/transformer/wrapper.py

29-29: Avoid specifying long messages outside the exception class

(TRY003)


60-60: Avoid specifying long messages outside the exception class

(TRY003)


90-90: Avoid specifying long messages outside the exception class

(TRY003)


111-111: Avoid specifying long messages outside the exception class

(TRY003)

AI/pipelines/daily_routine.py

47-47: Do not catch blind exception: Exception

(BLE001)


55-55: Do not catch blind exception: Exception

(BLE001)


74-74: f-string without any placeholders

Remove extraneous f prefix

(F541)


88-88: f-string without any placeholders

Remove extraneous f prefix

(F541)


150-150: f-string without any placeholders

Remove extraneous f prefix

(F541)


185-185: Do not catch blind exception: Exception

(BLE001)


196-196: Undefined name reports_tuple

(F821)


209-209: f-string without any placeholders

Remove extraneous f prefix

(F541)

AI/modules/trader/policy.py

11-11: Unused function argument: ticker

(ARG001)


17-17: Unused function argument: total_asset

(ARG001)

AI/libs/llm/ollama.py

36-36: Probable use of requests call without timeout

(S113)


40-40: Do not catch blind exception: Exception

(BLE001)


47-47: Consider moving this statement to an else block

(TRY300)


48-48: Do not catch blind exception: Exception

(BLE001)

🔇 Additional comments (11)
AI/libs/llm/ollama.py (1)

13-16: LGTM!

클래스 정의 및 초기화 로직이 적절합니다. 기본값 설정이 로컬 Ollama 환경에 적합합니다.

AI/pipelines/daily_routine.py (4)

1-27: LGTM! 모듈 구조와 임포트가 잘 정리되어 있습니다.

이전 리뷰에서 지적된 중복 임포트 문제가 해결되었습니다. 프로젝트 루트 경로 설정 및 모듈 임포트가 명확합니다.


110-144: 거래 후 상태 계산 및 P&L 로직 LGTM!

이전 리뷰에서 지적된 문제들이 올바르게 수정되었습니다:

  • next_cash, next_qty, next_avg_price 변수로 거래 후 상태 정확히 계산
  • 평단가 갱신 로직 (이동평균법) 올바름
  • 실현 손익 (pnl_realized)과 미실현 손익 (pnl_unrealized) 계산 추가됨

164-183: 실행 결과 데이터 구조 LGTM!

repository.pysave_executions_to_db가 요구하는 모든 필드가 포함되어 있으며, next_ 변수를 사용하여 거래 후 상태가 올바르게 기록됩니다.


211-220: CLI 진입점 LGTM!

argparse를 사용한 명령줄 인터페이스가 잘 구성되어 있습니다. --tickers, --mode, --no-xai 옵션이 명확합니다.

AI/modules/trader/policy.py (2)

46-65: LGTM! - avg_price 0 나누기 방어 로직 적용됨

이전 리뷰에서 제기된 avg_price가 0일 때 발생할 수 있는 ZeroDivisionError 문제가 Lines 48-51에서 적절히 처리되었습니다. avg_price <= 0인 경우 리스크 관리를 스킵하고 HOLD를 반환하는 방어 로직이 올바르게 구현되어 있습니다.


85-97: LGTM!

매도 조건 및 관망 로직이 명확하게 구현되어 있습니다. 포지션이 없을 때 매도 신호가 발생하는 경우도 적절히 처리됩니다.

AI/modules/signal/models/transformer/wrapper.py (4)

15-18: 초기화 로직이 간결하고 명확합니다.

config 전달과 모델명 설정이 기본 동작을 깔끔하게 캡슐화합니다.


20-48: 빌드/컴파일 흐름이 일관적입니다.

입력 차원 정규화와 컴파일 파라미터 구성이 명확합니다.


87-96: 입력 차원 보정 후 예측 흐름이 명확합니다.

2D 입력을 배치 차원으로 확장하는 처리와 예측 호출이 자연스럽습니다.


108-113: 모델 로드 경로 검증이 명확합니다.

파일 존재 확인 후 로드하는 흐름이 안전합니다.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment on lines +35 to +42
try:
response = requests.post(url, json=payload)
response.raise_for_status()
result = response.json()
return result.get("response", "")
except Exception as e:
print(f"[OllamaClient][Error] 텍스트 생성 실패: {e}")
return ""
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

requests.post에 timeout 추가 및 로깅 개선 필요

  1. Line 36: requests.post 호출에 timeout이 없어 LLM 서버 응답 지연 시 무한 대기 상태가 발생할 수 있습니다.
  2. Line 41: print 대신 프로젝트 로깅 표준(예: logging 모듈)을 사용하는 것이 운영 환경에서 적합합니다.
🔧 권장 수정
+import logging
+
+logger = logging.getLogger(__name__)
+
 # ... 클래스 내부 ...
 
         try:
-            response = requests.post(url, json=payload)
+            response = requests.post(url, json=payload, timeout=60)
             response.raise_for_status()
             result = response.json()
             return result.get("response", "")
         except Exception as e:
-            print(f"[OllamaClient][Error] 텍스트 생성 실패: {e}")
+            logger.error(f"[OllamaClient] 텍스트 생성 실패: {e}")
             return ""
🧰 Tools
🪛 Ruff (0.14.11)

36-36: Probable use of requests call without timeout

(S113)


40-40: Do not catch blind exception: Exception

(BLE001)

🤖 Prompt for AI Agents
In `@AI/libs/llm/ollama.py` around lines 35 - 42, The requests.post call in the
Ollama client must include a timeout to avoid hanging and the exception handling
should use the project's logger instead of print; update the requests.post(...)
invocation to pass a sensible timeout value (e.g., timeout=10) and replace the
print in the except block with a logging call (create or use a module logger via
logging.getLogger(__name__)) and log the error with context and exc_info=True;
ensure you still return "" on failure and keep the existing
response.raise_for_status() and result parsing logic intact.

Comment on lines +62 to +80
# ✅ 호출자가 주면 우선, 없으면 config, 없으면 default
epochs = int(kwargs.pop("epochs", self.config.get("epochs", 50)))
batch_size = int(kwargs.pop("batch_size", self.config.get("batch_size", 32)))
verbose = int(kwargs.pop("verbose", 1))

# callbacks는 pop으로 빼서 중복 전달 방지
callbacks = kwargs.pop("callbacks", [])

# validation_data는 (X_val, y_val)이 둘 다 있을 때만
validation_data = (X_val, y_val) if (X_val is not None and y_val is not None) else None

history = self.model.fit(
X_train, y_train,
validation_data=validation_data,
epochs=epochs,
batch_size=batch_size,
callbacks=callbacks,
verbose=verbose,
**kwargs
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, let's find and read the wrapper.py file
find . -path "*/signal/models/transformer/wrapper.py" -type f

Repository: SISC-IT/sisc-web

Length of output: 109


🏁 Script executed:

# Get the file size to determine how to read it
stat AI/modules/signal/models/transformer/wrapper.py 2>/dev/null || echo "File not found, searching..."

Repository: SISC-IT/sisc-web

Length of output: 88


🏁 Script executed:

# Search for the file more broadly
fd wrapper.py --type f | head -20

Repository: SISC-IT/sisc-web

Length of output: 107


🏁 Script executed:

# Read the wrapper.py file, showing line numbers
cat -n AI/modules/signal/models/transformer/wrapper.py

Repository: SISC-IT/sisc-web

Length of output: 4721


verbose의 설정 무시 및 validation_data 중복 전달 위험이 있습니다.

verboseepochs, batch_size와 달리 config 값을 무시하고 직접 기본값 1을 사용합니다. 또한 kwargs에 validation_data가 포함되면 동일 인자를 두 번 넘겨 TypeError가 발생할 수 있습니다.

🐛 수정 제안
-        verbose = int(kwargs.pop("verbose", 1))
+        verbose = int(kwargs.pop("verbose", self.config.get("verbose", 1)))
@@
-        # validation_data는 (X_val, y_val)이 둘 다 있을 때만
-        validation_data = (X_val, y_val) if (X_val is not None and y_val is not None) else None
+        # validation_data는 kwargs가 우선, 없으면 (X_val, y_val) 사용
+        validation_data = kwargs.pop("validation_data", None)
+        if validation_data is None and (X_val is not None and y_val is not None):
+            validation_data = (X_val, y_val)
🤖 Prompt for AI Agents
In `@AI/modules/signal/models/transformer/wrapper.py` around lines 62 - 80, The
current fit wrapper ignores config for verbose and risks passing validation_data
twice; change the verbose assignment to pop from kwargs with fallback to
self.config (e.g. verbose = int(kwargs.pop("verbose", self.config.get("verbose",
1)))) mirroring epochs/batch_size behavior, and pop any provided validation_data
from kwargs before building the local validation_data tuple (so remove/consume
kwargs["validation_data"] if present) to avoid duplicate keyword when calling
self.model.fit; keep callbacks being popped as is and ensure the final
self.model.fit call uses the local validation_data, epochs, batch_size,
callbacks, verbose, **kwargs.

Comment on lines +70 to +83
if score >= BUY_THRESHOLD:
if position_qty == 0: # 포지션 없을 때만 진입 (단타 전략 예시)
# 가용 현금의 95%까지 매수
invest_amount = cash * MAX_INVEST_RATIO
buy_qty = int(invest_amount // current_price)

if buy_qty > 0:
action = "BUY"
qty = buy_qty
reason = f"강력 매수 신호 (점수: {score:.4f})"
else:
reason = "매수 신호 발생했으나 현금 부족"
else:
reason = "이미 포지션 보유 중 (추가 매수 없음)"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

current_price 유효성 검증 부재

Line 74에서 current_price로 나누기 연산을 수행하는데, current_price가 0 이하인 경우(데이터 오류, 상장폐지 등) ZeroDivisionError 또는 잘못된 수량 계산이 발생할 수 있습니다.

🛡️ 방어 로직 제안
     if score >= BUY_THRESHOLD:
         if position_qty == 0:
+            if current_price <= 0:
+                reason = "유효하지 않은 현재가 (매수 불가)"
+                return "HOLD", 0, reason
             invest_amount = cash * MAX_INVEST_RATIO
             buy_qty = int(invest_amount // current_price)
🤖 Prompt for AI Agents
In `@AI/modules/trader/policy.py` around lines 70 - 83, The buy branch lacks
validation for current_price before dividing; in the block that computes
invest_amount and buy_qty (references: BUY_THRESHOLD branch, variables
current_price, MAX_INVEST_RATIO, invest_amount, buy_qty, position_qty, action,
qty, reason), add a guard that treats current_price <= 0 as invalid: log or set
a clear reason (e.g., "유효하지 않은 현재가"), do not perform the division, and skip
setting action/qty; only compute buy_qty when current_price > 0 and handle the
zero-buy_qty case as already implemented.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@AI/pipelines/daily_routine.py`:
- Around line 30-41: The function run_daily_pipeline currently only prints the
mode but doesn't change behavior; update run_daily_pipeline to make mode
meaningful: if mode == "live" call the real order execution path (e.g., invoke
the existing order submission function or a new wrapper like
execute_live_orders) or, if live orders aren't implemented, explicitly block or
require confirmation by raising an exception or returning early with a logged
warning via the pipeline logger (e.g., process_logger.warn/error) to prevent
accidental live runs; also update the docstring to state the enforced behavior
for "live" and ensure any CLI entrypoint that calls run_daily_pipeline passes
the mode through unchanged.
♻️ Duplicate comments (2)
AI/libs/llm/ollama.py (1)

35-42: requests.post timeout 및 로깅 적용 필요 (기존 리뷰와 동일 이슈).
응답 지연 시 무한 대기 가능성이 있고, print는 운영 로깅에 부적합합니다. 이전 리뷰와 동일한 내용이므로 재확인만 부탁드립니다.

🔧 제안 수정
+import logging
+
+logger = logging.getLogger(__name__)
+
         try:
-            response = requests.post(url, json=payload)
+            response = requests.post(url, json=payload, timeout=60)
             response.raise_for_status()
             result = response.json()
             return result.get("response", "")
         except Exception as e:
-            print(f"[OllamaClient][Error] 텍스트 생성 실패: {e}")
+            logger.error("[OllamaClient] 텍스트 생성 실패", exc_info=e)
             return ""
AI/pipelines/daily_routine.py (1)

211-218: 리포트 ID 매핑의 순서 가정은 여전히 취약합니다

save_reports_to_db의 반환 순서가 입력과 동일하다는 가정으로 매핑하고 있어, DB 반환 순서가 바뀌면 티커-리포트 ID가 뒤섞일 수 있습니다. 저장 함수에서 (ticker, id)를 직접 반환하거나 RETURNING ticker, id 기반 매핑으로 바꾸는 것이 안전합니다. Python 3.10+라면 zip(..., strict=True)로 길이 불일치도 조기에 감지하세요.

🧩 최소 안전장치(길이 검증)
-            saved_report_map = {
-                r["ticker"]: saved_id 
-                for r, saved_id in zip(report_results, saved_report_ids)
-            }
+            saved_report_map = {
+                r["ticker"]: saved_id 
+                for r, saved_id in zip(report_results, saved_report_ids, strict=True)
+            }
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between beebf78 and e6b179c.

📒 Files selected for processing (2)
  • AI/libs/llm/ollama.py
  • AI/pipelines/daily_routine.py
🧰 Additional context used
🧬 Code graph analysis (1)
AI/libs/llm/ollama.py (1)
AI/libs/llm/base_client.py (3)
  • BaseLLMClient (11-36)
  • generate_text (19-31)
  • get_health (34-36)
🪛 Ruff (0.14.11)
AI/pipelines/daily_routine.py

48-48: Do not catch blind exception: Exception

(BLE001)


56-56: Do not catch blind exception: Exception

(BLE001)


75-75: f-string without any placeholders

Remove extraneous f prefix

(F541)


89-89: f-string without any placeholders

Remove extraneous f prefix

(F541)


156-156: f-string without any placeholders

Remove extraneous f prefix

(F541)


170-170: Do not catch blind exception: Exception

(BLE001)


194-194: Do not catch blind exception: Exception

(BLE001)


217-217: zip() without an explicit strict= parameter

Add explicit value for parameter strict=

(B905)


219-219: Do not catch blind exception: Exception

(BLE001)


233-233: Do not catch blind exception: Exception

(BLE001)


236-236: f-string without any placeholders

Remove extraneous f prefix

(F541)

AI/libs/llm/ollama.py

36-36: Probable use of requests call without timeout

(S113)


40-40: Do not catch blind exception: Exception

(BLE001)


47-47: Consider moving this statement to an else block

(TRY300)


48-48: Do not catch blind exception: Exception

(BLE001)

🔇 Additional comments (2)
AI/pipelines/daily_routine.py (2)

86-90: score < 0 실패 조건은 계약 확인이 필요합니다

모델 점수가 음수도 정상 범위라면 Line 88-90에서 정상 시그널이 누락됩니다. run_inference가 실패 시 음수를 반환한다는 계약이 있는지 확인하고, 아니라면 None/NaN 또는 예외로 실패를 구분해 주세요.


111-147: 거래 후 상태 및 P&L 계산 흐름이 명확합니다

BUY/SELL/HOLD에 따른 next_*와 손익 계산이 일관되게 정리되어 있습니다.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment on lines +30 to +41
def run_daily_pipeline(target_tickers: list, mode: str = "simulation", enable_xai: bool = True):
"""
일일 파이프라인 실행 함수

Args:
target_tickers (list): 분석 대상 종목 코드 리스트
mode (str): 실행 모드 ('simulation': DB저장만, 'live': 실제 주문 전송)
enable_xai (bool): XAI 리포트 생성 여부
"""
today_str = datetime.now().strftime("%Y-%m-%d")
print(f"\n[{today_str}] === AI Daily Routine Started (Mode: {mode}, XAI: {enable_xai}) ===")

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

mode 옵션이 출력에만 사용됩니다

mode가 로그 외에는 동작에 영향을 주지 않습니다. 독스트링/CLI에서 live를 노출하는 만큼, 실제 주문 로직을 호출하거나 최소한 경고/차단으로 명시해 오해를 줄여주세요.

🛠️ 간단한 경고 처리 예시
 def run_daily_pipeline(target_tickers: list, mode: str = "simulation", enable_xai: bool = True):
@@
-    print(f"\n[{today_str}] === AI Daily Routine Started (Mode: {mode}, XAI: {enable_xai}) ===")
+    print(f"\n[{today_str}] === AI Daily Routine Started (Mode: {mode}, XAI: {enable_xai}) ===")
+    if mode == "live":
+        print("[Warning] live 모드는 아직 주문 실행과 연결되지 않았습니다. simulation 흐름으로 진행합니다.")
🤖 Prompt for AI Agents
In `@AI/pipelines/daily_routine.py` around lines 30 - 41, The function
run_daily_pipeline currently only prints the mode but doesn't change behavior;
update run_daily_pipeline to make mode meaningful: if mode == "live" call the
real order execution path (e.g., invoke the existing order submission function
or a new wrapper like execute_live_orders) or, if live orders aren't
implemented, explicitly block or require confirmation by raising an exception or
returning early with a logged warning via the pipeline logger (e.g.,
process_logger.warn/error) to prevent accidental live runs; also update the
docstring to state the enforced behavior for "live" and ensure any CLI
entrypoint that calls run_daily_pipeline passes the mode through unchanged.

@twq110
Copy link
Contributor Author

twq110 commented Jan 16, 2026

ㅇㅇ

@twq110 twq110 merged commit f866933 into main Jan 16, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant