Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions AI/modules/data_collector/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
from .company_fundamentals_data import FundamentalsDataCollector
from .macro_data import MacroDataCollector
from .crypto_data import CryptoDataCollector
from .index_data import IndexDataCollector
from .event_data import EventDataCollector
from .market_breadth_data import MarketBreadthCollector
from .market_breadth_stats import MarketBreadthStatsCollector
Expand All @@ -16,8 +15,7 @@
"StockInfoCollector",
"FundamentalsDataCollector",
"MacroDataCollector",
"CryptoDataCollector"
"IndexDataCollector",
"CryptoDataCollector",
"EventDataCollector",
"MarketBreadthCollector",
"MarketBreadthStatsCollector",
Expand Down
55 changes: 9 additions & 46 deletions AI/modules/data_collector/company_fundamentals_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@
class FundamentalsDataCollector:
"""
기업의 재무제표(손익계산서, 대차대조표, 현금흐름표)를 수집하고
주요 퀀트 투자 지표(PER, PBR, ROE, 이자보상배율 등)를 계산하여 DB에 저장하는 클래스
주요 퀀트 투자 지표(ROE, 부채비율, 이자보상배율 등)를 계산하여 DB에 저장하는 클래스
per과 pbr은 MarketDataCollector에서 계산하여 저장됨(중복 방지)
"""

def __init__(self, db_name: str = "db"):
Expand All @@ -46,7 +47,7 @@ def fetch_and_calculate_metrics(self, ticker: str):
bal_df = stock.quarterly_balance_sheet.T
cash_df = stock.quarterly_cashflow.T
except Exception as e:
# print(f" [{ticker}] yfinance 데이터 로드 실패: {e}")
print(f"[ERROR][{ticker}] yfinance 데이터 로드 실패: {e}")
return pd.DataFrame()

if fin_df.empty or bal_df.empty:
Expand All @@ -61,17 +62,6 @@ def fetch_and_calculate_metrics(self, ticker: str):
merged_df = fin_df.join(bal_df, lsuffix='_fin', rsuffix='_bal', how='inner')
merged_df = merged_df.join(cash_df, rsuffix='_cash', how='left')

# 4. 주가 데이터 로드 (PER/PBR 계산용)
if not merged_df.empty:
start_date = merged_df.index.min() - timedelta(days=5)
end_date = merged_df.index.max() + timedelta(days=5)
try:
hist_df = stock.history(start=start_date, end=end_date)
except:
hist_df = pd.DataFrame()
else:
hist_df = pd.DataFrame()

processed_data = []

for date_idx, row in merged_df.iterrows():
Expand All @@ -87,7 +77,7 @@ def fetch_and_calculate_metrics(self, ticker: str):
operating_cash_flow = self.get_safe_value(row, ['Operating Cash Flow', 'Total Cash From Operating Activities'])
shares_issued = self.get_safe_value(row, ['Share Issued', 'Ordinary Shares Number'])

# --- [추가] 이자보상배율 계산을 위한 항목 ---
# --- 이자보상배율 계산을 위한 항목 ---
op_income = self.get_safe_value(row, ['Operating Income', 'EBIT'])
int_expense = self.get_safe_value(row, ['Interest Expense', 'Interest Expense Non Operating'])

Expand All @@ -103,38 +93,13 @@ def fetch_and_calculate_metrics(self, ticker: str):
if total_liabilities is not None and equity is not None and equity != 0:
debt_ratio = total_liabilities / equity

# 3. [신규] 이자보상배율 (Interest Coverage) = 영업이익 / |이자비용|
# 3. 이자보상배율 (Interest Coverage) = 영업이익 / |이자비용|
interest_coverage = None
if op_income is not None and int_expense is not None:
abs_int = abs(int_expense)
if abs_int > 0:
interest_coverage = op_income / abs_int

# 4. 주가 기반 지표 (PER, PBR)
close_price = None
if not hist_df.empty:
try:
target_ts = pd.Timestamp(date_val)
if target_ts in hist_df.index:
close_price = float(hist_df.loc[target_ts]['Close'])
else:
idx = hist_df.index.get_indexer([target_ts], method='pad')
if idx[0] != -1:
close_price = float(hist_df.iloc[idx[0]]['Close'])
except:
close_price = None

# PER
per = None
if close_price is not None and eps is not None and eps != 0:
per = close_price / eps

# PBR
pbr = None
if close_price is not None and equity is not None and shares_issued is not None and shares_issued != 0:
bps = equity / shares_issued
pbr = close_price / bps

