docs: add detailed README.md with project documentation

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
yakenator
2026-02-24 04:47:05 +09:00
parent f0c4060aed
commit 34261ac36f

184
README.md Normal file
View File

@ -0,0 +1,184 @@
# stock-common
한국 주식 분석 마이크로서비스 플랫폼의 **공유 라이브러리**. 모든 백엔드 서비스(`stock-dart-collector`, `stock-kis-collector`, `stock-screener` 등)가 이 패키지에 의존합니다.
## 주요 기능
- **데이터 모델**: Pydantic v2 기반 도메인 모델 (Stock, DailyPrice, Valuation, Financial, Screening 등)
- **데이터베이스**: PostgreSQL (asyncpg) + MongoDB (motor) 커넥션 풀 관리
- **Redis Streams**: 서비스 간 비동기 메시지 큐 (publish/consume/ack)
- **API 팩토리**: FastAPI 앱 생성 (`/health`, `/streams` 엔드포인트 자동 포함)
- **Collector 베이스 클래스**: Rate limiting, 자동 재시도, HTTP 세션 관리
- **설정 관리**: pydantic-settings 기반 환경변수 로딩
## 설치
```bash
pip install -e .
```
다른 서비스에서 의존성으로 사용:
```toml
# pyproject.toml
[project]
dependencies = ["stock-common"]
```
Docker 빌드 시:
```dockerfile
COPY stock-common/ /tmp/stock-common/
RUN pip install --no-cache-dir /tmp/stock-common/
```
## 모듈 구조
```
src/stock_common/
├── __init__.py
├── config.py # Settings (pydantic-settings) - 환경변수 관리
├── logging_config.py # structlog 기반 로깅 설정
├── api_base.py # FastAPI 앱 팩토리 (create_app)
├── collector_base.py # BaseCollector ABC (rate limit, retry)
├── database/
│ ├── __init__.py
│ ├── postgres.py # asyncpg 커넥션 풀 (get_pg_pool)
│ └── mongodb.py # motor 클라이언트 (get_mongo_database)
├── queue/
│ ├── __init__.py
│ ├── redis_client.py # Redis 싱글턴 (get_redis)
│ └── streams.py # publish / consume / ack
└── models/
├── __init__.py
├── stock.py # Stock, Market
├── price.py # DailyPrice
├── valuation.py # Valuation (PER, PBR, ROE...)
├── financial.py # Financial, PeriodType
├── screening.py # ScreeningResult, ScreeningStrategy
├── analysis.py # LLMAnalysis, Recommendation
├── disclosure.py # Disclosure, Sentiment
└── news.py # News
```
## 핵심 컴포넌트
### Settings (config.py)
pydantic-settings로 환경변수를 자동 로딩합니다. `.env` 파일도 지원합니다.
```python
from stock_common.config import settings
# 데이터베이스
settings.postgres_dsn # postgresql://stock:stock@localhost:5432/stockdb
settings.mongodb_uri # mongodb://localhost:27017
settings.redis_url # redis://localhost:6379/0
# API 키
settings.dart_api_key
settings.kis_app_key
settings.anthropic_api_key
# Rate Limits
settings.dart_rate_limit # 10.0 req/s
settings.kis_rate_limit # 20.0 req/s
```
### API 팩토리 (api_base.py)
모든 서비스는 `create_app()`으로 FastAPI 인스턴스를 생성합니다. 자동으로 포함되는 엔드포인트:
| 엔드포인트 | 설명 |
|-----------|------|
| `GET /health` | 서비스 상태, Redis 연결, uptime |
| `GET /streams` | 관련 Redis Stream 큐 길이 |
```python
from stock_common.api_base import create_app
app = create_app(
title="stock-dart-collector",
streams=["queue:trigger-dart", "queue:raw-financials"],
)
# 서비스별 엔드포인트 추가
@app.post("/collect/financials")
async def collect_financials(...):
...
```
### BaseCollector (collector_base.py)
HTTP API 수집기의 공통 패턴:
- **Rate Limiting**: `aiolimiter.AsyncLimiter`로 초당 요청 수 제한
- **자동 재시도**: `tenacity`로 최대 3회, 지수 백오프 (1~30초)
- **429 처리**: `Retry-After` 헤더 존중
- **세션 관리**: `async with` 컨텍스트 매니저
```python
from stock_common.collector_base import BaseCollector
class DARTCollector(BaseCollector):
def __init__(self):
super().__init__(rate_limit=10.0, timeout=30)
async def collect(self, **kwargs):
data = await self._request("GET", url, params=params)
binary = await self._download_binary(url)
```
### Redis Streams (queue/streams.py)
서비스 간 비동기 메시지 전달:
```python
from stock_common.queue import publish, consume, ack
# 메시지 발행
msg_id = await publish("queue:trigger-dart", {"type": "financials", "stock_codes": ["005930"]})
# 메시지 소비 (consumer group)
messages = await consume("queue:trigger-dart", "dart-collectors", "worker-1", count=10, block=5000)
for msg in messages:
process(msg.data)
await ack("queue:trigger-dart", "dart-collectors", msg.message_id)
```
### Database
```python
from stock_common.database import get_pg_pool, get_mongo_database
# PostgreSQL (asyncpg)
pool = await get_pg_pool()
async with pool.acquire() as conn:
rows = await conn.fetch("SELECT * FROM stock WHERE is_active = true")
# MongoDB (motor)
db = get_mongo_database()
cursor = db.llm_analysis.find({"stock_code": "005930"})
docs = await cursor.to_list(10)
```
## 의존성
| 패키지 | 용도 |
|--------|------|
| `pydantic` >= 2.0 | 데이터 모델 |
| `pydantic-settings` >= 2.0 | 환경변수 설정 |
| `asyncpg` >= 0.29 | PostgreSQL 비동기 드라이버 |
| `motor` >= 3.3 | MongoDB 비동기 드라이버 |
| `redis` >= 5.0 | Redis 클라이언트 |
| `fastapi` >= 0.115 | REST API 프레임워크 |
| `uvicorn` >= 0.34 | ASGI 서버 |
| `aiohttp` >= 3.9 | 비동기 HTTP 클라이언트 |
| `aiolimiter` >= 1.1 | Rate limiting |
| `tenacity` >= 8.2 | 재시도 로직 |
| `structlog` >= 24.0 | 구조화된 로깅 |
## 요구사항
- Python >= 3.12
- 빌드: hatchling