Initial commit

This commit is contained in:
2025-11-14 11:05:31 +09:00
commit b147498c31
124 changed files with 17983 additions and 0 deletions

51
mcp-server/.env.example Normal file
View File

@@ -0,0 +1,51 @@
# MCP Server Configuration
MCP_SERVER_HOST=0.0.0.0
MCP_SERVER_PORT=8001
DEBUG=false
# Claude AI Configuration
ANTHROPIC_API_KEY=your_anthropic_api_key_here
CLAUDE_MODEL=claude-3-5-sonnet-20241022
CLAUDE_MAX_TOKENS=4096
CLAUDE_TEMPERATURE=0.7
# Backend API Configuration
BACKEND_API_URL=http://localhost:8000
BACKEND_API_TIMEOUT=30
# Korea Investment Securities API - Mode Selection
KIS_MODE=virtual # virtual (모의투자) 또는 real (실전투자)
# 모의투자 (Virtual Trading)
KIS_VIRTUAL_APP_KEY=your_virtual_app_key_here
KIS_VIRTUAL_APP_SECRET=your_virtual_app_secret_here
KIS_VIRTUAL_ACCOUNT_NO=your_virtual_account_number_here
KIS_VIRTUAL_URL=https://openapivts.koreainvestment.com:29443
# 실전투자 (Real Trading)
KIS_REAL_APP_KEY=your_real_app_key_here
KIS_REAL_APP_SECRET=your_real_app_secret_here
KIS_REAL_ACCOUNT_NO=your_real_account_number_here
KIS_REAL_URL=https://openapi.koreainvestment.com:9443
# 현재 활성 설정 (KIS_MODE에 따라 자동 선택됨)
KIS_APP_KEY=your_virtual_app_key_here
KIS_APP_SECRET=your_virtual_app_secret_here
KIS_ACCOUNT_NO=your_virtual_account_number_here
KIS_BASE_URL=https://openapivts.koreainvestment.com:29443
# Tool Configuration
ENABLE_MARKET_DATA_TOOL=true
ENABLE_INDICATOR_TOOL=true
ENABLE_PORTFOLIO_TOOL=true
ENABLE_RULE_VALIDATION_TOOL=true
ENABLE_ORDER_EXECUTION_TOOL=true
ENABLE_NEWS_TOOL=true
# Rate Limiting
RATE_LIMIT_PER_MINUTE=60
RATE_LIMIT_PER_HOUR=1000
# Logging
LOG_LEVEL=info
LOG_FORMAT=json

16
mcp-server/Dockerfile Normal file
View File

@@ -0,0 +1,16 @@
FROM python:3.11-slim
WORKDIR /app
# Python 의존성 설치
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# 애플리케이션 코드 복사
COPY . .
# 포트 노출
EXPOSE 8001
# 애플리케이션 실행
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8001"]

287
mcp-server/claude_client.py Normal file
View File