processed_data.append((
str(ticker),
date_val,
Expand All @@ -143,12 +108,11 @@ def fetch_and_calculate_metrics(self, ticker: str):
total_assets,
total_liabilities,
equity,
shares_issued,
eps,
per,
pbr,
roe,
debt_ratio,
interest_coverage, # [추가됨]
interest_coverage,
operating_cash_flow
))

Expand All @@ -170,7 +134,7 @@ def save_to_db(self, ticker: str, data: List[tuple]):
insert_query = """
INSERT INTO public.financial_statements (
ticker, date, revenue, net_income, total_assets,
total_liabilities, equity, eps, per, pbr, roe, debt_ratio,
total_liabilities, equity, shares_issued, eps, roe, debt_ratio,
interest_coverage, operating_cash_flow
)
VALUES %s
Expand All @@ -181,9 +145,8 @@ def save_to_db(self, ticker: str, data: List[tuple]):
total_assets = EXCLUDED.total_assets,
total_liabilities = EXCLUDED.total_liabilities,
equity = EXCLUDED.equity,
shares_issued = EXCLUDED.shares_issued,
eps = EXCLUDED.eps,
per = EXCLUDED.per,
pbr = EXCLUDED.pbr,
roe = EXCLUDED.roe,
debt_ratio = EXCLUDED.debt_ratio,
interest_coverage = EXCLUDED.interest_coverage,
Expand Down
166 changes: 0 additions & 166 deletions AI/modules/data_collector/index_data.py

This file was deleted.

39 changes: 25 additions & 14 deletions AI/modules/data_collector/macro_data.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#AI/modules/data_collector/macro_data.py
# AI/modules/data_collector/macro_data.py
import sys
import os
import pandas as pd
Expand Down Expand Up @@ -102,7 +102,6 @@ def fetch_yahoo_data(self, start_date):
data = yf.download(ticker, start=start_date, progress=False)
if not data.empty:
# 'Close' 컬럼만 추출하여 이름 변경
# yfinance 버전 이슈로 인해 MultiIndex 컬럼일 수 있음 처리
if isinstance(data.columns, pd.MultiIndex):
price_series = data['Close'][ticker]
else:
Expand Down Expand Up @@ -172,21 +171,33 @@ def save_to_db(self, combined_df):

try:
data_to_insert = []

for date_idx, row in combined_df.iterrows():
# NaN 값을 None(SQL NULL)으로 변환
row = row.where(pd.notnull(row), None)

# [수정] Numpy 타입을 Python Native Type으로 변환하는 헬퍼 함수
def to_py(val):
# NaN 또는 None 체크
if pd.isna(val) or val is None:
return None
# Numpy 숫자 타입이면 .item()으로 변환
if hasattr(val, 'item'):
return val.item()
return float(val)

data_to_insert.append((
date_idx.date(),
row.get('cpi'), row.get('gdp'), row.get('ppi'), row.get('jolt'), row.get('cci'),
row.get('interest_rate'), row.get('trade_balance'),
row.get('core_cpi'), row.get('real_gdp'), row.get('unemployment_rate'), row.get('consumer_sentiment'),
row.get('ff_targetrate_upper'), row.get('ff_targetrate_lower'),
row.get('pce'), row.get('core_pce'),
row.get('tradebalance_goods'), row.get('trade_import'), row.get('trade_export'),
row.get('us10y'), row.get('us2y'), row.get('yield_spread'),
row.get('vix_close'), row.get('dxy_close'),
row.get('wti_price'), row.get('gold_price'), row.get('credit_spread_hy')
to_py(row.get('cpi')), to_py(row.get('gdp')), to_py(row.get('ppi')),
to_py(row.get('jolt')), to_py(row.get('cci')),
to_py(row.get('interest_rate')), to_py(row.get('trade_balance')),
to_py(row.get('core_cpi')), to_py(row.get('real_gdp')),
to_py(row.get('unemployment_rate')), to_py(row.get('consumer_sentiment')),
to_py(row.get('ff_targetrate_upper')), to_py(row.get('ff_targetrate_lower')),
to_py(row.get('pce')), to_py(row.get('core_pce')),
to_py(row.get('tradebalance_goods')), to_py(row.get('trade_import')),
to_py(row.get('trade_export')),
to_py(row.get('us10y')), to_py(row.get('us2y')), to_py(row.get('yield_spread')),
to_py(row.get('vix_close')), to_py(row.get('dxy_close')),
to_py(row.get('wti_price')), to_py(row.get('gold_price')),
to_py(row.get('credit_spread_hy'))
))

if data_to_insert:
Expand Down
Loading