# Stock Data API Guide
> **마지막 업데이트**: 2026-10-22 06:00:00 (KST)

분할 저장 구조(Splitting) 및 3-Tier 계층형 아키텍처 기반의 고성능 데이터 API 사용 방법을 설명합니다.

## 1. 기본 정보

- **파일 포맷**: JSON (Minified)
- **타임존**: 모든 시간은 **US/Eastern (New York)** 기준입니다.
- **Light 실시간 시세 갱신**: 정규장 15분 간격 (:03, :18, :33, :48), 프리/애프터 30분 간격 → `update_realtime.py` (전체 643+개 주식 및 지수의 실시간 체결가/등락률 일괄 갱신, 15초 내 완료)
- **Tier 1 Intraday 캔들/프롬프트 갱신**: 매 30분마다 (:03, :33) → `collect_data.py --tier core` (핵심 ~84개 Core 대형주/주요 ETF/지수/Essential Seed 캔들 및 AI 프롬프트 갱신)
- **Tier 2 Midday 캔들 갱신**: 평일 12:00~16:00 ET 사이 첫 실행 시 1회 (01:00 KST~) → `collect_data.py --tier tier2` (S&P 500, 나스닥 100 등 미드티어 캔들 일중 갱신)
- **Daily Full Universe 갱신**: 뉴욕 장 마감(16:00 ET) 이후 첫 실행 시 세션당 1회 → `collect_data.py --tier all` (전체 643~900개 전 종목 캔들/재무제표 일괄 갱신)
- **상태 기반 Catch-up 스케줄링**: GitHub Actions schedule 지연/누락에 대비해 `pipeline_scheduler.py`가 `.pipeline_state.json`의 마지막 성공 세션을 기준으로 Full/Tier2 수집 필요 여부를 판단합니다 (특정 분 단위 윈도우에 의존하지 않음, 주말에도 누락 세션 자동 복구).
- **단기/중기/장기 Top Pick 갱신**: Daily Full 및 Midday Tier 2 수집 직후 → `generate_top_picks.py` (네트워크 호출 없는 로컬 퀀트 스코어링, 1초 내 완료) → `top_picks.json` (자세한 내용은 [14. Top Pick API](#14-단기--중기--장기--종합-top-pick-api-top_picksjson) 참고)
- **가격 무결성 보장(Non-null Guarantee)**: 장 시작 직전 프리마켓이나 세션 전환 시에도 지수와 주식의 `currentPrice`가 null로 비워지지 않도록 Fallback 계층(실시간 체결가 → 정규장 최종가 → 최근 종가 → 전일 종가)을 영구 보장합니다.
- **Weekly Ticker Lifecycle & Health 자동화**: 매주 일요일 00:00 UTC (09:00 KST / 토요일 20:00 ET) → `manage_tickers_lifecycle.py` (보수적 2-Strike 기반 상장폐지/사망 종목 완전 정리 및 4대 소스 신규 티커 발굴·쿼터 밸런싱)
- **온디맨드 티커 추가**: `python add_ticker.py <TICKER>...` 명령어로 비S&P 500 중소형주, L.P., 고배당주 등 어떤 종목이든 즉시 시스템 편입 가능
- **캐시 버스팅**: 최신 데이터를 가져오려면 쿼리 파라미터를 사용하세요 (예: `market_summary.json?t=1234567`)

---

## 2. 파일 구조 (File Splitting)

단일 대용량 파일 대신 경량 인덱스 + 개별 종목 파일로 분리됩니다.

```
├── supported_tickers.json    ← 지원 티커 검증 엔드포인트 (전체 티커 목록 및 O(1) 맵)
├── market_summary.json       ← 전 종목 요약 (현재가, 등락률 등 경량 정보)
├── indices_summary.json      ← 주요 지수 요약 (Nasdaq 100, S&P 500, VIX 등)
├── top_picks.json            ← 단기/중기/장기 Top Pick (구간별 5~20개, 일 2회 갱신)
├── data/
│   ├── NVDA.json             ← NVDA 상세 (fundamentals + candles + ai_prompts)
│   └── ...
├── prompts/
│   ├── NVDA.json             ← NVDA 전용 경량 AI 분석 프롬프트 (momentum + comprehensive)
│   └── ...
└── indices/
    ├── IDX_NDX.json          ← 나스닥 100 상세 (Windows 호환을 위해 IDX_ 접두어 사용)
    ├── IDX_GSPC.json         ← S&P 500 상세
    └── ...
```

### 클라이언트 권장 로딩 패턴 (On-demand Fetching)

```javascript
// 0단계 (선택 사항): 입력받은 티커 지원 여부 사전 검증
const supportData = await fetch('supported_tickers.json').then(r => r.json());
const isSupported = Boolean(supportData.map?.[ticker]?.supported);
if (!isSupported) {
    console.warn(`미지원 티커: ${ticker}`);
}

// 1단계: 종목 및 지수 인덱스 로드 (경량)
const [stocks, indices] = await Promise.all([
    fetch('market_summary.json').then(r => r.json()),
    fetch('indices_summary.json').then(r => r.json())
]);

// 2단계: 클릭 시에만 상세 파일 로드
// 일반 종목
const stockDetail = await fetch(`data/${ticker}.json`).then(r => r.json());
// 지수 (티커가 ^로 시작할 경우)
const safeTicker = ticker.startsWith('^') ? 'IDX_' + ticker.substring(1) : ticker;
const indexDetail = await fetch(`indices/${safeTicker}.json`).then(r => r.json());
```

---

## 3. `market_summary.json` - 인덱스 파일

### 3.1 `metadata` 섹션

| key | 설명 |
|:---|:---|
| `last_updated_ny` | 마지막 갱신 시간 (ISO 8601, New York tz) |
| `fundamentals_last_updated` | 기업 정보 마지막 갱신 날짜 (YYYY-MM-DD) |
| `market_state` | 전체 시장 상태: `REGULAR` / `PRE` / `POST` / `OVERNIGHT` / `CLOSED` |
| `tickers` | 추적 중인 전체 티커 목록 |
| `ticker_count` | 추적 중인 전체 티커 수량 |
| `ticker_meta` | 각 티커별 카테고리 및 섹터 정보 매핑 (`{ TICKER: { "sector": "tech", "category": "core" } }`)<br>- 카테고리 종류: `core`(주요 대형주), `sp500`(S&P 500), `nasdaq100`(나스닥 100), `etf`(주요 ETF), `growth`(고성장주), `trending`(러셀/소형 핫 모멘텀 종목), `dividend`(고배당 인컴주), `custom`(사용자 지정 관심주) |

### 3.2 `summary.{TICKER}` 섹션 (경량 가격 정보)

| key | 설명 |
|:---|:---|
| `currentPrice` | 최신 체결가 (장외 포함) |
| `previousClose` | 전 거래일 종가 |
| `change` | 가격 변동폭 (`currentPrice - previousClose`) |
| `changePercent` | 등락률 (%) |
| `marketState` | 해당 종목의 현재 장 상태 (`REGULAR`, `PRE`, `POST`, `CLOSED` 등) |
| `shortName` | 회사 약칭 |
| `marketCap` | 시가총액 (원시 숫자) |
| `dayHigh` / `dayLow` | 당일 정규장 고가 / 저가 |
| `open` | 당일 정규장 시가 |
| `volume` | 정규장 거래량 |
| `fiftyTwoWeekHigh` / `fiftyTwoWeekLow` | 52주 최고가 / 최저가 |

---

## 4. `data/{TICKER}.json` - 종목 상세 파일

### 4.1 `fundamentals` 섹션 (전체 가격 + 기업 정보)

**실시간 가격 필드:**

| key | 설명 |
|:---|:---|
| `currentPrice` | 최신 체결가 (실시간) |
| `regularMarketPrice` | **정규장** 최종 체결가 |
| `regularLastPrice` / `regularLastTime` | 정규장 마지막 체결가 및 시간 |
| `regularMarketOpen` | 정규장 시가 |
| `regularMarketDayHigh` / `regularMarketDayLow` | 정규장 당일 최고가/최저가 |
| `regularMarketPreviousClose` / `previousClose` | 정규장 이전 거래일 종가 / 통합 이전 거래일 종가 |
| `extendedLastPrice` / `extendedLastTime` | 장외(Pre/Post) 마지막 체결가 및 시간 |
| `extendedLastType` | 장외 체결 타입 (e.g. PRE, POST) |
| `marketState` | 세션 상태: `REGULAR`, `PRE`, `POST`, `OVERNIGHT`, `CLOSED` |
| `trailingPE` / `forwardPE` | 주가수익비율 (Trailing / Forward) |
| `priceToBook` | 주가순자산비율 (P/B) |
| `dividendYield` | 배당수익률 (예: 0.02 = 2%) |
| `beta` | 시장 변동성 지수 (1.0 기준) |

**등락 계산 가이드 (UI 구현 시):**
- **실시간 현재가**: `currentPrice`를 메인으로 표시합니다.
- **정규장 등락**: `regularMarketPrice - previousClose`로 계산합니다.
- **장외 등락**: `marketState`가 `REGULAR`가 아닐 경우, `currentPrice - regularMarketPrice`로 계산하여 별도 표시할 수 있습니다.

**재무/기술적 정보:**
- 시가총액: `marketCap`, 범위: `dayLow`/`dayHigh`/`fiftyTwoWeekLow`/`fiftyTwoWeekHigh`
- 이동평균: `fiftyDayAverage`, `twoHundredDayAverage`
- `52WeekChange`: 52주 주가 변동률
- `SandP52WeekChange`: 동일 기간 S&P 500 변동률 (벤치마크)
- `revenueGrowth`: 매출 성장률 (YoY)
- `grossMargins` / `profitMargins`: 매출총이익률 / 순이익률
- `shortRatio`: 공매도 잔고 비율 (Cover days)
- `debtToEquity`: 부채비율
- `overallRisk`: 종합 리스크 점수 (1~10, 낮을수록 안전)

### 4.2 `candles.{timeframe}` 섹션 (차트 데이터)

| 타임프레임 | 기간 | 장외 포함? |
|:---|:---|:---|
| `1h` | 최근 1개월 (1시간봉) | ✅ Pre/Post/Overnight |
| `1d` | 최근 1년 (일봉) | ❌ 정규장 전용 |
| `1w` | 최근 2년 (주봉) | ❌ 정규장 전용 |

**캔들 오브젝트 필드:**
- `time`: 봉 시작 시간 (ISO 8601)
- `Open`, `High`, `Low`, `Close`: OHLC 가격
- `Volume`: 거래량
- `rsi`: RSI 14 (Wilder's Smoothing 방식, TradingView 기준과 동일)
- `mfi`: MFI 14 (거래량 가중 모멘텀)

---

## 5. `indices_summary.json` - 주요 시장 지수

시장 전체 흐름 파악을 위한 데이터입니다. 구조는 `market_summary.json`과 동일합니다.

| 티커 | 정식 명칭 | 설명 |
|:---|:---|:---|
| `^NDX` | Nasdaq 100 | 기술주 중심 100개 종목 |
| `^GSPC` | S&P 500 | 미국 대표 500개 대형주 |
| `^DJI` | Dow Jones | 우량주 30개 (다우 지수) |
| `^RUT` | Russell 2000 | 중소형주 2000개 |
| `^VIX` | VIX | 공포 심리 지수 (CBOE Volatility) |
| `^TNX` | 10Y Treasury | 미 10년물 국채 금리 (시장 금리 지표) |
| `^SOX` | PHLX Semiconductor | 필라델피아 반도체 지수 |

> [!TIP]
> **VIX**와 **TNX**는 일반 종목과 달리 거래량(Volume)이 `0`이거나 없을 수 있습니다. 차트 구현 시 이 점을 유의하세요.

---

## 6. 티커 관리 및 확장 구조 (`tickers.json`)
 
티커 목록은 코드에 하드코딩되지 않고 `tickers.json` 중앙 설정 파일에서 동적으로 관리됩니다.
 
```
/
├── tickers.json              ← 전체 주식 및 지수 정의 (카테고리/섹터/우선순위 메타데이터 포함)
├── sync_tickers.py           ← S&P 500 등 대규모 티커 자동 동기화 및 보강 스크립트
├── supported_tickers.json    ← 587개 전체 지원 티커 검증 인덱스
```

### 6.1 지원 카테고리 및 그룹 (총 587개)
- **Indices (7개)**: `^NDX`, `^GSPC`, `^DJI`, `^RUT`, `^SOX`, `^VIX`, `^TNX`
- **Major ETFs (37개)**:
  - 지수: `SPY`, `QQQ`, `DIA`, `IWM`, `VOO`, `IVV`, `VTI`
  - 반도체/테크: `SOXX`, `SMH`, `XLK`, `VGT`
  - 레버리지/인버스: `TQQQ`, `SQQQ`, `SOXL`, `SOXS`, `NVDL`, `TSLL`
  - 배당/채권: `SCHD`, `JEPI`, `JEPQ`, `TLT`, `IEF`, `SHY`, `BND`
  - 원자재/암호화폐: `GLD`, `SLV`, `USO`, `IBIT`
  - 섹터: `XLE`, `XLF`, `XLV`, `XLI`, `XLP`, `XLY`, `XLU`, `XLRE`, `GDX`
- **Core Tech (30개)**: `NVDA`, `AAPL`, `MSFT`, `GOOGL`, `AMZN`, `META`, `TSLA`, `AVGO`, `COST`, `ASML` 등
- **Nasdaq 100 Mainstream (59개)**: `ADI`, `PYPL`, `PANW`, `SNPS`, `CDNS`, `CSX`, `MAR`, `KLAC`, `PDD`, `MNST` 등
- **Growth & Strategic (30개)**: `TSM`, `ARM`, `PLTR`, `U`, `SNOW`, `SQ`, `COIN`, `RIVN`, `SOFI` 등
- **S&P 500 Universe (424개)**: `JPM`, `V`, `MA`, `BAC`, `UNH`, `JNJ`, `LLY`, `XOM`, `PG`, `KO`, `WMT`, `MMM` 등 S&P 500 구성 전 종목

### 6.2 티커 확장 방법 및 계층화 수집
1. `tickers.json` 파일의 `stocks` 또는 `indices` 객체에 신규 티커 심볼 및 섹터 정보를 추가합니다.
2. `python sync_tickers.py` 스크립트를 실행하여 Wikipedia 최신 S&P 500 전 종목을 언제든 자동으로 동기화할 수 있습니다.
3. 데이터 수집 파이프라인은 계층화 정책(`--tier core` vs `--tier all`)을 적용하여 장중에는 핵심 74개 종목을 20~25초 내에 안전하게 수집하고, 전체 587개 종목의 실시간 시세는 청크 배치(`update_realtime.py`)로 갱신하여 야후 파이낸스 차단이나 타임아웃 없이 안정적으로 운영됩니다.


---
 
## 7. 티커 지원 여부 조회 API (`supported_tickers.json`)
 
외부 서비스나 클라이언트에서 특정 티커(단일 또는 복수)를 이 서버가 지원하고 있는지 고속으로 검증할 수 있는 전용 엔드포인트를 제공합니다.
 
- **엔드포인트 URL**: `https://raw.githubusercontent.com/{owner}/{repo}/main/supported_tickers.json` (또는 로컬/Pages의 `supported_tickers.json`)
- **응답 속도**: 정적 경량 JSON(~21KB)으로 O(1) 해시 룩업 가능
 
### 7.1 데이터 스키마
 
```json
{
  "metadata": {
    "last_updated_ny": "2026-09-25T19:59:22.206883-04:00",
    "total_supported": 198,
    "description": "API for verifying supported stock/index tickers"
  },
  "tickers": ["^NDX", "^GSPC", "NVDA", "AAPL", "GOOGL", ...],
  "map": {
    "GOOGL": {
      "supported": true,
      "type": "stock",
      "name": "Alphabet Inc.",
      "sector": "tech",
      "category": "core"
    },
    "^NDX": {
      "supported": true,
      "type": "index",
      "name": "Nasdaq 100",
      "sector": "indices",
      "category": "indices"
    }
  }
}
```
 
### 7.2 단일 / 복수 티커 조회 클라이언트 코드 예제
 
#### JavaScript / TypeScript
 
```javascript
// 단일 티커 확인: checkTicker("GOOGL")
// 복수 티커 확인: checkTickers(["GOOGL", "AAPL", "XYZ"]) 또는 checkTickers("GOOGL, AAPL, XYZ")

async function checkTickers(input) {
  const resp = await fetch('supported_tickers.json?t=' + Date.now());
  const data = await resp.json();
  const map = data.map || {};

  const queryList = Array.isArray(input) 
    ? input 
    : String(input).split(/[\s,]+/).map(t => t.trim().toUpperCase()).filter(Boolean);

  const results = {};
  const supported = [];
  const unsupported = [];

  queryList.forEach(ticker => {
    if (map[ticker]) {
      results[ticker] = { supported: true, ...map[ticker] };
      supported.push(ticker);
    } else {
      results[ticker] = { supported: false };
      unsupported.push(ticker);
    }
  });

  return {
    query: queryList,
    totalQueried: queryList.length,
    supportedCount: supported.length,
    unsupportedCount: unsupported.length,
    allSupported: unsupported.length === 0,
    supported,
    unsupported,
    results
  };
}

// 사용 예시
checkTickers("GOOGL, AAPL, INVALID_TICKER").then(res => {
  console.log(res);
  /* 출력 예시:
  {
    query: ["GOOGL", "AAPL", "INVALID_TICKER"],
    totalQueried: 3,
    supportedCount: 2,
    unsupportedCount: 1,
    allSupported: false,
    supported: ["GOOGL", "AAPL"],
    unsupported: ["INVALID_TICKER"],
    results: {
      "GOOGL": { supported: true, type: "stock", name: "Alphabet Inc.", sector: "tech", category: "core" },
      "AAPL":  { supported: true, type: "stock", name: "Apple Inc.", sector: "tech", category: "core" },
      "INVALID_TICKER": { supported: false }
    }
  }
  */
});
```

#### Python
 
```python
import requests

def check_tickers(symbols):
    """'GOOGL' 단일 문자열 또는 'GOOGL, AAPL' 콤마 구분 또는 리스트 조회 가능"""
    if isinstance(symbols, str):
        query = [s.strip().upper() for s in symbols.replace(",", " ").split() if s.strip()]
    else:
        query = [str(s).strip().upper() for s in symbols]

    url = "supported_tickers.json"  # 또는 원격 GitHub raw URL
    data = requests.get(url).json()
    ticker_map = data.get("map", {})

    results = {}
    supported = []
    unsupported = []

    for sym in query:
        if sym in ticker_map:
            results[sym] = {"supported": True, **ticker_map[sym]}
            supported.append(sym)
        else:
            results[sym] = {"supported": False}
            unsupported.append(sym)

    return {
        "query": query,
        "all_supported": len(unsupported) == 0,
        "supported": supported,
        "unsupported": unsupported,
        "results": results
    }

# 사용 예시:
print(check_tickers("GOOGL, AAPL, UNKNOWN"))
```

#### cURL / CLI
 
```bash
# 전체 지원 목록 및 맵 다운로드
curl -s https://raw.githubusercontent.com/{owner}/{repo}/main/supported_tickers.json | jq '.map["GOOGL"]'

# 복수 티커 일괄 확인 (jq 필터)
curl -s supported_tickers.json | jq '{
  GOOGL: (.map["GOOGL"] // {supported: false}),
  AAPL: (.map["AAPL"] // {supported: false}),
  XYZ: (.map["XYZ"] // {supported: false})
}'
```

---

## 8. 개발자 팁

1. **Null 처리**: 배당이 없는 종목 등 일부 필드는 `null`로 반환됩니다. 반드시 체크하세요.
2. **티커 존재 확인**: `summary.{ticker}`가 있는지 먼저 확인하세요.
3. **숫자 포맷**: API는 원시 숫자를 반환합니다. `Intl.NumberFormat` 등으로 포맷팅하세요.
4. **캐시 전략**: 한 번 로드된 `data/{TICKER}.json`은 메모리에 캐시하여 중복 요청을 방지하세요.
5. **데이터 실패**: 야후 파이낸스에서 데이터를 제공하지 않거나 일시적 오류 발생 시, 해당 티커의 필드는 `null`이거나 파일 자체가 `{}`(빈 객체)일 수 있습니다. (예: `SQ`, `WBA` 등 종목은 상장 거래소나 일시적 문제로 데이터가 비어있을 수 있음)
6. **장외 시간**: `1h` 캔들은 장외(Pre/Post) 데이터를 포함하지만, `1d`와 `1w`는 정규장 데이터만 포함합니다. 정밀한 차트 구현 시 참고하세요.

---

## 9. 러셀/소형 핫 티커 순환 발굴 시스템 (Trending Tickers)

Russell 2000(`IWM`) 등 2,000여 개 소형주를 무차별 수집하면 GitHub Actions 런타임 한도(6시간) 및 Yahoo Finance Rate Limit에 걸리게 됩니다. 이를 해결하기 위해 시장의 관심과 거래량이 집중되는 핫 종목(예: `NBIS`, `BE`)을 지속 가능하고 자동화된 방식으로 발굴하는 **3대 소스 회전식(Rotation) 순환 엔진**을 운영합니다.

### 9.1 3대 소스 순환 로직 (`sync_hot_tickers.py`)

주간 단위로 소스를 A → B → C 순서로 자동 전환하며 최신 트렌드를 다각도에서 수집합니다:

- **Source A (소형주/러셀 ETF 상위 홀딩스)**:
  - `IWM`(iShares Russell 2000 ETF), `VB`(Vanguard Small-Cap ETF) 등의 Top Holdings에서 비중 상위 종목 발굴.
- **Source B (Yahoo Finance Trending & Small-Cap Screener)**:
  - Yahoo Finance 미국 트렌딩 종목(`trending/US`), `aggressive_small_caps`, `day_gainers` 스크리너를 조회하여 시장 주목도가 급상승한 종목 발굴.
- **Source C (Russell 2000 거래량/모멘텀 서지 스캔)**:
  - 러셀 핵심 종목군 중 당일 거래량 폭발(거래량 상위) 및 모멘텀 급등 종목 발굴.
- **Source D (고배당/인컴 ETF 및 스크리너)**:
  - `SCHD`, `VYM`, `SPYD` 등 대표 배당 ETF 상위 홀딩스 및 Yahoo High Yield Dividend 스크리너에서 우량 고배당주 발굴.

### 9.2 핵심 영구 보장 종목 (Pinned Tickers)

순환 갱신 시에도 사용자가 지정한 핵심 핫 종목은 탈락하지 않고 영구 유지됩니다:
- `NBIS` (Nebius Group N.V. - AI 인프라)
- `BE` (Bloom Energy Corp - 친환경 수소/전력)
- `AB` (AllianceBernstein Holding L.P. - 고배당 ~10% 자산운용)

### 9.3 핫 종목 데이터 수집 및 UI 표기

1. **Intraday Heavy 수집 (30분 주기)**:
   - `trending` 및 `dividend` 카테고리 종목은 `collect_data.py --tier core`에 자동으로 포함되어 장중 30분마다 캔들(1h/1d/1w)이 실시간 갱신됩니다.
2. **프론트엔드 대시보드 (`index.html`)**:
   - 사이드바 카테고리 탭에 `🔥 Hot` 필터가 제공됩니다.
   - 종목 리스트에서 `HOT`(모멘텀) 및 `DIV`(고배당) 뱃지가 심볼 옆에 시각적으로 강조 표시됩니다.
3. **자동화 워크플로우 (`.github/workflows/sync_hot_tickers.yml`)**:
   - 매주 월요일 08:00 UTC (17:00 KST / 03:00 ET)에 자동 실행되며, 수동(`workflow_dispatch`) 실행도 지원합니다.

---

## 10. 범용 온디맨드 티커 추가 도구 (`add_ticker.py`)

S&P 500에 속하지 않는 중형주(Mid-Cap), L.P.(합자회사), 고배당 인컴주, 신규 상장주 등 사용자가 원하는 종목을 단 한 줄의 명령어로 즉시 시스템에 편입할 수 있는 유틸리티를 제공합니다.

### 10.1 사용법

```bash
# 1. 단일 종목 추가 (yfinance에서 종목명, 섹터 자동 추출)
python add_ticker.py AB

# 2. 카테고리 및 우선순위 지정 추가 (dividend 카테고리로 지정)
python add_ticker.py AB --category dividend --priority high

# 3. 복수 종목 동시 추가
python add_ticker.py AB MO O BTI --category dividend

# 4. 추가 즉시 캔들 데이터(data/AB.json)까지 수집
python add_ticker.py AB --collect
```

### 10.2 자동 수행 작업
1. **yfinance 유효성 검증**: 심볼 오타나 상장폐지 여부 판별, 정식 기업명 및 섹터(예: Financial Services -> finance) 자동 매핑.
2. **`tickers.json` 안전 병합**: 중복 방지 및 정렬 저장.
3. **`supported_tickers.json` 즉시 갱신**: 클라이언트/서버에서 즉시 O(1) 질의 가능하도록 지원 인덱스 동기화.
4. **`--tier core` 수집 연동**: `priority: high`로 등록되어 장중 30분 캔들 수집 대상에 즉시 편입.

---

## 11. 서버 생성 AI 분석 프롬프트 API (`ai_prompts`)

LLM(Gemini, ChatGPT, Claude 등)에 전달하여 전문가급 금융 분석 리포트를 생성할 수 있는 사전 정형화된 영문 프롬프트 템플릿을 API 서버에서 직접 제공합니다.

### 11.1 특징 및 설계 원칙
- **영문 프롬프트 기반 정밀 추론**: 프롬프트 본문 및 분석 체계는 영어로 작성되어 최신 LLM의 금융 팩터 추론 능력 및 토큰 압축 효율을 극대화합니다.
- **결론 및 요약 우선 제시 구조 (Conclusion & Executive Summary First, BLUF)**:
  - 프롬프트 JSON에 `"mandatory_response_structure"` 필드가 명시적으로 포함되어 있으며, `system_directive`, `analysis_guidelines`, `task` 전 영역에서 **최종 결론, 투자 의견(Strong Buy 🚀 / Buy ⬆️ / Hold ➖ / Sell ⬇️ / Strong Sell 💥), 핵심 요약을 답변 최상단(Section 1)에 먼저 작성**하도록 강제합니다. 그 후 세부 데이터 및 근거 분석(Section 2)을 전개합니다.
  - **Section 1 실질적 투자 판단 5대 핵심 구성 (두괄식 필수 출력)**:
    - **종합 펀더멘털 분석 (`comprehensive`)**:
      1. **간결한 분석 대상 (Concise Target)**: 긴 법적 회사명/거래소 접미사 없이 티커와 약칭만 표기 (예: `종목: GRAB (Grab Holdings)`, `지수: ^NDX (나스닥 100)`, `ETF: QQQ (Invesco QQQ)`).
      2. **확정적 투자의견 (Definitive Investment Verdict)**: 5단계 시그널(🚀⬆️➖⬇️💥)과 1줄 핵심 확신 사유.
      3. **목표가 스펙트럼 및 상승여력 (Target Price Spectrum & Upside %)**: 월가 컨센서스 중앙값 vs 모델 목표가 및 기대 수익률(%).
      4. **3대 핵심 투자 논거 (3 Core Investment Theses)**:
         - 펀더멘털 실적 성장 (매출/이익 성장성, 흑자 전환, 마진 개선)
         - 밸류에이션 및 멀티플 (P/E, PEG, P/S 동종업계 대비 저평가 근거)
         - 기술적/하방 지지력 (RSI/MFI 과매도 반등, 순현금 기반 하방 경직성)
      5. **실행 전략 구간 및 핵심 리스크 (Actionable Strategy & Key Downside Risk)**: 권장 진입 밴드, 이탈/손절 기준가, 주시해야 할 1대 핵심 하방 리스크.
    - **기술적 모멘텀 분석 (`momentum`)**:
      1. **간결한 분석 대상 (Concise Target)**: 티커 및 회사 약칭만 간결히 표기.
      2. **확정적 매매 시그널 (Definitive Trade Signal)**: 5단계 시그널(🚀⬆️➖⬇️💥)과 1줄 모멘텀 확신 사유.
      3. **핵심 트레이딩 레벨 및 손익비 (Key Trading Levels & RRR)**: 현재가, 권장 진입 밴드, 돌파 트리거, TP1/TP2, 손절가, 정량적 손익비 공식 계산값(`RRR = (TP1 - Entry) / (Entry - Stop)`).
      4. **3대 핵심 모멘텀 논거 (3 Core Momentum Theses)**:
         - 다중 타임프레임 추세 정렬 (주봉 추세 필터, 일봉 스윙 방향성, 시간봉 정밀 진입 타이밍)
         - 프라이스 액션 및 캔들 매물대 (캔들 패턴, 윗꼬리/밑꼬리 거부 반응, 지지/저항 매물대 Volume Shelf)
         - 거래량 및 오실레이터 플로우 (거래량 확증 VSA, RSI/MFI 과열 해소/다이버전스, MACD 모멘텀)
      5. **실행 전략 및 손절/이탈 리스크 (Actionable Execution & Invalidation Risk)**: 트리거 기반 분할 매수(Scale-in) / 트레일링 스탑 규칙 및 1줄 무효화 기준선.
  - **Section 2 세부 분석 및 데이터 근거 (Comprehensive Breakdown)**:
    - 팩터별 상세 분석(밸류에이션, 피어 그룹 비교, 재무제표 건전성, 이동평균/오실레이터, 매크로 연계 및 시나리오 모델링)을 서두의 결론을 뒷받침하는 순서로 전개.
- **무결점 가독성 및 불필요 메타데이터 배제 지침 (Clean Presentation & High Readability)**:
  - **내부 타임스탬프 및 감사 로그 노출 전면 금지**: 데이터 스냅샷 기준 시각, 라이브 컨텍스트 시각, 환율 시각 등은 AI의 내부 최신성 검증 및 데이터 필터링용 앵커로만 활용되며, **사용자 대면 최종 리포트에는 일체 출력하지 않음**.
  - **원시 URL 및 하이퍼링크 전면 금지**: 지저분한 웹 링크나 마크다운 앵커(`([https://...](...))`) 출력을 엄격히 차단하고 정갈한 플레인 텍스트로만 정보 전달.
  - **모바일/웹 시각적 가독성 극대화**: 핵심 수치와 멀티플은 볼드체(`**`), 간결한 불릿 포인트(`•`), 불필요한 줄글 벽 및 중첩 괄호 지양.
- **토큰 효율성 및 인라인 직렬화 최적화 (Token Diet & Signal-to-Noise Max)**:
  - **인라인 JSON 직렬화**: 1차원 캔들/스칼라 배열을 한 줄로 인라인 압축하여 불필요한 공백과 줄바꿈 토큰을 약 35~50% 대폭 절감했습니다.
  - **시간대별 캔들 최적화**: 
    - 모멘텀(`momentum`): 최근 2거래일 1시간 봉 15개(`1_hour_recent_15`), 1개월 일봉 20개(`1_day_recent_20`), 2.5개월 주봉 10개(`1_week_recent_10`)로 노이즈를 덜어내고 즉각적 프라이스 액션에 집중.
    - 종합 분석(`comprehensive`): 펀더멘털 분석 맥락을 흐리는 30개 일중 시간봉을 배제하고, 일봉 15개(`1d_recent_15`)와 주봉 12개(`1w_recent_12`)로 중장기 트렌드 채널을 제공.
- **고부가가치 금융 팩터 사전 계산 주입**:
  - **3단계 밸류에이션 궤적**: TTM P/E(`trailing_pe`) → 올해 추정 P/E(`current_year_pe`) → 내년 선행 P/E(`forward_pe`)를 동시에 제공하여 실적 개선/악화 멀티플 궤적을 직관적으로 제공.
  - **목표가 상승여력 사전 계산 (`target_median_upside`)**: 월가 애널리스트 목표주가 중간값 대비 현재가 상승/하락 여력(%)을 사전 연산하여 LLM의 산술 연산 오류를 방지.
  - **공매도 전월 대비 증감률 (`short_interest_mom_change`)**: 숏커버링 진행 여부 또는 숏 베팅 증가 추세를 즉각 진단.
  - **이동평균 정배열 추세 (`sma_50_vs_200_trend`)**: SMA 50과 SMA 200의 골든크로스 / 데드크로스 배열 상태를 명시.
  - **재무제표 보고 분기 시점 (`latest_reporting_quarter`)**: 제공된 재무 데이터의 가장 최근 분기 결산일을 표기.
  - **ETF 기관급 가치 지표**: ETF 편입종목 가중 P/E(`fund_trailing_pe`), 가중 P/B(`fund_price_to_book`), 최근 3개월 수익률(`trailing_three_month_return`), 펀드 설정일(`fund_inception_date`) 보강.
- **선택적 언어 지정 및 사용자 모국어 자동 감지 (`{{RESPONSE_LANGUAGE}}`)**:
  - **옵셔널(Optional) 설계**: `{{RESPONSE_LANGUAGE}}` 치환은 **완전한 선택 사항**입니다. API 호출처에서 이 값을 치환하지 않거나 생략하더라도, LLM이 사용자의 선호 언어 및 모국어(대화 맥락/로케일, 한국어 사용 환경이면 한국어)를 스스로 감지하여 최적의 언어로 자연스럽게 답변하도록 프롬프트 기본 지침(`LANGUAGE DIRECTIVE`)이 설계되어 있습니다.
  - **앱 레벨 언어 강제**: 앱 설정 등에서 특정 언어(예: `"Korean"`, `"English"`, `"Japanese"`)를 명시적으로 고정하고자 할 경우에만 `{{RESPONSE_LANGUAGE}}`를 해당 언어 문자열로 치환하면 되며, 치환하지 않은 순수 JSON 그대로 LLM에 전달해도 완벽하게 동작합니다.
- **조건부 실시간 웹 검색 및 내부 추론 규칙 (Conditional Real-Time Research)**:
  - **웹 검색 도구 지원 환경 (Mode A)**: 최신 실시간 주가(정규장/시간외 거래가) 및 72시간 이내의 실적/공시 촉매를 웹 검색으로 보강하여 내부 추론 및 팩터 분석에 반영합니다. 단, 확인된 시각 정보는 AI 내부 검증용으로만 사용하며 리포트에 감사 블록 형태로 노출하지 않습니다.
  - **웹 검색 미지원 환경 (Mode B)**: 웹 검색 도구가 비활성화된 일반 LLM 환경에서는 제공된 `data_baseline` 스냅샷 데이터에만 엄격히 기반하여 분석을 수행하며, 실시간 가격이나 수치를 임의로 날조(Hallucination)하는 것을 원천 금지합니다.
- **스냅샷 기준 시점 메타데이터 (`data_baseline`)**: 프롬프트 생성 시각(`snapshot_generated_at`), 가격 데이터 체결 시각(`price_data_as_of`), 장 상태(`market_state`)를 프롬프트 최상단에 명시하여 LLM이 데이터 기준점을 정확히 인지하도록 보장합니다.
- **2가지 제공 엔드포인트**:
  1. **상세 데이터 파일 내장 (`data/{TICKER}.json`의 `ai_prompts` 필드)**: 이미 상세 데이터를 조회한 앱은 추가 네트워크 요청 0회로 프롬프트 즉시 획득.
  2. **경량 전용 엔드포인트 (`prompts/{TICKER}.json`)**: 100KB+ 캔들 전체를 받을 필요 없이 프롬프트 전용 엔드포인트를 제공하여 외부 앱 또는 챗봇에서 초경량 연동 가능.

### 11.2 수치 데이터 시계열(Time Horizons) 구조 비교

| 분석 유형 | 주요 시계열 성격 | 주입되는 핵심 수치 데이터 및 범위 |
|:---|:---|:---|
| **AI 모멘텀 분석**<br>(`momentum`) | **초단기 ~ 단기<br>(Intraday ~ Days)** | • **초단기 (Intraday)**: 당일 실시간 체결가, 등락률, 풀데이 등락률(`fullday_change`), 시가/고가/저가, 프리·애프터마켓 시세, 최근 15개 1시간 봉(`1_hour_recent_15`)<br>• **단기 (Days)**: 최근 20개 일봉(`1_day_recent_20`), 10일/3개월 평균 거래량, 다중 타임프레임(1H/1D/1W) RSI, MFI, MACD 지표<br>• **중기 보조 (Weeks/Months)**: 최근 10개 주봉(`1_week_recent_10`), SMA 50/200 이평 정배열 상태(`sma_50_vs_200_trend`), 52주 고점/저점 괴리율, 공매도 잔고/비율 및 전월비 증감률(`short_interest_mom_change`) |
| **AI 종합 분석**<br>(`comprehensive`) | **단기 + 중기 + 장기<br>통합 멀티 팩터<br>(Days ~ Months ~ Years)** | • **단기 (Short-term, Days)**:<br>  - 최신 체결가, 당일 등락률, 풀데이 등락률, 시가/고가/저가/전일종가, 당일 거래량, 10일 평균 거래량<br>  - 프리마켓/애프터마켓 거래가 및 등락률, 최신 다중 타임프레임(1H/1D/1W) RSI/MFI/MACD 오실레이터 지표<br>  - 차기 실적발표일 카운트다운 및 추정 여부(`is_earnings_date_estimate`), 조건부 72시간 이내 실시간 촉매/뉴스 검색 데이터<br>• **중기 (Medium-term, Weeks ~ Months ~ 1 Year)**:<br>  - 3개월 평균 거래량, 50일 이동평균선(SMA 50) 및 200일 이동평균선(SMA 200) 괴리율, 이동평균 정배열 상태(`sma_50_vs_200_trend`)<br>  - 52주 최고가/최저가 범위 및 현재가 대비 괴리율, 1년 누적 수익률 및 S&P 500 상대 알파<br>  - 최근 15개 일봉(`1d_recent_15`) 및 12개 주봉(`1w_recent_12`)<br>  - 최근 결산 분기 시점(`latest_reporting_quarter`): 매출성장률(YoY), 순이익성장률(YoY), 분기이익성장률(Quarterly Growth)<br>  - 월가 12개월 목표주가(최저/중간/평균/최고) 및 목표가 중간값 대비 상승여력(`target_median_upside`), 투자의견 평점, 애널리스트 수<br>  - 공매도 잔고 추이: 유통주식수 대비 공매도 비중(%), 숏커버 일수, 공매도 잔고 및 전월비 증감률(`short_interest_mom_change`)<br>  - 1년 베타 변동성 및 내재변동성(IV), 최근 배당락일 및 배당 지급액<br>• **장기 (Long-term, Multi-Year ~ Fundamental Secular)**:<br>  - 역대 최고가/최저가(ATH / ATL)<br>  - 5년 평균 배당수익률, 연간 배당금 및 배당성향(Payout Ratio)<br>  - 밸류에이션 궤적: Trailing P/E (TTM), Current Year P/E (올해 추정), Forward P/E (내년 선행), PEG, Trailing PEG, P/B, P/S, EV/EBITDA, EV/Revenue<br>  - 주당 본질가치 지표: Trailing/Forward EPS, BPS(주당순자산), SPS(주당매출), CPS(주당현금)<br>  - 대차대조표 및 지급능력(Solvency): 총현금, 총부채, 순부채(Net Debt), 부채비율(D/E), 유동비율, 당좌비율<br>  - 수익성 및 자본효율성: 매출총이익률(Gross Margin), 영업이익률, 순이익률, EBITDA 마진, ROE(자기자본이익률), ROA(총자산이익률)<br>  - 현금흐름 창출력: 영업현금흐름(OCF), 잉여현금흐름(FCF), 설비투자(CapEx), 연구개발비(R&D)<br>  - 지배구조 리스크: ISS Governance Risk Scores (Audit, Board, Compensation, Shareholder Rights)<br>  - 피어 벤치마크: 동일 섹터 3대 경쟁사 상대 멀티플 비교표(`peer_group_valuation_benchmark`)<br>  - ETF 전용 멀티플: 펀드 P/E(`fund_trailing_pe`), 펀드 P/B(`fund_price_to_book`), 최근 3개월 수익률(`trailing_three_month_return`), 설정일(`fund_inception_date`)<br>  - 거시 환경 레짐: 미국 10년물 국채금리(^TNX), VIX 변동성 심리 구간 (`macro_environment_context`) |

### 11.3 경량 전용 엔드포인트 응답 규격 (`prompts/{TICKER}.json`)

```json
{
  "symbol": "AAPL",
  "generated_at": "2026-10-07T08:06:27.534888-04:00",
  "target_language_placeholder": "{{RESPONSE_LANGUAGE}}",
  "ai_app_targets": {
    "gemini": {
      "name": "Gemini",
      "web_url": "https://gemini.google.com/app",
      "ios_universal_link": "https://gemini.google.com/app?target=spark",
      "ios_scheme": "googlegemini://",
      "android_package": "com.google.android.apps.bard",
      "android_intent_uri": "intent://gemini.google.com/app#Intent;scheme=https;package=com.google.android.apps.bard;S.browser_fallback_url=https%3A%2F%2Fgemini.google.com%2Fapp;end"
    },
    "chatgpt": {
      "name": "ChatGPT",
      "web_url": "https://chatgpt.com/",
      "ios_universal_link": "https://chatgpt.com/#native",
      "ios_scheme": "chatgpt://",
      "android_package": "com.openai.chatgpt",
      "android_intent_uri": "intent://chatgpt.com/#Intent;scheme=https;package=com.openai.chatgpt;S.browser_fallback_url=https%3A%2F%2Fchatgpt.com%2F;end"
    },
    "claude": {
      "name": "Claude",
      "web_url": "https://claude.ai/new",
      "ios_universal_link": "https://claude.ai/new",
      "ios_scheme": "claude://",
      "android_package": "com.anthropic.claude",
      "android_intent_uri": "intent://claude.ai/new#Intent;scheme=https;package=com.anthropic.claude;S.browser_fallback_url=https%3A%2F%2Fclaude.ai%2Fnew;end"
    },
    "perplexity": {
      "name": "Perplexity",
      "web_url": "https://www.perplexity.ai/",
      "ios_universal_link": "https://www.perplexity.ai/search",
      "ios_scheme": "perplexity://",
      "android_package": "ai.perplexity.app.android",
      "android_intent_uri": "intent://www.perplexity.ai/#Intent;scheme=https;package=ai.perplexity.app.android;S.browser_fallback_url=https%3A%2F%2Fwww.perplexity.ai%2F;end"
    },
    "grok": {
      "name": "Grok",
      "web_url": "https://grok.com/",
      "ios_universal_link": "https://grok.com/",
      "ios_scheme": "grok://",
      "android_package": "ai.x.grok",
      "android_intent_uri": "intent://grok.com/#Intent;scheme=https;package=ai.x.grok;S.browser_fallback_url=https%3A%2F%2Fgrok.com%2F;end"
    }
  },
  "prompts": {
    "momentum": "{\n  \"analysis_type\": \"Technical Momentum Analysis\",\n  \"symbol\": \"AAPL\",\n  ...",
    "comprehensive": "{\n  \"analysis_type\": \"Comprehensive Quantitative Analysis\",\n  \"symbol\": \"AAPL\",\n  ..."
  }
}
```

### 11.4 클라이언트 연동 예제 (JavaScript / TypeScript)

```typescript
// 1. 경량 프롬프트 엔드포인트 호출 (또는 data/{ticker}.json의 ai_prompts 사용)
const res = await fetch(`https://stock-api.on-tap.xyz/prompts/AAPL.json`);
const data = await res.json();
const rawPrompt = data.prompts.comprehensive;

// 패턴 A (권장): 별도 언어 치환 없이 그대로 사용
// -> LLM이 사용자의 대화 맥락과 선호 언어(예: 한국어 사용 환경이면 한국어)를 자동 감지하여 답변합니다.
await navigator.clipboard.writeText(rawPrompt);

// 패턴 B (선택 사항): 앱 설정에서 특정 언어로 강제하고 싶은 경우
const appLanguage = "Korean"; // 또는 "English", "Japanese" 등
const finalPrompt = rawPrompt.replace(/\{\{RESPONSE_LANGUAGE\}\}/g, appLanguage);
await navigator.clipboard.writeText(finalPrompt);
```

### 11.5 파이썬 연동 예제

```python
import requests

res = requests.get("https://stock-api.on-tap.xyz/prompts/TSLA.json")
data = res.json()

# 패턴 A: 언어 치환 없이 그대로 LLM에 전송 (LLM이 사용자 선호/모국어로 자동 응답)
default_prompt = data["prompts"]["comprehensive"]

# 패턴 B: 명시적으로 특정 언어 강제 지정 (선택 사항)
custom_prompt = data["prompts"]["momentum"].replace("{{RESPONSE_LANGUAGE}}", "Korean")
```

### 11.6 AI 네이티브 앱 연동 및 딥링크 가이드 (Native AI App Handoff & Deep Linking)

프롬프트를 클립보드에 복사한 후 사용자를 AI 서비스(Gemini, ChatGPT, Claude, Perplexity, Grok)로 이동시킬 때, 모바일 기기에 **네이티브 앱이 설치되어 있다면 웹 브라우저 대신 앱으로 즉시 연결**하고 미설치 시 웹 URL로 자동 폴백(Fallback)하도록 구현할 수 있습니다. `prompts/{TICKER}.json` 및 `data/{TICKER}.json`의 `ai_prompts.ai_app_targets` 필드에 각 AI 서비스별 공식 검증된 딥링크/패키지 규격이 내장되어 있습니다.

#### 1) 주요 AI 서비스별 네이티브 앱 연동 표준 규격표

각 AI 서비스의 `.well-known/assetlinks.json`(Android App Links) 및 `.well-known/apple-app-site-association`(iOS Universal Links) 실측 검증 결과입니다.

| 서비스 (`key`) | Android Package ID | iOS Custom Scheme | iOS Universal Link (AASA 검증 경로) | 기본 Web Fallback URL |
|:---|:---|:---|:---|:---|
| **Gemini** (`gemini`) | `com.google.android.apps.bard` | `googlegemini://` | `https://gemini.google.com/app?target=spark` | `https://gemini.google.com/app` |
| **ChatGPT** (`chatgpt`) | `com.openai.chatgpt` | `chatgpt://` | `https://chatgpt.com/#native` | `https://chatgpt.com/` |
| **Claude** (`claude`) | `com.anthropic.claude` | `claude://` | `https://claude.ai/new` | `https://claude.ai/new` |
| **Perplexity** (`perplexity`) | `ai.perplexity.app.android` | `perplexity://` | `https://www.perplexity.ai/search` | `https://www.perplexity.ai/` |
| **Grok** (`grok`) | `ai.x.grok` | `grok://` | `https://grok.com/` | `https://grok.com/` |

> [!TIP]
> - **Gemini iOS Universal Link 주의사항**: `https://gemini.google.com/` 루트 경로는 Apple AASA 매칭에서 제외되며, `/app?target=spark` 경로가 공식 Universal Link 엔트리입니다.
> - **ChatGPT iOS Universal Link 주의사항**: OpenAI의 AASA는 일반 웹 루트(`/`)를 인앱 전환에서 제외하고 `https://chatgpt.com/#native` 프래그먼트 경로를 인앱 새 대화 시작용 Universal Link로 지정하고 있습니다.
> - **모바일 타이머(Countdown) 이후 이동 시 주의사항**: 3초 카운트다운 등 `setTimeout`/`setInterval` 내부에서 `window.open(..., '_blank')`을 호출하면 브라우저의 User Activation 만료로 팝업이 차단되거나 인앱 인터셉트가 실패합니다. 모바일에서는 `window.location.href`를 사용하면 네이티브 앱만 깔끔하게 실행되고 기존 웹 탭은 그대로 유지됩니다.

#### 2) 웹 / 모바일 브라우저 연동 예제 (TypeScript / JavaScript)

```typescript
export type AiServiceKey = 'gemini' | 'chatgpt' | 'claude' | 'perplexity' | 'grok';

export interface AiAppTarget {
  name: string;
  web_url: string;
  ios_universal_link: string;
  ios_scheme: string;
  android_package: string;
  android_intent_uri: string;
}

export const AI_APP_TARGETS: Record<AiServiceKey, AiAppTarget> = {
  gemini: {
    name: 'Gemini',
    web_url: 'https://gemini.google.com/app',
    ios_universal_link: 'https://gemini.google.com/app?target=spark',
    ios_scheme: 'googlegemini://',
    android_package: 'com.google.android.apps.bard',
    android_intent_uri:
      'intent://gemini.google.com/app#Intent;scheme=https;package=com.google.android.apps.bard;S.browser_fallback_url=https%3A%2F%2Fgemini.google.com%2Fapp;end',
  },
  chatgpt: {
    name: 'ChatGPT',
    web_url: 'https://chatgpt.com/',
    ios_universal_link: 'https://chatgpt.com/#native',
    ios_scheme: 'chatgpt://',
    android_package: 'com.openai.chatgpt',
    android_intent_uri:
      'intent://chatgpt.com/#Intent;scheme=https;package=com.openai.chatgpt;S.browser_fallback_url=https%3A%2F%2Fchatgpt.com%2F;end',
  },
  claude: {
    name: 'Claude',
    web_url: 'https://claude.ai/new',
    ios_universal_link: 'https://claude.ai/new',
    ios_scheme: 'claude://',
    android_package: 'com.anthropic.claude',
    android_intent_uri:
      'intent://claude.ai/new#Intent;scheme=https;package=com.anthropic.claude;S.browser_fallback_url=https%3A%2F%2Fclaude.ai%2Fnew;end',
  },
  perplexity: {
    name: 'Perplexity',
    web_url: 'https://www.perplexity.ai/',
    ios_universal_link: 'https://www.perplexity.ai/search',
    ios_scheme: 'perplexity://',
    android_package: 'ai.perplexity.app.android',
    android_intent_uri:
      'intent://www.perplexity.ai/#Intent;scheme=https;package=ai.perplexity.app.android;S.browser_fallback_url=https%3A%2F%2Fwww.perplexity.ai%2F;end',
  },
  grok: {
    name: 'Grok',
    web_url: 'https://grok.com/',
    ios_universal_link: 'https://grok.com/',
    ios_scheme: 'grok://',
    android_package: 'ai.x.grok',
    android_intent_uri:
      'intent://grok.com/#Intent;scheme=https;package=ai.x.grok;S.browser_fallback_url=https%3A%2F%2Fgrok.com%2F;end',
  },
};

export function launchAiService(serviceKey: AiServiceKey) {
  const target = AI_APP_TARGETS[serviceKey] || AI_APP_TARGETS.gemini;
  const ua = navigator.userAgent || '';
  const isAndroid = /Android/i.test(ua);
  const isIOS =
    /iPhone|iPad|iPod/i.test(ua) ||
    (/Macintosh/i.test(ua) && navigator.maxTouchPoints > 1);

  // 1. Android: Chrome intent:// URI 호출 (설치 시 네이티브 앱 즉시 실행, 미설치 시 S.browser_fallback_url로 자동 이동)
  if (isAndroid) {
    window.location.href = target.android_intent_uri;
    return;
  }

  // 2. iOS / iPadOS: Custom URL Scheme 우선 호출 후, 앱 미설치로 페이지가 숨겨지지 않으면 Universal Link / Web URL로 폴백
  if (isIOS) {
    let appOpened = false;
    const markAppOpened = () => {
      if (document.hidden || document.visibilityState === 'hidden') {
        appOpened = true;
      }
    };
    const onBlur = () => {
      appOpened = true;
    };

    document.addEventListener('visibilitychange', markAppOpened);
    window.addEventListener('pagehide', onBlur);
    window.addEventListener('blur', onBlur);

    window.location.href = target.ios_scheme;

    window.setTimeout(() => {
      document.removeEventListener('visibilitychange', markAppOpened);
      window.removeEventListener('pagehide', onBlur);
      window.removeEventListener('blur', onBlur);

      if (!appOpened && !document.hidden) {
        window.location.href = target.ios_universal_link;
      }
    }, 1200);
    return;
  }

  // 3. Desktop: 새 탭으로 웹 서비스 오픈
  window.open(target.web_url, '_blank', 'noopener,noreferrer');
}
```

#### 3) Android 네이티브 앱 (Kotlin / Flutter Android) 연동 예제

Android 11 (API 30) 이상에서는 외부 패키지 설치 여부를 조회(`getLaunchIntentForPackage`)하기 위해 `AndroidManifest.xml`에 `<queries>` 선언이 필수입니다.

**`AndroidManifest.xml`**:
```xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <queries>
        <package android:name="com.google.android.apps.bard" />
        <package android:name="com.openai.chatgpt" />
        <package android:name="com.anthropic.claude" />
        <package android:name="ai.perplexity.app.android" />
        <package android:name="ai.x.grok" />
    </queries>
</manifest>
```

**Kotlin 실행 코드**:
```kotlin
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.content.Intent
import android.net.Uri

fun copyPromptAndLaunchAi(
    context: Context,
    promptText: String,
    packageName: String = "com.google.android.apps.bard",
    fallbackWebUrl: String = "https://gemini.google.com/app"
) {
    // 1. 프롬프트 클립보드 복사
    val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
    clipboard.setPrimaryClip(ClipData.newPlainText("Stock AI Prompt", promptText))

    // 2. 네이티브 앱 설치 여부 확인 후 실행 (미설치 시 웹 브라우저 폴백)
    val launchIntent = context.packageManager.getLaunchIntentForPackage(packageName)?.apply {
        addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
    }

    if (launchIntent != null) {
        context.startActivity(launchIntent)
    } else {
        val webIntent = Intent(Intent.ACTION_VIEW, Uri.parse(fallbackWebUrl)).apply {
            addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
        }
        context.startActivity(webIntent)
    }
}
```

#### 4) iOS 네이티브 앱 (Swift / Flutter iOS) 연동 예제

iOS 9+ 환경에서 `UIApplication.shared.canOpenURL`을 사용하려면 `Info.plist`의 `LSApplicationQueriesSchemes`에 대상 스킴을 등록해야 합니다.

**`Info.plist`**:
```xml
<key>LSApplicationQueriesSchemes</key>
<array>
    <string>googlegemini</string>
    <string>chatgpt</string>
    <string>claude</string>
    <string>perplexity</string>
    <string>grok</string>
</array>
```

**Swift 실행 코드**:
```swift
import UIKit

func copyPromptAndLaunchAi(
    promptText: String,
    schemeUrl: String = "googlegemini://",
    fallbackWebUrl: String = "https://gemini.google.com/app"
) {
    // 1. 프롬프트 클립보드 복사
    UIPasteboard.general.string = promptText

    // 2. 커스텀 스킴으로 네이티브 앱 실행 시도 -> 실패 시 웹 URL 오픈
    if let appUrl = URL(string: schemeUrl), UIApplication.shared.canOpenURL(appUrl) {
        UIApplication.shared.open(appUrl, options: [:], completionHandler: nil)
    } else if let webUrl = URL(string: fallbackWebUrl) {
        UIApplication.shared.open(webUrl, options: [:], completionHandler: nil)
    }
}
```

---

## 12. API 제약 사항 및 클라이언트 보완 가이드 (Limitations & Client Enrichment Guide)

본 API는 15분 주기 실시간 시세 갱신(`update_realtime.py`) 및 일 1회 상세 배치 수집(`collect_data.py`) 기반의 정적 JSON 호스팅(GitHub Pages / 정적 CDN) 아키텍처로 운영됩니다.

이에 따라 클라이언트 앱(예: 안드로이드 `StockPromptBuilder.enrichServerPrompt()`)이 정적 스냅샷의 시차(Lag)를 인지하고 능동적으로 보완할 수 있도록 프롬프트 JSON 내에 `client_enrichment_hints` 메타데이터를 기본 탑재하고 있습니다.

### 12.1 정적 스냅샷 시차(Lag) 위험 필드 (`stale_risk_fields`)

장중 실시간 변동성이 극대화되는 시기나 정규장 개장 직전/직후(프리·애프터마켓)에는 서버 스냅샷 파일의 생성 시점과 클라이언트 실시간 시세 간에 수분~수십분의 시차가 발생할 수 있습니다. 다음 필드는 최신성 확인이 필수적입니다:

- `baseline_price` / `baseline_change`: 스냅샷 기준 체결가 및 등락률
- `market_state`: 정규장(`REGULAR`), 장전(`PRE`), 장후(`POST`), 휴장(`CLOSED`)
- `day_volume`: 당일 정규장 누적 거래량
- `pre_market_price` / `post_market_price`: 시간외 거래 시세
- `intraday_and_session_stats`: 일중 고가/저가/시가 세션 통계

### 12.2 클라이언트 실시간 보완 권장 키 (`recommended_client_enrichment_keys`)

클라이언트 환경에 실시간 틱/호가 스트리밍 연결(브로커리지 API 등)이 유지되고 있다면, 서버 프롬프트의 최하단에 다음 구조로 실시간 컨텍스트를 주입(Enrich)하는 것을 권장합니다:

```json
{
  "real_time_live_context": {
    "live_price": 182.45,
    "live_bid_ask_spread": "182.40 - 182.50 (Spread: $0.10)",
    "intraday_tick_direction": "UPTICK (+1.2% in last 10m)",
    "extended_hours_latest_trade": "183.10 (+0.35% in Post-market)",
    "breaking_news_catalyst_within_hours": "Q3 earnings released 15 mins ago: Revenue beat by +4.2%"
  }
}
```

> **안드로이드 앱 연동 예시 (`StockPromptBuilder.enrichServerPrompt`)**:
> 앱 내에 실시간 호가/시세 리스너가 존재하는 경우, 서버에서 가져온 `prompts/{TICKER}.json`을 수신한 후 로컬의 최신 틱 데이터를 `real_time_live_context` 노드로 결합하여 LLM에 전달하면 완벽한 실시간 분석이 완성됩니다.

### 12.3 API 구조상 지원 불가능한 기능 (`api_unsupported_data`)

GitHub Pages 정적 호스팅 기반 분할 아키텍처 특성상, 다음 4가지 영역은 API 서버 레벨에서 기술적으로 지원이 불가능하며 외부 실시간 데이터 소스나 클라이언트 브로커리지 피드로 보완해야 합니다:

| 미지원 항목 | 기술적 사유 | 권장 보완 방법 |
|:---|:---|:---|
| **Level-2 오더북 호가창 & 호가 잔량 뎁스** (Market Depth) | 호가 10단계 뎁스는 초당 수천 번의 갱신이 발생하므로 정적 JSON 파일로는 서빙 불가능 | 클라이언트의 브로커리지 실시간 호가 스트림 또는 웹 검색 활용 |
| **초 단위 틱 스트리밍 (Sub-second tick feed)** | WebSocket 양방향 스트리밍 미지원 (정적 CDN 한계) | 실시간 주식 틱 웹소켓 프로바이더 직접 연결 |
| **장중 실시간 ETF 순자산가치(iNAV) & 추적오차** | 지수산출기관의 실시간 바스켓 가격 피드 필요 | ETF 운용사 공시 페이지 웹 검색 또는 종가 기준 NAV 활용 |
| **기관 블록 딜 & 다크풀(Dark Pool) 테이프** | 유료 기관용 독점 결제망 데이터 | 분기별 13F 공시 및 Float 대비 공매도 비중 간접 분석 |

### 12.4 특화 자산(지수/ETF) 및 거시/피어 팩터 지원 안내

API 2.0 프롬프트 엔진은 단순 개별 주식뿐 아니라 지수, ETF, 거시 팩터를 차별화하여 제공합니다:

1. **지수 (Indices, `^NDX`, `^GSPC`, `^DJI`, `^SOX`, `^RUT` 등)**:
   - 기업 재무제표(부채비율, 마진, R&D 등) N/A를 전면 제거.
   - **`index_macro_profile`**: 지수 가중평균 선행 P/E 추정치, 지수 배당수익률, 상위 5~10대 앵커 종목 가중치(예: 나스닥 100의 NVDA, MSFT, AAPL 비중), 금리 민감도 제공.
2. **ETF (`SPY`, `QQQ`, `SOXX`, `SCHD`, `TLT` 등 35대 ETF)**:
   - **`etf_fund_profile`**: 운용사(Issuer), 운용보수(Expense Ratio), 벤치마크 지수, 상위 편입 종목, AUM, 순자산가치(NAV), 테마 분류, 추적오차 및 레버리지 디케이 리스크 제공.
3. **거시 환경 컨텍스트 (`macro_environment_context`)**:
   - 모든 프롬프트에 VIX 변동성 지수 및 시장 심리 구간 (`Complacent`, `Normal`, `Elevated Fear`, `Extreme Panic`), 미국 10년물 국채금리 (`^TNX`), S&P 500, 나스닥 100 벤치마크가 동시 주입됨.
4. **동종업계 피어 벤치마크 (`peer_group_valuation_benchmark`)**:
   - 개별 주식 분석 시 동일 섹터 시총 상위 3대 경쟁사의 핵심 멀티플(P/E, Forward P/E, 영업이익률, 매출성장률, 시총) 1줄 요약 테이블을 함께 전달하여 상대 밸류에이션 추론을 지원.

---

## 13. 티커 생명주기 및 3-Tier 계층형 용량 관리 (Ticker Lifecycle & Tiered Capacity)

본 API는 대규모 유니버스 확장(~900+개 종목)과 안정적인 실시간 서빙 성능을 양립하기 위해 **3-Tier 계층형 용량 관리 및 보수적 2-Strike 생명주기(Lifecycle) 엔진**(`manage_tickers_lifecycle.py`)을 운영합니다.

### 13.1 3-Tier 계층 분류 및 용량 쿼터 (Tier Quotas)

| 티어 (Tier) | 대상 종목군 | 쿼터 한도 (Quota) | 실시간 시세 갱신 | 캔들 및 AI 프롬프트 갱신 | 면책 정책 (Immunity) |
|:---|:---|:---:|:---:|:---:|:---:|
| **Tier 1** (Core / Top-tier) | 핵심 대형주(NVDA, AAPL 등), 주요 ETF(SPY, QQQ 등), 지수(^NDX 등), 에센셜 시드(NBIS, BE, AB, IONQ 등) | **최대 150개**<br>(현재 ~84개) | 매 15분마다 | 매 30분마다 (:00, :30) | **완전 면책 (Immunity)**<br>절대 자동 삭제 불가 |
| **Tier 2** (Mid-tier Universe) | S&P 500, 나스닥 100 편입주, 고배당 인컴주, 견고한 성장주 | **최대 650개**<br>(현재 ~502개) | 매 15분마다 | 일 2회<br>(12:00 ET, 17:00 ET 장 마감) | **보수적 보호**<br>상장폐지 확정 시에만 제외 |
| **Tier 3** (Small-tier / Discovery) | 러셀 2000 발굴주, 모멘텀/거래량 급증주, 신규 핫 트렌딩 종목 | **최대 120개**<br>(현재 ~57개) | 매 15분마다 | 일 1회<br>(17:00 ET 장 마감) | **동적 순환 (Rotation)**<br>쿼터 초과 시 저활동주 순환 |
| **전체 유니버스** | Tier 1 + Tier 2 + Tier 3 통합 | **최대 900+개**<br>(현재 643개) | 매 15분마다 | 정규장 마감 후 전체 동기화 | - |

> **용량 확장 원리**:
> - 배치 가격 조회(`update_realtime.py`)는 900개 전 종목을 50개 청크로 분할하여 **15초 이내**에 일괄 갱신하므로 전체 종목의 실시간 시세 신선도가 균일하게 유지됩니다.
> - 무거운 일중 캔들 및 프롬프트 계산(`collect_data.py --tier core`)은 Tier 1(~84개)에 집중 배치하여 GitHub Actions 실행 시간 제한과 yfinance Rate Limit을 완벽히 방어합니다.

### 13.2 보수적 제외 정책 및 2-Strike 유예 기간 (Conservative Exclusion & 2-Strike Grace Period)

일시적인 시장 이상(일시 거래정지, 네트워크 지연 등)으로 인해 정상 종목이 섣부르게 삭제되는 위험을 차단하기 위해 **2단계 검증(Two-Strike Grace Period)** 정책을 적용합니다.

```mermaid
flowchart TD
    Start["주간 헬스 감사 (Weekly Audit)"] --> CheckImmunity{"면책 대상인가?<br>(Tier 1, Core, ETF, Index, Seed)"}
    CheckImmunity -- Yes --> Safe["[PASS] 안전 보장 (절대 삭제 안 함)"]
    CheckImmunity -- No --> CheckCandles{"최근 10거래일<br>캔들/거래량 존재하는가?"}
    CheckCandles -- Yes --> Healthy["[HEALTHY] 정상 유지 (기존 경고 해제)"]
    CheckCandles -- No --> StrikeCheck{"기존 1차 경고가<br>기록되어 있는가?"}
    StrikeCheck -- No --> Strike1["[STRIKE 1/2] 최초 경고 등록<br>(.ticker_health.json 기록, 7일 유예기간, 삭제 안 함)"]
    StrikeCheck -- Yes --> Strike2["[STRIKE 2/2] 2회 연속 적발 확정<br>(상장폐지/사망 종목 승인)"]
    Strike2 --> CleanPrune["[CLEAN PRUNE] 완전한 리소스 정리 실행"]
```

1. **완전 면책 (Immunity Guarantee)**:
   - `category: "core"`, `category: "etf"`, 지수(`^` 접두사), 그리고 `ESSENTIAL_SEEDS`(`NBIS`, `BE`, `AB`, `IONQ`, `SMCI`, `PLUG`, `GRAB`, `CRDO`, `IREN`, `CORZ`)는 영구적으로 삭제되지 않습니다.
2. **1-Strike 유예 기간 (Grace Period)**:
   - 최근 10거래일 동안 캔들이 전혀 없거나 거래량이 0인 종목이 발견되면, 즉시 삭제하지 않고 `.ticker_health.json`에 `strikes: 1`, `status: "grace_period"`로 기록하며 7일간의 유예를 부여합니다.
3. **2-Strike 확정 및 안전 제거 (Clean Pruning)**:
   - 다음 주차 정기 감사에서도 동일하게 비활성 상태가 확인된 경우에만 `strikes: 2`로 승격되어 안전하게 영구 제거됩니다.

### 13.3 완전한 리소스 정리 프로토콜 (Clean Resource Deletion)

제외 대상으로 확정된 종목은 시스템의 어떤 곳에서도 찌꺼기 파일이나 끊어진 참조(Broken Reference)를 남기지 않도록 단일 트랜잭션으로 정리됩니다:

1. `tickers.json`의 `stocks` 맵에서 해당 티커 삭제
2. `data/{TICKER}.json` 상세 데이터 파일 즉시 삭제
3. `prompts/{TICKER}.json` 프롬프트 파일 즉시 삭제
4. `market_summary.json` 내 `summary[TICKER]` 노드 및 `metadata.tickers`, `metadata.ticker_meta` 동기화 삭제
5. `.ticker_health.json` 경고 해제 및 제거 이력(`history`) 기록
6. `supported_tickers.json` 즉시 자동 재생성 (클라이언트 404 방지)

### 13.4 4대 후보 발굴 소스 (Discovery Sources)

`manage_tickers_lifecycle.py`는 매주 4대 소스를 순환(Rotation)하며 새로운 유망 종목을 발굴합니다:

- **Source A (소형주 대표 ETF)**: IWM(러셀 2000), IWO(러셀 2000 성장), VB(뱅가드 스몰캡) 상위 유동성 보유 종목
- **Source B (야후 트렌딩 & 스크리너)**: Yahoo Finance Trending US 및 `aggressive_small_caps`, `small_cap_gainers`
- **Source C (거래량 폭증 & 모멘텀)**: `most_actives`, `day_gainers` 중 시가총액 3억~400억 달러 구간의 활성주
- **Source D (고배당 인컴)**: SCHD, VYM, SPYD 상위 편입 종목 및 고배당 스크리너

### 13.5 CLI 및 GitHub Actions 실행 방법

```bash
# 전체 수명주기 실행 (헬스 감사 + 신규 발굴 + 쿼터 밸런싱)
python manage_tickers_lifecycle.py --mode full --source auto

# 드라이런 (파일 변경 없이 결과만 시뮬레이션 확인)
python manage_tickers_lifecycle.py --dry-run --mode full

# 헬스 감사 전용 실행 (상장폐지/사망 종목 조사)
python manage_tickers_lifecycle.py --mode audit

# 특정 소스(Source D: 고배당주) 발굴 실행
python manage_tickers_lifecycle.py --mode add --source D
```

> **자동화 워크플로우**:
> - `.github/workflows/ticker_lifecycle.yml`: 매주 일요일 00:00 UTC (09:00 KST / 토요일 20:00 ET, 주말 장 마감 시간대)에 자동 실행되며, 필요 시 GitHub 웹 콘솔에서 `workflow_dispatch`로 즉시 수동 트리거할 수 있습니다.

---

## 14. 단기 / 중기 / 장기 / 종합 Top Pick API (`top_picks.json`)

전체 유니버스(ETF 제외 약 580~630개 주식)를 대상으로 **멀티 팩터 퀀트 스코어링**을 수행하여 투자 기간(Horizon)별 Top Pick을 **구간당(단기, 중기, 장기, 종합) 최소 5개 ~ 최대 20개** 선정합니다.

- **생성 스크립트**: `generate_top_picks.py` (입력: `tickers.json` + `data/{TICKER}.json`, 네트워크 호출 없음)
- **갱신 주기**: 평일 Daily Full 수집(장 마감 후) 및 Midday Tier 2 수집 직후 자동 재생성 (일 최대 2회). 파일이 없으면 다음 실행에서 즉시 생성.
- **수동 실행**: `python generate_top_picks.py` / 결과만 확인: `python generate_top_picks.py --dry-run`

> [!IMPORTANT]
> Top Pick은 공개 시세·재무 데이터 기반의 정량 스크리닝 결과이며 투자 권유가 아닙니다. 클라이언트는 `metadata.disclaimer` 문구를 화면에 함께 노출해야 합니다.

### 14.1 구간별 스코어링 모델

| 구간 (key) | 보유 기간 | 핵심 팩터 (가중치) | 필수 게이트 (Gate) | 임계 점수 | 허용 일봉 지연 |
|:---|:---|:---|:---|:---:|:---:|
| **단기** (`short_term`) | 1~4주 | 20일 수익률(20%), 5일 수익률(15%), 거래량 급증 5일/50일(15%), RSI 55~70 구간(15%), 20/50일선 추세(15%), MFI 50~80(10%), 과열 부담 낮음(10%) | 종가 > 50일선, RSI < 80 | 83 | 5일 |
| **중기** (`mid_term`) | 1~6개월 | 이동평균 정배열(20%), 3개월·6개월 상대강도(각 15%), 52주 고점 근접(10%), 주봉 RSI(10%), 애널리스트 업사이드(10%), 실적 성장(10%), 투자의견(5%), 저변동성(5%) | 종가 > 200일선, 50일선 > 200일선 | 85 | 10일 |
| **장기** (`long_term`) | 1년 이상 | ROE(12%), 매출 성장(12%), 순이익률·FCF 마진·이익 성장·선행 PER(각 8%), PEG·FCF 수익률·저부채(각 7%), 영업이익률·애널리스트·시총 안정성(각 5%), 유동비율(3%) | 흑자(순이익률 > 0), 시총 ≥ $2B, 200일선 대비 -20% 이상 붕괴 아님 | 68 | 30일 |
| **종합** (`overall`) | 3~12개월 | 이동평균 정배열(15%), 20일·3개월 수익률·ROE(각 10%), 거래량 급증·52주 고점 근접·매출 성장(각 8%), RSI·목표가 업사이드·순이익률(각 7%), 선행 PER·저부채(각 5%) | 종가 > 50일선 및 200일선, 흑자(순이익률 > 0), 시총 ≥ $2B | 72 | 10일 |

**산출 절차**

1. **유니버스 필터**: ETF/지수 제외, 주가 ≥ \$5, 20일 평균 거래대금 ≥ \$10M, 구간별 최소 일봉 수(단기 60 / 중기 200 / 장기 120) 및 데이터 신선도(최신 세션 대비 허용 지연일) 충족.
2. **팩터 정규화**: 각 팩터를 유니버스 내 **백분위(0~1)** 또는 **사다리꼴 Sweet-spot 함수**(RSI/MFI 등 과열·과매도 회피)로 변환.
3. **가중 합산**: 누락 팩터는 제외 후 가중치 재정규화, 데이터 커버리지에 따라 최대 15% 감점. 커버리지가 최소 기준(단기 80% / 중·장기 60%) 미만이면 제외.
4. **개수 결정**: 게이트 통과 & 임계 점수 이상 종목 수를 **[5, 20]으로 클램프** → 강세장에서는 최대 20개, 약세장에서는 최소 5개.
5. **분산 및 안정성**: 섹터당 최대 `ceil(선정 개수 × 30%)`(최소 2개) 상한, 직전 편입 종목에 소폭 가점(단기 +1 / 중기 +2.5 / 장기 +3)을 부여해 불필요한 일간 교체(Churn)를 억제.
6. **최소 개수 보장**: 조건을 만족하는 종목이 5개 미만이면 섹터 상한 해제 → 게이트 미통과 고득점 종목 순으로 보충합니다 (`passed_gate: false`로 표기).

### 14.2 응답 스키마

```json
{
  "metadata": {
    "schema_version": 1,
    "generated_at_ny": "2026-10-21T17:20:11-04:00",
    "as_of_session": "2026-10-21",
    "timezone": "America/New_York",
    "universe_total": 626,
    "min_picks": 5,
    "max_picks": 20,
    "filters": { "min_price": 5.0, "min_avg_dollar_volume_20d": 10000000, "excluded_categories": ["etf"] },
    "disclaimer": "본 Top Pick은 ... 투자자 본인에게 있습니다."
  },
  "horizons": {
    "short_term": {
      "label": "단기",
      "holding_period": "1~4주",
      "description": "가격 모멘텀·거래량 급증·RSI/MFI 수급 기반 스윙 트레이딩 후보",
      "score_threshold": 83,
      "count": 16,
      "qualified_count": 16,
      "universe_size": 586,
      "excluded": { "stale": 0, "insufficient_history": 0, "liquidity": 16 },
      "factor_weights": { "ret_5d": 0.15, "ret_20d": 0.2, "...": "..." },
      "changes": { "added": ["PCVX"], "removed": ["XYZ"] },
      "picks": [
        {
          "rank": 1,
          "ticker": "PCVX",
          "name": "Vaxcyte, Inc.",
          "sector": "healthcare",
          "industry": "Biotechnology",
          "category": "trending",
          "score": 94.7,
          "data_coverage": 1.0,
          "passed_gate": true,
          "price": 63.83,
          "change_percent": -3.62,
          "market_cap": 9964001414.88,
          "reasons": ["20일 수익률 (+5.7%)", "RSI 상승 구간 (RSI 60.7)", "단기 추세(20/50일선)"],
          "factor_scores": { "ret_5d": 91, "ret_20d": 80, "volume_surge": 100, "rsi_sweet": 100 },
          "metrics": { "rsi14": 60.72, "atr14": 5.21, "return_5d_pct": 12.59, "return_20d_pct": 5.75, "volume_ratio_5d_50d": 3.66 },
          "reference_levels": {
            "entry_reference": 63.83,
            "stop_loss": 53.41,
            "target": 79.46,
            "expected_return_pct": 24.49,
            "risk_reward_ratio": 1.5
          },
          "is_new": true,
          "first_picked_session": "2026-10-21",
          "last_candle_date": "2026-10-21"
        }
      ]
    },
    "mid_term": { "...": "동일 구조" },
    "long_term": { "...": "동일 구조" },
    "overall": { "...": "동일 구조" }
  }
}
```

**주요 필드 설명**

| 필드 | 설명 |
|:---|:---|
| `as_of_session` | 스코어링 기준 세션(유니버스 내 최신 일봉 날짜, ET) |
| `qualified_count` | 게이트 통과 & 임계 점수 이상 종목 수 (`count`는 이를 5~20으로 클램프한 값) |
| `excluded` | 필터로 제외된 종목 수 (`stale`: 일봉 지연, `insufficient_history`: 이력 부족, `liquidity`: 저가/저유동성) |
| `changes.added` / `changes.removed` | 직전 생성 대비 신규 편입 / 편출 종목 |
| `score` | 0~100 종합 점수 (직전 편입 가점 포함) |
| `factor_scores` | 팩터별 0~100 점수 (`null` = 데이터 없음) |
| `metrics` | 구간별 핵심 지표 (단기: 수익률·거래량비·RSI/MFI·이동평균 / 중기: 3·6개월 수익률·주봉 RSI·52주 고점 대비·업사이드 / 장기: ROE·마진·성장률·PER·PEG·FCF 수익률·부채비율 / 종합: 20일·3개월 수익률·52주 고점 대비·50/200일선·ROE·마진·선행 PER) |
| `reference_levels` | 참고 가격대. 단기: 손절 −2×ATR, 목표 +3×ATR / 중기·종합: 손절 max(−3.5×ATR, 50일선×0.97), 목표 애널리스트 평균 목표가(5% 이상 업사이드 시) 또는 +7×ATR / 장기: 손절 200일선×0.9, 목표 애널리스트 평균 목표가 |
| `is_new` / `first_picked_session` | 이번 생성에서 신규 편입 여부 / 연속 편입이 시작된 세션 |
| `passed_gate` | 필수 게이트 통과 여부 (`false`는 최소 5개 보장을 위한 보충 종목) |

> [!NOTE]
> `price`는 생성 시점 스냅샷입니다. 실시간 가격은 15분 주기로 갱신되는 `market_summary.json`의 `summary.{TICKER}.currentPrice`와 조인하여 표시하는 것을 권장합니다.

### 14.3 클라이언트 연동 예제

```javascript
const BASE = 'https://<github-pages-host>/stock-api';
const [picks, market] = await Promise.all([
  fetch(`${BASE}/top_picks.json?t=${Date.now()}`).then(r => r.json()),
  fetch(`${BASE}/market_summary.json?t=${Date.now()}`).then(r => r.json()),
]);

for (const key of ['short_term', 'mid_term', 'long_term', 'overall']) {
  const h = picks.horizons[key];
  console.log(`${h.label} (${h.holding_period}) - ${h.count}개`);
  for (const p of h.picks) {
    const live = market.summary[p.ticker]?.currentPrice ?? p.price;
    console.log(`#${p.rank} ${p.ticker} ${p.score}점 $${live}`, p.reasons.join(' · '), p.is_new ? '🆕' : '');
  }
}
```

```python
import requests
picks = requests.get("https://<github-pages-host>/stock-api/top_picks.json", timeout=10).json()
for key, h in picks["horizons"].items():
    print(h["label"], [p["ticker"] for p in h["picks"]])
```

---

## 15. UI/UX 표준 규칙: 광고(AdSense) · 플로팅 버튼 · 컨텍스트 메뉴(`⋯`) 통합 가이드

Stock API를 연동하는 클라이언트 웹/앱 서비스(`stock-on-tap`, `alpha-on-tap`, `stock-clock`, `buy-or-sell` 등 전체 On-Tap 생태계)는 상단 헤더의 시각적 복잡도를 줄이고 모바일 화면에서 광고 배너와 플로팅 요소가 충돌하지 않도록 다음 **3대 통합 규칙**을 필수로 준수합니다.

### 15.1 헤더 `⋯` (3-Dot) 컨텍스트 메뉴 통합 원칙 (`[data-brand-context-menu]`)
1. **보조 설정 단일화**:
   - 상단 헤더(Navbar)에 라이트/다크 테마 전환 버튼, 기본 AI 분석 앱 선택, 언어/시간제 전환, 데이터 새로고침/초기화 등 보조 설정 버튼들을 개별 나열하지 않고, 헤더 우측 끝 **`⋯` (더보기 / 설정 컨텍스트 메뉴)** 버튼 하나로 통합합니다.
2. **서포트 & 생태계 필수 포함**:
   - 모든 `⋯` 컨텍스트 메뉴 하단에는 구분선과 함께 다음 서포트 항목을 반드시 배치합니다:
     - **🍺 Buy me a beer (후원하기)**: 클릭 시 `window.BrandUI?.openDonate()` 호출 (`data-brand-open-donate` 속성 지원) 또는 `#beer-btn` 클릭, 미로드 시 `https://brand-ui.on-tap.xyz/donate`로 폴백합니다.
     - **🚀 개발자 페이지 (On Tap)**: `https://dev.on-tap.xyz`로 연결합니다.
     - **(선택) 자매 서비스 크로스 링크**: `https://stock.on-tap.xyz` (Stock on Tap), `https://etf.on-tap.xyz` (ActiveRadar) 등 생태계 서비스 간 이동 링크를 제공합니다.
3. **자동 감지 속성 (`data-brand-context-menu`)**:
   - 컨텍스트 메뉴 래퍼에 `data-brand-context-menu="true"`를 부여하면 `brand-ui`의 `widget.js`가 이를 자동 감지합니다.

### 15.2 플로팅 버튼(`#beer-btn`) & 컨텍스트 메뉴 연동 규칙
- 헤더에 `⋯` 컨텍스트 메뉴(`data-brand-context-menu`)가 존재하여 상시 후원/개발자 페이지 진입점이 보장되는 경우, 모바일 화면(`< 768px`)에서 광고가 게재되었거나 `donationPolicy: 'menu-only'`로 설정된 경우 하단 플로팅 버튼(`#beer-btn`)은 화면 간섭을 방지하기 위해 자동으로 숨김(`display: none`) 처리됩니다.
- 플로팅 버튼이 숨겨진 상태에서도 모달 인스턴스와 `window.BrandUI.openDonate()` API는 항상 활성 상태로 유지되므로, 사용자가 `⋯` 메뉴에서 **🍺 Buy me a beer**를 클릭하면 동일한 프리미엄 후원 모달이 즉시 팝업됩니다.

### 15.3 Google AdSense 배너 배치 및 충돌 방지 규칙
1. **정적 `<head>` 태그 및 `/ads.txt`**:
   - 모든 웹 프로젝트는 `<head>`에 `<meta name="google-adsense-account" content="ca-pub-3683251602683857">`와 AdSense 스크립트 태그를 포함하고, 정적 루트에 `/ads.txt`(`google.com, pub-3683251602683857, DIRECT, f08c47fec0942fa0`)를 배치합니다.
2. **선언형 배너 슬롯 (`[data-brand-ad]`)**:
   - 본문 하단(푸터 위) 등 콘텐츠 흐름을 방해하지 않는 위치에 `<div data-brand-ad data-app-key="<app-key>" class="brand-ui-ad-slot"></div>`를 배치합니다.
   - 슬롯 미설정/미게재 시 높이 `0`(`display: none`)으로 자동 축소되며, 광고가 렌더링된 경우 플로팅 요소와 최소 `16px` 이상의 안전 간격(Auto-Lift / AdBlock Fallback)을 유지합니다.