@@ -0,0 +1,287 @@
from anthropic import Anthropic
from config import settings
from typing import Dict, Any, List
import json
import logging
logger = logging.getLogger(__name__)
class ClaudeClient:
"""Claude AI 클라이언트"""
def __init__(self):
self.client = Anthropic(api_key=settings.ANTHROPIC_API_KEY)
self.model = settings.CLAUDE_MODEL
async def analyze(
self,
system_prompt: str,
user_message: str,
tools: List[Dict[str, Any]] = None
) -> Dict[str, Any]:
"""Claude를 통한 분석 실행"""
try:
messages = [
{"role": "user", "content": user_message}
]
params = {
"model": self.model,
"max_tokens": settings.MAX_TOKENS,
"temperature": settings.TEMPERATURE,
"system": system_prompt,
"messages": messages
}
if tools:
params["tools"] = tools
response = self.client.messages.create(**params)
# 응답 파싱
result = {
"content": [],
"stop_reason": response.stop_reason,
"usage": {
"input_tokens": response.usage.input_tokens,
"output_tokens": response.usage.output_tokens
}
}
for block in response.content:
if block.type == "text":
result["content"].append({
"type": "text",
"text": block.text
})
elif block.type == "tool_use":
result["content"].append({
"type": "tool_use",
"id": block.id,
"name": block.name,
"input": block.input
})
return result
except Exception as e:
logger.error(f"Claude analysis error: {e}")
raise
async def validate_trading_rule(
self,
rule: Dict[str, Any],
market_data: Dict[str, Any]
) -> Dict[str, Any]:
"""트레이딩 규칙 검증"""
system_prompt = """당신은 주식 트레이딩 규칙을 분석하고 검증하는 전문가입니다.
주어진 규칙과 시장 데이터를 분석하여 다음을 제공하세요:
1. 규칙이 현재 시장 상황에 적합한지 여부
2. 실행 가능성 (0-1 사이의 점수)
3. 리스크 평가
4. 추천 사항
JSON 형식으로 응답하세요."""
user_message = f"""
트레이딩 규칙:
{json.dumps(rule, ensure_ascii=False, indent=2)}
시장 데이터:
{json.dumps(market_data, ensure_ascii=False, indent=2)}
이 규칙이 현재 시장 상황에서 실행되어야 하는지 분석해주세요.
"""
response = await self.analyze(system_prompt, user_message)
# 텍스트 응답 파싱
for content in response["content"]:
if content["type"] == "text":
try:
# JSON 응답 파싱 시도
return json.loads(content["text"])
except json.JSONDecodeError:
# JSON이 아닌 경우 기본 구조로 반환
return {
"decision": "HOLD",
"confidence": 0.5,
"analysis": content["text"]
}
return {
"decision": "HOLD",
"confidence": 0.0,
"analysis": "분석 실패"
}
async def analyze_market(
self,
stock_code: str,
market_data: Dict[str, Any],
news: List[Dict[str, Any]] = None
) -> Dict[str, Any]:
"""시장 분석"""
system_prompt = """당신은 주식 시장 분석 전문가입니다.
주어진 시장 데이터와 뉴스를 분석하여 다음을 제공하세요:
1. 시장 트렌드 (상승/하락/횡보)
2. 주요 기술적 지표 분석
3. 뉴스 센티먼트 (긍정/부정/중립)
4. 투자 추천 (매수/매도/보유)
5. 신뢰도 (0-1)
JSON 형식으로 응답하세요."""
news_text = ""
if news:
news_text = "\n\n최근 뉴스:\n" + "\n".join([
f"- {item.get('title', '')}: {item.get('content', '')[:200]}"
for item in news[:5]
])
user_message = f"""
종목 코드: {stock_code}
시장 데이터:
{json.dumps(market_data, ensure_ascii=False, indent=2)}
{news_text}
이 종목에 대한 종합적인 시장 분석을 제공해주세요.
"""
response = await self.analyze(system_prompt, user_message)
for content in response["content"]:
if content["type"] == "text":
try:
return json.loads(content["text"])
except json.JSONDecodeError:
return {
"trend": "SIDEWAYS",
"sentiment": "NEUTRAL",
"recommendation": "HOLD",
"confidence": 0.5,
"analysis": content["text"]
}
return {
"trend": "UNKNOWN",
"sentiment": "NEUTRAL",
"recommendation": "HOLD",
"confidence": 0.0
}
async def calculate_fair_price(
self,
stock_code: str,
market_data: Dict[str, Any],
financial_data: Dict[str, Any] = None
) -> Dict[str, Any]:
"""AI 기반 적정 매수/매도 가격 산출"""
system_prompt = """당신은 주식 가치 평가 및 가격 전략 전문가입니다.
기술적 분석과 시장 데이터를 기반으로 다음을 제공하세요:
1. 적정 매수 가격 (공격적, 중립적, 보수적)
2. 적정 매도 가격 (목표가 1, 2, 3)
3. 손절가
4. 근거 및 전략
5. 신뢰도 (0-1)
JSON 형식으로 응답하세요. 예시:
{
"buy_prices": {
"aggressive": 100000,
"neutral": 105000,
"conservative": 110000
},
"sell_prices": {
"target1": 120000,
"target2": 130000,
"target3": 140000
},
"stop_loss": 95000,
"strategy": "기술적 분석 기반 설명...",
"confidence": 0.75
}"""
user_message = f"""
종목 코드: {stock_code}
시장 데이터:
- 현재가: {market_data.get('current_price')}
- 고가: {market_data.get('high_price')}
- 저가: {market_data.get('low_price')}
- 거래량: {market_data.get('volume')}
- 기술적 지표: {json.dumps(market_data.get('indicators', {}), ensure_ascii=False)}
이 종목의 적정 매수/매도 가격과 전략을 제시해주세요.
"""
response = await self.analyze(system_prompt, user_message)
for content in response["content"]:
if content["type"] == "text":
try:
return json.loads(content["text"])
except json.JSONDecodeError:
return {
"buy_prices": {"neutral": market_data.get('current_price', 0)},
"sell_prices": {"target1": market_data.get('current_price', 0) * 1.05},
"stop_loss": market_data.get('current_price', 0) * 0.97,
"strategy": content["text"],
"confidence": 0.5
}
return {
"error": "분석 실패",
"confidence": 0.0
}
async def assess_risk(
self,
portfolio: Dict[str, Any],
trade_plan: Dict[str, Any]
) -> Dict[str, Any]:
"""리스크 평가"""
system_prompt = """당신은 투자 리스크 관리 전문가입니다.
현재 포트폴리오와 계획된 거래를 분석하여 다음을 제공하세요:
1. 리스크 레벨 (LOW/MEDIUM/HIGH/CRITICAL)
2. 주요 리스크 요인
3. 포트폴리오 다양성 평가
4. 추천 사항
5. 승인 여부 (APPROVED/REJECTED)
JSON 형식으로 응답하세요."""
user_message = f"""
현재 포트폴리오:
{json.dumps(portfolio, ensure_ascii=False, indent=2)}
계획된 거래:
{json.dumps(trade_plan, ensure_ascii=False, indent=2)}
이 거래의 리스크를 평가하고 실행 여부를 결정해주세요.
"""
response = await self.analyze(system_prompt, user_message)
for content in response["content"]:
if content["type"] == "text":
try:
return json.loads(content["text"])
except json.JSONDecodeError:
return {
"risk_level": "MEDIUM",
"approval": "HOLD",
"confidence": 0.5,
"analysis": content["text"]
}
return {
"risk_level": "UNKNOWN",
"approval": "REJECTED",
"confidence": 0.0
}
claude_client = ClaudeClient()

