mirror of
https://github.com/shawnkim1997/All-in-one-Financial-Analysis.git
synced 2026-08-09 16:50:57 +00:00
chore: harden technical page nulls, clarify settings copy, refresh claude.md
- technical page treats all indicator fields as nullable so partial yfinance/yahooquery payloads no longer crash the indicator card - settings About copy now correctly states API keys reach the local backend only when an AI feature needs them - claude.md catalogues the transcripts page, components, lib hooks, and the bumped router count
This commit is contained in:
@@ -87,7 +87,7 @@ export default function SettingsPage() {
|
||||
<Card title="About">
|
||||
<div className="text-text-muted text-sm space-y-1">
|
||||
<p>ATLAS Terminal v2.0 — Advanced Trading & Liquidity Analysis System</p>
|
||||
<p>API keys are stored locally in your browser. They are never sent to our servers.</p>
|
||||
<p>API keys are stored locally in your browser and only sent to your local ATLAS backend when an AI feature needs them.</p>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
@@ -9,13 +9,13 @@ import { useTicker } from "../lib/use-ticker";
|
||||
|
||||
interface Indicators {
|
||||
ticker: string;
|
||||
current_price: number;
|
||||
rsi_14: number;
|
||||
sma: { sma_20: number; sma_50: number; sma_200: number | null };
|
||||
ema: { ema_12: number; ema_26: number };
|
||||
macd: { macd: number; signal: number; histogram: number };
|
||||
bollinger_bands: { upper: number; middle: number; lower: number };
|
||||
atr_14: number;
|
||||
current_price: number | null;
|
||||
rsi_14: number | null;
|
||||
sma: { sma_20: number | null; sma_50: number | null; sma_200: number | null };
|
||||
ema: { ema_12: number | null; ema_26: number | null };
|
||||
macd: { macd: number | null; signal: number | null; histogram: number | null };
|
||||
bollinger_bands: { upper: number | null; middle: number | null; lower: number | null };
|
||||
atr_14: number | null;
|
||||
}
|
||||
|
||||
interface ChartBar {
|
||||
@@ -29,10 +29,37 @@ interface ChartBar {
|
||||
|
||||
interface FibLevels {
|
||||
ticker: string;
|
||||
high_52w: number;
|
||||
low_52w: number;
|
||||
current_price: number;
|
||||
levels: Record<string, number>;
|
||||
high_52w: number | null;
|
||||
low_52w: number | null;
|
||||
current_price: number | null;
|
||||
levels: Record<string, number | null>;
|
||||
}
|
||||
|
||||
function isFiniteNumber(value: unknown): value is number {
|
||||
return typeof value === "number" && Number.isFinite(value);
|
||||
}
|
||||
|
||||
function formatMoney(value: number | null | undefined, digits = 2): string {
|
||||
return isFiniteNumber(value) ? `$${value.toFixed(digits)}` : "—";
|
||||
}
|
||||
|
||||
function formatNumber(value: number | null | undefined, digits = 2): string {
|
||||
return isFiniteNumber(value) ? value.toFixed(digits) : "—";
|
||||
}
|
||||
|
||||
function formatPercent(value: number | null | undefined, digits = 1): string {
|
||||
return isFiniteNumber(value) ? `${value.toFixed(digits)}%` : "—";
|
||||
}
|
||||
|
||||
function safeRatioPercent(numerator: number | null | undefined, denominator: number | null | undefined): number | null {
|
||||
if (!isFiniteNumber(numerator) || !isFiniteNumber(denominator) || denominator === 0) return null;
|
||||
const result = (numerator / denominator) * 100;
|
||||
return Number.isFinite(result) ? result : null;
|
||||
}
|
||||
|
||||
function compareAbove(lhs: number | null | undefined, rhs: number | null | undefined): boolean | null {
|
||||
if (!isFiniteNumber(lhs) || !isFiniteNumber(rhs)) return null;
|
||||
return lhs > rhs;
|
||||
}
|
||||
|
||||
export default function TechnicalPage() {
|
||||
@@ -153,7 +180,7 @@ export default function TechnicalPage() {
|
||||
if (loading) return <LoadingPulse label="Loading technical data…" />;
|
||||
|
||||
const macdSignal = indicators?.macd
|
||||
? indicators.macd.histogram > 0 ? "Bullish" : "Bearish"
|
||||
? indicators.macd.histogram == null ? "—" : indicators.macd.histogram > 0 ? "Bullish" : "Bearish"
|
||||
: "—";
|
||||
|
||||
return (
|
||||
@@ -185,20 +212,20 @@ export default function TechnicalPage() {
|
||||
{/* Indicator Cards */}
|
||||
{indicators && (
|
||||
<div className="grid grid-cols-4 gap-3 mb-6">
|
||||
<StatCard label="Current Price" value={`$${indicators.current_price?.toFixed(2)}`} />
|
||||
<StatCard label="Current Price" value={formatMoney(indicators.current_price)} />
|
||||
<StatCard
|
||||
label="RSI (14)"
|
||||
value={indicators.rsi_14}
|
||||
tone={indicators.rsi_14 > 70 ? "negative" : indicators.rsi_14 < 30 ? "positive" : "default"}
|
||||
detail={indicators.rsi_14 > 70 ? "Overbought" : indicators.rsi_14 < 30 ? "Oversold" : "Neutral"}
|
||||
value={formatNumber(indicators.rsi_14, 1)}
|
||||
tone={indicators.rsi_14 != null && indicators.rsi_14 > 70 ? "negative" : indicators.rsi_14 != null && indicators.rsi_14 < 30 ? "positive" : "default"}
|
||||
detail={indicators.rsi_14 != null ? (indicators.rsi_14 > 70 ? "Overbought" : indicators.rsi_14 < 30 ? "Oversold" : "Neutral") : "No data"}
|
||||
/>
|
||||
<StatCard
|
||||
label="MACD Signal"
|
||||
value={macdSignal}
|
||||
tone={indicators.macd.histogram > 0 ? "positive" : "negative"}
|
||||
detail={`H: ${indicators.macd.histogram.toFixed(4)}`}
|
||||
tone={indicators.macd.histogram == null ? "default" : indicators.macd.histogram > 0 ? "positive" : "negative"}
|
||||
detail={`H: ${formatNumber(indicators.macd.histogram, 4)}`}
|
||||
/>
|
||||
<StatCard label="ATR (14)" value={indicators.atr_14} detail="Volatility" />
|
||||
<StatCard label="ATR (14)" value={formatNumber(indicators.atr_14, 2)} detail="Volatility" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -209,16 +236,16 @@ export default function TechnicalPage() {
|
||||
<h3 className="text-text-secondary text-sm font-semibold mb-3">Moving Averages</h3>
|
||||
<div className="space-y-2">
|
||||
{[
|
||||
{ label: "SMA 20", value: indicators.sma.sma_20, signal: indicators.current_price > indicators.sma.sma_20 },
|
||||
{ label: "SMA 50", value: indicators.sma.sma_50, signal: indicators.current_price > indicators.sma.sma_50 },
|
||||
{ label: "SMA 200", value: indicators.sma.sma_200, signal: indicators.sma.sma_200 ? indicators.current_price > indicators.sma.sma_200 : null },
|
||||
{ label: "EMA 12", value: indicators.ema.ema_12, signal: indicators.current_price > indicators.ema.ema_12 },
|
||||
{ label: "EMA 26", value: indicators.ema.ema_26, signal: indicators.current_price > indicators.ema.ema_26 },
|
||||
{ label: "SMA 20", value: indicators.sma.sma_20, signal: compareAbove(indicators.current_price, indicators.sma.sma_20) },
|
||||
{ label: "SMA 50", value: indicators.sma.sma_50, signal: compareAbove(indicators.current_price, indicators.sma.sma_50) },
|
||||
{ label: "SMA 200", value: indicators.sma.sma_200, signal: compareAbove(indicators.current_price, indicators.sma.sma_200) },
|
||||
{ label: "EMA 12", value: indicators.ema.ema_12, signal: compareAbove(indicators.current_price, indicators.ema.ema_12) },
|
||||
{ label: "EMA 26", value: indicators.ema.ema_26, signal: compareAbove(indicators.current_price, indicators.ema.ema_26) },
|
||||
].map((ma) => (
|
||||
<div key={ma.label} className="flex justify-between items-center text-sm">
|
||||
<span className="text-text-muted">{ma.label}</span>
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="text-text-primary font-mono">{ma.value != null ? `$${ma.value.toFixed(2)}` : "—"}</span>
|
||||
<span className="text-text-primary font-mono">{formatMoney(ma.value)}</span>
|
||||
<span className={`text-xs font-semibold px-2 py-0.5 rounded ${
|
||||
ma.signal === true ? "bg-fin-positive/15 text-fin-positive" :
|
||||
ma.signal === false ? "bg-fin-negative/15 text-fin-negative" : "bg-surface-sunken text-text-muted"
|
||||
@@ -233,48 +260,62 @@ export default function TechnicalPage() {
|
||||
|
||||
<ChartContainer title="Bollinger Bands" subtitle="20-period, 2 standard deviations.">
|
||||
<h3 className="text-text-secondary text-sm font-semibold mb-3">Bollinger Bands (20, 2)</h3>
|
||||
{(() => {
|
||||
const upper = indicators.bollinger_bands.upper;
|
||||
const middle = indicators.bollinger_bands.middle;
|
||||
const lower = indicators.bollinger_bands.lower;
|
||||
const bbWidth = middle == null ? null : safeRatioPercent((upper ?? 0) - (lower ?? 0), middle);
|
||||
const bandSpan = isFiniteNumber(upper) && isFiniteNumber(lower) ? upper - lower : null;
|
||||
const percentB =
|
||||
isFiniteNumber(indicators.current_price) && isFiniteNumber(lower) && isFiniteNumber(bandSpan) && bandSpan !== 0
|
||||
? ((indicators.current_price - lower) / bandSpan) * 100
|
||||
: null;
|
||||
|
||||
return (
|
||||
<div className="space-y-3">
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="text-text-muted">Upper Band</span>
|
||||
<span className="font-mono text-fin-negative">${indicators.bollinger_bands.upper.toFixed(2)}</span>
|
||||
<span className="font-mono text-fin-negative">{formatMoney(upper)}</span>
|
||||
</div>
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="text-text-muted">Middle Band</span>
|
||||
<span className="font-mono text-brand-gold">${indicators.bollinger_bands.middle.toFixed(2)}</span>
|
||||
<span className="font-mono text-brand-gold">{formatMoney(middle)}</span>
|
||||
</div>
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="text-text-muted">Lower Band</span>
|
||||
<span className="font-mono text-fin-positive">${indicators.bollinger_bands.lower.toFixed(2)}</span>
|
||||
<span className="font-mono text-fin-positive">{formatMoney(lower)}</span>
|
||||
</div>
|
||||
<div className="flex justify-between text-sm border-t border-border pt-3">
|
||||
<span className="text-text-muted">BB Width</span>
|
||||
<span className="text-text-primary font-mono">
|
||||
{((indicators.bollinger_bands.upper - indicators.bollinger_bands.lower) / indicators.bollinger_bands.middle * 100).toFixed(2)}%
|
||||
{formatPercent(bbWidth, 2)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="text-text-muted">%B Position</span>
|
||||
<span className="text-text-primary font-mono">
|
||||
{((indicators.current_price - indicators.bollinger_bands.lower) / (indicators.bollinger_bands.upper - indicators.bollinger_bands.lower) * 100).toFixed(1)}%
|
||||
{formatPercent(percentB, 1)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})()}
|
||||
|
||||
{/* MACD Detail */}
|
||||
<h3 className="text-text-secondary text-sm font-semibold mb-3 mt-5">MACD (12, 26, 9)</h3>
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="text-text-muted">MACD Line</span>
|
||||
<span className="text-text-primary font-mono">{indicators.macd.macd.toFixed(4)}</span>
|
||||
<span className="text-text-primary font-mono">{formatNumber(indicators.macd.macd, 4)}</span>
|
||||
</div>
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="text-text-muted">Signal Line</span>
|
||||
<span className="text-text-primary font-mono">{indicators.macd.signal.toFixed(4)}</span>
|
||||
<span className="text-text-primary font-mono">{formatNumber(indicators.macd.signal, 4)}</span>
|
||||
</div>
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="text-text-muted">Histogram</span>
|
||||
<span className={`font-mono ${indicators.macd.histogram > 0 ? "text-fin-positive" : "text-fin-negative"}`}>
|
||||
{indicators.macd.histogram.toFixed(4)}
|
||||
<span className={`font-mono ${indicators.macd.histogram != null && indicators.macd.histogram > 0 ? "text-fin-positive" : "text-fin-negative"}`}>
|
||||
{formatNumber(indicators.macd.histogram, 4)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -288,19 +329,23 @@ export default function TechnicalPage() {
|
||||
<h3 className="text-text-secondary text-sm font-semibold mb-3">Fibonacci Retracement</h3>
|
||||
<div className="grid grid-cols-7 gap-3">
|
||||
{Object.entries(fib.levels).map(([level, price]) => {
|
||||
const isNear = Math.abs(price - fib.current_price) / fib.current_price < 0.02;
|
||||
const isNear =
|
||||
isFiniteNumber(price) &&
|
||||
isFiniteNumber(fib.current_price) &&
|
||||
fib.current_price !== 0 &&
|
||||
Math.abs(price - fib.current_price) / fib.current_price < 0.02;
|
||||
return (
|
||||
<div key={level} className={`rounded-lg p-3 text-center ${isNear ? "border border-brand-gold bg-brand-gold/10" : "bg-surface-sunken"}`}>
|
||||
<div className="text-text-muted text-xs mb-1">{level}</div>
|
||||
<div className={`font-mono text-sm font-semibold ${isNear ? "text-brand-navy" : "text-text-primary"}`}>
|
||||
${price.toFixed(2)}
|
||||
{formatMoney(price)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div className="mt-3 text-text-muted text-xs font-mono">
|
||||
52W Range: ${fib.low_52w.toFixed(2)} — ${fib.high_52w.toFixed(2)} | Current: ${fib.current_price.toFixed(2)}
|
||||
52W Range: {formatMoney(fib.low_52w)} — {formatMoney(fib.high_52w)} | Current: {formatMoney(fib.current_price)}
|
||||
</div>
|
||||
</ChartContainer>
|
||||
)}
|
||||
|
||||
+41
-14
@@ -96,17 +96,23 @@ atlas-terminal/
|
||||
│ │ ├── macro/page.tsx # 거시 5탭(FRED/Cycle/OECD/Korea/Calendar) + Quadrant/YieldFX/SmartMoney
|
||||
│ │ ├── earnings/page.tsx # EPS Beat/Miss + Revenue + Next Earnings
|
||||
│ │ ├── news/page.tsx # Split-view: 기사 리스트 + iframe 원문
|
||||
│ │ ├── transcripts/page.tsx # 비디오/오디오 트랜스크립트 업로드 + 검색 + 분석
|
||||
│ │ ├── screener/page.tsx # 종목 스크리너 + 전략 백테스트
|
||||
│ │ ├── portfolio/page.tsx # 포지션 CRUD + Risk Metrics + OCR
|
||||
│ │ ├── filings/page.tsx # 공시 원문 — SEC/DART/EDINET 자동분기 (5탭 + AI 요약)
|
||||
│ │ ├── settings/page.tsx # API Key 관리
|
||||
│ │ ├── components/
|
||||
│ │ │ ├── app-shell.tsx # 3-panel 레이아웃 래퍼 (SSR-safe dynamic import)
|
||||
│ │ │ ├── sidebar.tsx # 좌측 네비게이션 (12개 메뉴)
|
||||
│ │ │ ├── sidebar.tsx # 좌측 네비게이션 (13개 메뉴)
|
||||
│ │ │ ├── ticker-bar.tsx # 상단 실시간 지수 바 (S&P, NASDAQ, KOSPI, BTC)
|
||||
│ │ │ ├── chat-panel.tsx # 우측 AI Copilot 채팅
|
||||
│ │ │ ├── filings/
|
||||
│ │ │ │ └── FilingsViewer.tsx # 공시 뷰어 (IntersectionObserver scroll-spy)
|
||||
│ │ │ ├── transcripts/
|
||||
│ │ │ │ ├── TranscriptUploadForm.tsx # YouTube/URL/업로드 3-탭 입력
|
||||
│ │ │ │ ├── TranscriptJobList.tsx # 상태 배지 + 진행률 리스트
|
||||
│ │ │ │ ├── TranscriptDetailPanel.tsx # 요약/키워드/감성/원문 패널
|
||||
│ │ │ │ └── TranscriptSearchBar.tsx # 저장된 트랜스크립트 전문 검색
|
||||
│ │ │ ├── macro/
|
||||
│ │ │ │ ├── GlobalMacroQuadrantChart.tsx # 성장 vs 인플레 Z-score 산점도
|
||||
│ │ │ │ ├── YieldFxDualAxisChart.tsx # US10Y 스프레드 vs FX 이중축
|
||||
@@ -134,6 +140,8 @@ atlas-terminal/
|
||||
│ │ └── lib/
|
||||
│ │ ├── use-ticker.ts # 티커 상태 훅 (localStorage + CustomEvent, hydration-safe)
|
||||
│ │ ├── api.ts # API 클라이언트 유틸 (apiFetch, apiPost)
|
||||
│ │ ├── use-video-transcript.ts # 트랜스크립트 submit/list/get/search/poll 훅
|
||||
│ │ ├── video-transcript-types.ts # 트랜스크립트 타입 정의
|
||||
│ │ ├── ticker-alias.ts # 자연어→티커 매핑 ("gold"→GC=F, "samsung"→005930.KS)
|
||||
│ │ └── filing-jurisdiction.ts # 티커→관할권 추론 (SEC/DART/EDINET)
|
||||
│ ├── next.config.mjs # /api/* → localhost:8000 프록시
|
||||
@@ -141,12 +149,13 @@ atlas-terminal/
|
||||
│ └── package.json
|
||||
│
|
||||
├── server/ # FastAPI 백엔드
|
||||
│ ├── main.py # FastAPI app + 21개 라우터 등록
|
||||
│ ├── routers/ # API 엔드포인트 (21개)
|
||||
│ ├── main.py # FastAPI app + 22개 라우터 등록
|
||||
│ ├── routers/ # API 엔드포인트 (22개)
|
||||
│ │ ├── analysis.py # POST /api/analysis — Gemini LLM 분석 (strategy/risks/mda/forensic)
|
||||
│ │ ├── chat.py # /api/chat — AI Copilot
|
||||
│ │ ├── crypto.py # /api/crypto — 암호화폐 가격
|
||||
│ │ ├── dart.py # /api/dart — Korea DART 기업검색 + 사업보고서 섹션
|
||||
│ │ ├── daily_news.py # /api/daily-news — FT 헤드라인 + 번역
|
||||
│ │ ├── earnings.py # /api/earnings — history|calendar|quarterly|transcript
|
||||
│ │ ├── edgar.py # /api/edgar — SEC 10-K 다운로드 + 파싱
|
||||
│ │ ├── edinet.py # /api/edinet — Japan EDINET 링크 + 섹션 (optional key)
|
||||
@@ -163,7 +172,8 @@ atlas-terminal/
|
||||
│ │ ├── research.py # /api/research — 퀀트 대시보드 (F-Score/DuPont/Sankey/Waterfall/Anomaly)
|
||||
│ │ ├── screener.py # /api/screener — 종목 검색 + 백테스트
|
||||
│ │ ├── technical.py # /api/technical — indicators/chart-data/fibonacci/ichimoku
|
||||
│ │ └── valuation.py # /api/valuation — DCF/Sensitivity/Monte Carlo/Tornado/Reverse DCF
|
||||
│ │ ├── valuation.py # /api/valuation — DCF/Sensitivity/Monte Carlo/Tornado/Reverse DCF
|
||||
│ │ └── video_transcript.py # /api/video — submit/upload/jobs/search/delete
|
||||
│ ├── services/ # 비즈니스 로직 (37개)
|
||||
│ │ ├── backtester.py # 전략 백테스트 (SMA/RSI/Buy&Hold)
|
||||
│ │ ├── cache.py # 인메모리 TTL 캐시 데코레이터
|
||||
@@ -200,6 +210,7 @@ atlas-terminal/
|
||||
│ │ ├── sensitivity.py # build_sensitivity_matrix, build_tornado_data
|
||||
│ │ ├── smart_money_service.py # Copper/Gold ratio + RORO 리스크 복합지수
|
||||
│ │ ├── text_chunker.py # smart_chunk, clean_text_for_llm
|
||||
│ │ ├── video_transcript_service.py # yt-dlp + faster-whisper + Gemini 통합 파이프라인
|
||||
│ │ └── yield_fx_service.py # US10Y 스프레드 vs FX (USDJPY/EURUSD/USDKRW)
|
||||
│ ├── db/ # 데이터베이스 레이어
|
||||
│ │ ├── unified_repo.py # SQLite/PostgreSQL 통합 인터페이스
|
||||
@@ -209,6 +220,8 @@ atlas-terminal/
|
||||
│ │ ├── portfolio_repo.py # 포트폴리오 SQLite CRUD
|
||||
│ │ ├── pg_portfolio_repo.py # 포트폴리오 PostgreSQL CRUD
|
||||
│ │ ├── pg_cache_repo.py # PostgreSQL 캐시
|
||||
│ │ ├── video_repo.py # 트랜스크립트 SQLite CRUD + FTS5
|
||||
│ │ ├── pg_video_repo.py # 트랜스크립트 PostgreSQL CRUD + tsvector
|
||||
│ │ ├── dashboard_repo.py # 대시보드 레이아웃 저장
|
||||
│ │ └── settings_repo.py # 설정 저장소
|
||||
│ ├── models/
|
||||
@@ -416,7 +429,17 @@ fontFamily: {
|
||||
|--------|------|------|
|
||||
| GET | `/api/markets/heatmap/{index_name}` | 지수 구성종목 히트맵 (S&P/NASDAQ/KOSPI/FTSE) |
|
||||
|
||||
### 5.16 기타
|
||||
### 5.16 Video Transcript (`/api/video`)
|
||||
| Method | Path | 설명 |
|
||||
|--------|------|------|
|
||||
| POST | `/api/video/submit` | YouTube/웹 URL/로컬 경로 제출 → 비동기 transcript job 생성 |
|
||||
| POST | `/api/video/upload` | multipart 업로드 → 임시 저장 후 transcript job 생성 |
|
||||
| GET | `/api/video/jobs` | 최근 트랜스크립트 작업 목록 |
|
||||
| GET | `/api/video/jobs/{job_id}` | 단일 작업 상세 (상태 + 분석 + 원문) |
|
||||
| GET | `/api/video/search?q=` | 저장된 트랜스크립트 전문 검색 |
|
||||
| DELETE | `/api/video/jobs/{job_id}` | 작업 삭제 |
|
||||
|
||||
### 5.17 기타
|
||||
| Prefix | 설명 |
|
||||
|--------|------|
|
||||
| `/api/analysis` | Gemini AI 분석 (strategy/risks/mda/forensic/financials) |
|
||||
@@ -428,7 +451,7 @@ fontFamily: {
|
||||
|
||||
---
|
||||
|
||||
## 6. Frontend Pages (전체 12개)
|
||||
## 6. Frontend Pages (전체 13개)
|
||||
|
||||
| 경로 | 파일 | 핵심 기능 |
|
||||
|------|------|----------|
|
||||
@@ -440,6 +463,7 @@ fontFamily: {
|
||||
| `/macro` | `macro/page.tsx` | **5탭**(FRED/Cycle Heatmap/OECD CLI/Korea/Calendar) + **Quadrant**(성장vs인플레) + **YieldFX** + **SmartMoney**(Copper/Gold+RORO) |
|
||||
| `/earnings` | `earnings/page.tsx` | 다음 실적일, EPS Beat/Miss 바차트, 분기 매출/순이익 |
|
||||
| `/news` | `news/page.tsx` | Split-view — 좌측 기사 리스트(340px) + 우측 iframe 원문 보기 |
|
||||
| `/transcripts` | `transcripts/page.tsx` | 비디오/오디오 입력, 상태 폴링, 요약/키워드/감성, 전문검색 |
|
||||
| `/screener` | `screener/page.tsx` | **종목 스크리너**(PE/섹터/배당 필터) + **전략 백테스트**(SMA/RSI/Buy&Hold, 캔들차트 시각화) |
|
||||
| `/portfolio` | `portfolio/page.tsx` | 포지션 CRUD + Risk Metrics(VaR/Sharpe/MDD) + OCR 스크린샷 |
|
||||
| `/filings` | `filings/page.tsx` | **관할권 자동분기** — 티커 접미사로 SEC/DART(`.KS`/`.KQ`)/EDINET(`.T`), 5탭 + AI Summary |
|
||||
@@ -566,14 +590,16 @@ pydantic>=2.7.0 google-generativeai>=0.8.0
|
||||
anthropic>=0.39.0 openai>=1.50.0
|
||||
beautifulsoup4>=4.12.0 requests>=2.31.0
|
||||
pandas>=2.0.0 lxml>=4.9.0
|
||||
python-dotenv>=1.0.0 yfinance>=0.2.40
|
||||
yahooquery>=2.2.0 sec-edgar-downloader>=5.0.0
|
||||
feedparser>=6.0.0 ta>=0.11.0
|
||||
dart-fss>=0.4.0 numpy>=1.20.0
|
||||
scipy>=1.10.0 dbnomics>=1.2.0
|
||||
aiosqlite>=0.20.0 asyncpg>=0.30.0
|
||||
pillow>=10.0.0 httpx>=0.27.0
|
||||
pytest>=8.0.0
|
||||
python-dotenv>=1.0.0 python-multipart>=0.0.9
|
||||
yfinance>=0.2.40 yahooquery>=2.2.0
|
||||
sec-edgar-downloader>=5.0.0 feedparser>=6.0.0
|
||||
ta>=0.11.0 dart-fss>=0.4.0
|
||||
numpy>=1.20.0 scipy>=1.10.0
|
||||
dbnomics>=1.2.0 aiosqlite>=0.20.0
|
||||
asyncpg>=0.30.0 pillow>=10.0.0
|
||||
httpx>=0.27.0 pytest>=8.0.0
|
||||
faster-whisper>=1.0.3 yt-dlp>=2024.10.7
|
||||
srt>=3.5.3 ffmpeg-python>=0.2.0
|
||||
```
|
||||
|
||||
### Node.js (`apps/web/package.json`)
|
||||
@@ -592,6 +618,7 @@ recharts: ^2.15.4
|
||||
GOOGLE_API_KEY= # Gemini API
|
||||
SEC_EDGAR_EMAIL= # SEC 정책 필수 (10-K 다운로드용)
|
||||
DATABASE_URL= # PostgreSQL (없으면 SQLite 자동)
|
||||
WHISPER_MODEL_SIZE= # faster-whisper model size (default: base, optional: small/medium)
|
||||
NEWS_API_KEY= # 선택
|
||||
FMP_API_KEY= # Financial Modeling Prep (선택 — 비율·트랜스크립트·캘린더)
|
||||
ECOS_API_KEY= # 한국은행 ECOS (선택)
|
||||
|
||||
Reference in New Issue
Block a user