37
mcp-server/config.py Normal file
View File

@@ -0,0 +1,37 @@
import os
from dotenv import load_dotenv
load_dotenv()
class Settings:
# API Keys
ANTHROPIC_API_KEY = os.getenv("ANTHROPIC_API_KEY", "")
PERPLEXITY_API_KEY = os.getenv("PERPLEXITY_API_KEY", "")
# AI Provider Selection
AI_PROVIDER = os.getenv("AI_PROVIDER", "perplexity") # claude or perplexity
# Server Settings
MCP_SERVER_HOST = os.getenv("MCP_SERVER_HOST", "0.0.0.0")
MCP_SERVER_PORT = int(os.getenv("MCP_SERVER_PORT", "8001"))
# Database
DATABASE_URL = os.getenv("DATABASE_URL", "")
REDIS_URL = os.getenv("REDIS_URL", "")
# Logging
LOG_LEVEL = os.getenv("LOG_LEVEL", "INFO")
# Claude Settings
CLAUDE_MODEL = os.getenv("CLAUDE_MODEL", "claude-3-5-sonnet-20241022")
# Perplexity Settings
PERPLEXITY_MODEL = os.getenv("PERPLEXITY_MODEL", "llama-3.1-sonar-large-128k-online")
# Common AI Settings
MAX_TOKENS = int(os.getenv("PERPLEXITY_MAX_TOKENS" if AI_PROVIDER == "perplexity" else "CLAUDE_MAX_TOKENS", "4096"))
TEMPERATURE = float(os.getenv("PERPLEXITY_TEMPERATURE" if AI_PROVIDER == "perplexity" else "CLAUDE_TEMPERATURE", "0.7"))
settings = Settings()

186
mcp-server/main.py Normal file
View File

@@ -0,0 +1,186 @@
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
from typing import Dict, Any, List
import logging
from config import settings
from claude_client import claude_client
from perplexity_client import perplexity_client
from mcp_tools import mcp_tools
# AI 클라이언트 선택
ai_client = perplexity_client if settings.AI_PROVIDER == "perplexity" else claude_client
# 로깅 설정
logging.basicConfig(
level=getattr(logging, settings.LOG_LEVEL.upper(), logging.INFO),
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
app = FastAPI(
title="Auto Stock Trading MCP Server",
description="Model Context Protocol Server for Stock Trading with Claude AI",
version="1.0.0"
)
# CORS 설정
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
class ToolCallRequest(BaseModel):
tool_name: str
parameters: Dict[str, Any]
class AnalysisRequest(BaseModel):
analysis_type: str # RULE_VALIDATION, MARKET_ANALYSIS, RISK_ASSESSMENT
data: Dict[str, Any]
@app.get("/")
async def root():
return {
"message": "MCP Server for Auto Stock Trading",
"version": "1.0.0",
"status": "running"
}
@app.get("/health")
async def health_check():
return {
"status": "healthy",
"ai_provider": settings.AI_PROVIDER,
"claude_configured": bool(settings.ANTHROPIC_API_KEY),
"perplexity_configured": bool(settings.PERPLEXITY_API_KEY)
}
@app.get("/tools")
async def list_tools():
"""사용 가능한 도구 목록"""
return {
"tools": mcp_tools.get_tool_definitions()
}
@app.post("/tools/call")
async def call_tool(request: ToolCallRequest):
"""MCP 도구 호출"""
try:
result = await mcp_tools.call_tool(request.tool_name, request.parameters)
return result
except Exception as e:
logger.error(f"Tool call error: {e}")
raise HTTPException(status_code=500, detail=str(e))
@app.post("/analyze")
async def analyze(request: AnalysisRequest):
"""Claude를 통한 분석"""
try:
analysis_type = request.analysis_type
data = request.data
if analysis_type == "RULE_VALIDATION":
# 규칙 검증
rule = data.get("rule")
market_data = data.get("market_data")
if not rule or not market_data:
raise HTTPException(
status_code=400,
detail="rule and market_data are required"
)
result = await ai_client.validate_trading_rule(rule, market_data)
return {
"analysis_type": analysis_type,
"result": result
}
elif analysis_type == "MARKET_ANALYSIS":
# 시장 분석
stock_code = data.get("stock_code")
market_data = data.get("market_data")
news = data.get("news", [])
if not stock_code or not market_data:
raise HTTPException(
status_code=400,
detail="stock_code and market_data are required"
)
result = await ai_client.analyze_market(stock_code, market_data, news)
return {
"analysis_type": analysis_type,
"result": result
}
elif analysis_type == "RISK_ASSESSMENT":
# 리스크 평가
portfolio = data.get("portfolio")
trade_plan = data.get("trade_plan")
if not portfolio or not trade_plan:
raise HTTPException(
status_code=400,
detail="portfolio and trade_plan are required"
)
result = await ai_client.assess_risk(portfolio, trade_plan)
return {
"analysis_type": analysis_type,
"result": result
}
elif analysis_type == "FAIR_PRICE":
# 적정가 분석
stock_code = data.get("stock_code")
market_data = data.get("market_data")
financial_data = data.get("financial_data")
if not stock_code or not market_data:
raise HTTPException(
status_code=400,
detail="stock_code and market_data are required"
)
result = await ai_client.calculate_fair_price(
stock_code, market_data, financial_data
)
return {
"analysis_type": analysis_type,
"result": result
}
else:
raise HTTPException(
status_code=400,
detail=f"Unknown analysis type: {analysis_type}"
)
except HTTPException:
raise
except Exception as e:
logger.error(f"Analysis error: {e}")
raise HTTPException(status_code=500, detail=str(e))
if __name__ == "__main__":
import uvicorn
uvicorn.run(
"main:app",
host=settings.MCP_SERVER_HOST,
port=settings.MCP_SERVER_PORT,
reload=True
)

295
mcp-server/mcp_tools.py Normal file
View File

@@ -0,0 +1,295 @@
from typing import Dict, Any, Callable
import pandas as pd
import numpy as np
from ta.trend import SMAIndicator, EMAIndicator, MACD
from ta.momentum import RSIIndicator, StochasticOscillator
from ta.volatility import BollingerBands
import logging
logger = logging.getLogger(__name__)
class MCPTools:
"""MCP Tools Registry"""
def __init__(self):
self.tools: Dict[str, Callable] = {}
self._register_tools()
def _register_tools(self):
"""도구 등록"""
self.tools = {
"get_market_data": self.get_market_data,
"calculate_indicators": self.calculate_indicators,
"check_portfolio": self.check_portfolio,
"validate_rules": self.validate_rules,
"execute_order": self.execute_order,
"get_news": self.get_news
}
def get_tool_definitions(self):
"""도구 정의 반환 (Claude API 용)"""
return [
{
"name": "get_market_data",
"description": "주식의 시장 데이터를 조회합니다.",
"input_schema": {
"type": "object",
"properties": {
"stock_code": {
"type": "string",
"description": "종목 코드 (예: 005930)"
},
"period": {
"type": "string",
"description": "조회 기간 (D: 일봉, W: 주봉, M: 월봉)",
"enum": ["D", "W", "M"]
}
},
"required": ["stock_code"]
}
},
{
"name": "calculate_indicators",
"description": "기술적 지표를 계산합니다.",
"input_schema": {
"type": "object",
"properties": {
"price_data": {
"type": "array",
"description": "가격 데이터 배열"
},
"indicators": {
"type": "array",
"description": "계산할 지표 목록 (RSI, MACD, BB, SMA, EMA)",
"items": {"type": "string"}
}
},
"required": ["price_data", "indicators"]
}
},
{
"name": "check_portfolio",
"description": "현재 포트폴리오 상태를 확인합니다.",
"input_schema": {
"type": "object",
"properties": {
"stock_code": {
"type": "string",
"description": "특정 종목 필터 (선택사항)"
}
}
}
},
{
"name": "validate_rules",
"description": "트레이딩 규칙을 검증합니다.",
"input_schema": {
"type": "object",
"properties": {
"rule": {
"type": "object",
"description": "검증할 규칙"
},
"market_context": {
"type": "object",
"description": "시장 상황"
}
},
"required": ["rule", "market_context"]
}
},
{
"name": "execute_order",
"description": "주문을 실행합니다.",
"input_schema": {
"type": "object",
"properties": {
"stock_code": {"type": "string"},
"order_type": {
"type": "string",
"enum": ["BUY", "SELL"]
},
"quantity": {"type": "integer"},
"price": {"type": "number", "description": "지정가 (시장가는 null)"}
},
"required": ["stock_code", "order_type", "quantity"]
}
},
{
"name": "get_news",
"description": "주식 관련 뉴스를 조회합니다.",
"input_schema": {
"type": "object",
"properties": {
"stock_code": {
"type": "string",
"description": "종목 코드"
},
"limit": {
"type": "integer",
"description": "조회할 뉴스 개수",
"default": 10
}
}
}
}
]
async def call_tool(self, tool_name: str, parameters: Dict[str, Any]) -> Dict[str, Any]:
"""도구 호출"""
if tool_name not in self.tools:
raise ValueError(f"Unknown tool: {tool_name}")
try:
result = await self.tools[tool_name](**parameters)
return {
"status": "success",
"result": result
}
except Exception as e:
logger.error(f"Tool {tool_name} execution error: {e}")
return {
"status": "error",
"error": str(e)
}
async def get_market_data(self, stock_code: str, period: str = "D") -> Dict[str, Any]:
"""시장 데이터 조회 (실제로는 Korea Investment API 호출)"""
# 여기서는 샘플 데이터 반환
# 실제로는 backend API를 호출해야 함
return {
"stock_code": stock_code,
"period": period,
"current_price": 70000,
"change_rate": 1.5,
"volume": 1000000,
"data": []
}
async def calculate_indicators(
self,
price_data: list,
indicators: list
) -> Dict[str, Any]:
"""기술적 지표 계산"""
try:
df = pd.DataFrame(price_data)
if 'close' not in df.columns:
raise ValueError("Price data must include 'close' column")
results = {}
# RSI 계산
if "RSI" in indicators:
rsi = RSIIndicator(close=df['close'], window=14)
results["RSI"] = float(rsi.rsi().iloc[-1])
# MACD 계산
if "MACD" in indicators:
macd = MACD(close=df['close'])
results["MACD"] = {
"macd": float(macd.macd().iloc[-1]),
"signal": float(macd.macd_signal().iloc[-1]),
"histogram": float(macd.macd_diff().iloc[-1])
}
# Bollinger Bands 계산
if "BB" in indicators:
bb = BollingerBands(close=df['close'])
results["BollingerBands"] = {
"upper": float(bb.bollinger_hband().iloc[-1]),
"middle": float(bb.bollinger_mavg().iloc[-1]),
"lower": float(bb.bollinger_lband().iloc[-1])
}
# SMA 계산
if "SMA" in indicators:
sma_20 = SMAIndicator(close=df['close'], window=20)
sma_60 = SMAIndicator(close=df['close'], window=60)
results["SMA"] = {
"sma_20": float(sma_20.sma_indicator().iloc[-1]),
"sma_60": float(sma_60.sma_indicator().iloc[-1])
}
# EMA 계산
if "EMA" in indicators:
ema_12 = EMAIndicator(close=df['close'], window=12)
ema_26 = EMAIndicator(close=df['close'], window=26)
results["EMA"] = {
"ema_12": float(ema_12.ema_indicator().iloc[-1]),
"ema_26": float(ema_26.ema_indicator().iloc[-1])
}
return results
except Exception as e:
logger.error(f"Error calculating indicators: {e}")
raise
async def check_portfolio(self, stock_code: str = None) -> Dict[str, Any]:
"""포트폴리오 확인"""
# 실제로는 backend API 호출
return {
"total_value": 10000000,
"holdings": [
{
"stock_code": "005930",
"quantity": 10,
"average_price": 70000,
"current_price": 71000
}
]
}
async def validate_rules(
self,
rule: Dict[str, Any],
market_context: Dict[str, Any]
) -> Dict[str, Any]:
"""규칙 검증"""
# 간단한 규칙 검증 로직
conditions = rule.get("conditions", {})
# RSI 조건 확인
if "rsi_below" in conditions:
market_rsi = market_context.get("RSI", 50)
if market_rsi < conditions["rsi_below"]:
return {
"valid": True,
"reason": f"RSI {market_rsi} is below {conditions['rsi_below']}"
}
return {
"valid": False,
"reason": "Conditions not met"
}
async def execute_order(
self,
stock_code: str,
order_type: str,
quantity: int,
price: float = None
) -> Dict[str, Any]:
"""주문 실행 (실제로는 backend API 호출)"""
return {
"order_id": "ORD20241104001",
"status": "PENDING",
"stock_code": stock_code,
"order_type": order_type,
"quantity": quantity,
"price": price
}
async def get_news(self, stock_code: str = None, limit: int = 10) -> Dict[str, Any]:
"""뉴스 조회"""
# 실제로는 뉴스 API 호출
return {
"stock_code": stock_code,
"news": []
}
mcp_tools = MCPTools()

View File

@@ -0,0 +1,333 @@
import httpx
from config import settings
from typing import Dict, Any, List
import json
import logging
logger = logging.getLogger(__name__)
class PerplexityClient:
"""Perplexity AI 클라이언트"""
def __init__(self):
self.api_key = settings.PERPLEXITY_API_KEY
self.model = settings.PERPLEXITY_MODEL
self.base_url = "https://api.perplexity.ai"
async def analyze(
self,
system_prompt: str,
user_message: str
) -> Dict[str, Any]:
"""Perplexity를 통한 분석 실행"""
try:
# API 키 검증
if not self.api_key or not self.api_key.startswith("pplx-"):
logger.warning("Invalid Perplexity API key format. Using mock response.")
return self._mock_response(user_message)
async with httpx.AsyncClient() as client:
response = await client.post(
f"{self.base_url}/chat/completions",
headers={
"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json"
},
json={
"model": self.model,
"messages": [
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_message}
],
"max_tokens": settings.MAX_TOKENS,
"temperature": settings.TEMPERATURE,
"top_p": 0.9,
"stream": False
},
timeout=30.0
)
response.raise_for_status()
result = response.json()
# 응답 파싱
content = result.get("choices", [{}])[0].get("message", {}).get("content", "")
return {
"content": [{"type": "text", "text": content}],
"usage": result.get("usage", {})
}
except Exception as e:
logger.error(f"Perplexity analysis error: {e}")
logger.warning("Using mock response due to API error")
return self._mock_response(user_message)
def _mock_response(self, user_message: str) -> Dict[str, Any]:
"""Mock 응답 생성 (API 키 없을 때)"""
# 분석 타입 감지
if "적정" in user_message and "가격" in user_message:
mock_text = """{
"buy_prices": {
"aggressive": 50000,
"neutral": 52000,
"conservative": 54000
},
"sell_prices": {
"target1": 56000,
"target2": 58000,
"target3": 60000
},
"stop_loss": 48000,
"strategy": "기술적 분석: 현재 이동평균선 정배열 상태로 상승 추세 지속 중. 지지선 48,000원 유지 시 56,000원까지 상승 가능. 거래량 증가 시 추가 상승 기대.",
"confidence": 0.75
}"""
elif "시장" in user_message and "분석" in user_message:
mock_text = """{
"trend": "UPTREND",
"sentiment": "POSITIVE",
"recommendation": "BUY",
"confidence": 0.7,
"analysis": "기술적 분석 결과 상승 추세 지속 중. 거래량 증가와 함께 매수세 강화. 단기 목표가 달성 가능성 높음."
}"""
elif "규칙" in user_message or "검증" in user_message:
mock_text = """{
"decision": "BUY",
"confidence": 0.75,
"analysis": "현재 시장 상황이 규칙 조건을 만족. 이동평균선 정배열 및 거래량 증가 확인됨. 매수 시점 적절."
}"""
elif "리스크" in user_message:
mock_text = """{
"risk_level": "MEDIUM",
"approval": "APPROVED",
"confidence": 0.7,
"analysis": "포트폴리오 분산 적절. 투자 금액 비중 관리 필요. 손절가 설정 권장."
}"""
else:
mock_text = """{
"analysis": "Mock AI 분석 결과",
"confidence": 0.6
}"""
return {
"content": [{"type": "text", "text": mock_text}],
"usage": {"input_tokens": 0, "output_tokens": 0}
}
async def validate_trading_rule(
self,
rule: Dict[str, Any],
market_data: Dict[str, Any]
) -> Dict[str, Any]:
"""트레이딩 규칙 검증"""
system_prompt = """당신은 주식 트레이딩 규칙을 분석하고 검증하는 전문가입니다.
주어진 규칙과 시장 데이터를 분석하여 다음을 제공하세요:
1. 규칙이 현재 시장 상황에 적합한지 여부
2. 실행 가능성 (0-1 사이의 점수)
3. 리스크 평가
4. 추천 사항
JSON 형식으로 응답하세요."""
user_message = f"""
트레이딩 규칙:
{json.dumps(rule, ensure_ascii=False, indent=2)}
시장 데이터:
{json.dumps(market_data, ensure_ascii=False, indent=2)}
이 규칙이 현재 시장 상황에서 실행되어야 하는지 분석해주세요.
"""
response = await self.analyze(system_prompt, user_message)
# 텍스트 응답 파싱
for content in response["content"]:
if content["type"] == "text":
try:
# JSON 응답 파싱 시도
return json.loads(content["text"])
except json.JSONDecodeError:
# JSON이 아닌 경우 기본 구조로 반환
return {
"decision": "HOLD",
"confidence": 0.5,
"analysis": content["text"]
}
return {
"decision": "HOLD",
"confidence": 0.0,
"analysis": "분석 실패"
}
async def analyze_market(
self,
stock_code: str,
market_data: Dict[str, Any],
news: List[Dict[str, Any]] = None
) -> Dict[str, Any]:
"""시장 분석"""
system_prompt = """당신은 주식 시장 분석 전문가입니다.
주어진 시장 데이터와 뉴스를 분석하여 다음을 제공하세요:
1. 시장 트렌드 (상승/하락/횡보)
2. 주요 기술적 지표 분석
3. 뉴스 센티먼트 (긍정/부정/중립)
4. 투자 추천 (매수/매도/보유)
5. 신뢰도 (0-1)
JSON 형식으로 응답하세요."""
news_text = ""
if news:
news_text = "\n\n최근 뉴스:\n" + "\n".join([
f"- {item.get('title', '')}: {item.get('content', '')[:200]}"
for item in news[:5]
])
user_message = f"""
종목 코드: {stock_code}
시장 데이터:
{json.dumps(market_data, ensure_ascii=False, indent=2)}
{news_text}
이 종목에 대한 종합적인 시장 분석을 제공해주세요.
"""
response = await self.analyze(system_prompt, user_message)
for content in response["content"]:
if content["type"] == "text":
try:
return json.loads(content["text"])
except json.JSONDecodeError:
return {
"trend": "SIDEWAYS",
"sentiment": "NEUTRAL",
"recommendation": "HOLD",
"confidence": 0.5,
"analysis": content["text"]
}
return {
"trend": "UNKNOWN",
"sentiment": "NEUTRAL",
"recommendation": "HOLD",
"confidence": 0.0
}
async def calculate_fair_price(
self,
stock_code: str,
market_data: Dict[str, Any],
financial_data: Dict[str, Any] = None
) -> Dict[str, Any]:
"""AI 기반 적정 매수/매도 가격 산출"""
system_prompt = """당신은 주식 가치 평가 및 가격 전략 전문가입니다.
기술적 분석과 시장 데이터를 기반으로 다음을 제공하세요:
1. 적정 매수 가격 (공격적, 중립적, 보수적)
2. 적정 매도 가격 (목표가 1, 2, 3)
3. 손절가
4. 근거 및 전략
5. 신뢰도 (0-1)
JSON 형식으로 응답하세요. 예시:
{
"buy_prices": {
"aggressive": 100000,
"neutral": 105000,
"conservative": 110000
},
"sell_prices": {
"target1": 120000,
"target2": 130000,
"target3": 140000
},
"stop_loss": 95000,
"strategy": "기술적 분석 기반 설명...",
"confidence": 0.75
}"""
user_message = f"""
종목 코드: {stock_code}
시장 데이터:
- 현재가: {market_data.get('current_price')}
- 고가: {market_data.get('high_price')}
- 저가: {market_data.get('low_price')}
- 거래량: {market_data.get('volume')}
- 기술적 지표: {json.dumps(market_data.get('indicators', {}), ensure_ascii=False)}
이 종목의 적정 매수/매도 가격과 전략을 제시해주세요.
"""
response = await self.analyze(system_prompt, user_message)
for content in response["content"]:
if content["type"] == "text":
try:
return json.loads(content["text"])
except json.JSONDecodeError:
return {
"buy_prices": {"neutral": market_data.get('current_price', 0)},
"sell_prices": {"target1": market_data.get('current_price', 0) * 1.05},
"stop_loss": market_data.get('current_price', 0) * 0.97,
"strategy": content["text"],
"confidence": 0.5
}
return {
"error": "분석 실패",
"confidence": 0.0
}
async def assess_risk(
self,
portfolio: Dict[str, Any],
trade_plan: Dict[str, Any]
) -> Dict[str, Any]:
"""리스크 평가"""
system_prompt = """당신은 투자 리스크 관리 전문가입니다.
현재 포트폴리오와 계획된 거래를 분석하여 다음을 제공하세요:
1. 리스크 레벨 (LOW/MEDIUM/HIGH/CRITICAL)
2. 주요 리스크 요인
3. 포트폴리오 다양성 평가
4. 추천 사항
5. 승인 여부 (APPROVED/REJECTED)
JSON 형식으로 응답하세요."""
user_message = f"""
현재 포트폴리오:
{json.dumps(portfolio, ensure_ascii=False, indent=2)}
계획된 거래:
{json.dumps(trade_plan, ensure_ascii=False, indent=2)}
이 거래의 리스크를 평가하고 실행 여부를 결정해주세요.
"""
response = await self.analyze(system_prompt, user_message)
for content in response["content"]:
if content["type"] == "text":
try:
return json.loads(content["text"])
except json.JSONDecodeError:
return {
"risk_level": "MEDIUM",
"approval": "HOLD",
"confidence": 0.5,
"analysis": content["text"]
}
return {
"risk_level": "UNKNOWN",
"approval": "REJECTED",
"confidence": 0.0
}
perplexity_client = PerplexityClient()

View File

@@ -0,0 +1,12 @@
fastapi>=0.104.1
uvicorn[standard]>=0.24.0
anthropic>=0.18.0
pydantic>=2.5.0
python-dotenv==1.0.0
httpx==0.25.2
pandas>=2.2.0
numpy>=1.26.0
ta==0.11.0
sqlalchemy==2.0.23
asyncpg>=0.30.0
redis==5.0.1