Skip to main content
Glama

Korea Stock Analyzer MCP Server

by Mrbaeksang
greenblatt.ts9.32 kB
/** * 조엘 그린블라트 매직 포뮬러 분석 - 완전 구현 */ import { BaseAnalyzer } from './base-analyzer.js'; import { GreenblattAnalysis, FinancialData } from '../types/index.js'; export class GreenblattAnalyzer extends BaseAnalyzer { constructor() { super('조엘 그린블라트', 'Magic Formula Value Investing'); } async analyze(data: any): Promise<GreenblattAnalysis> { const { financialData, marketData, balanceSheetData } = data; const currentFund = Array.isArray(financialData) ? financialData[0] : financialData; const currentPrice = marketData?.currentPrice || 0; const marketCap = marketData?.marketCap || 1000000000; // 1. ROIC (Return on Invested Capital) 계산 // ROIC = EBIT / (Net Working Capital + Net Fixed Assets) const roic = this.calculateROIC(currentFund, balanceSheetData); // 2. Earnings Yield 계산 // Earnings Yield = EBIT / Enterprise Value const earningsYield = this.calculateEarningsYield(currentFund, marketData); // 3. 조정 ROIC (한국 시장용) const adjustedROIC = this.calculateAdjustedROIC(currentFund); // 4. FCF Yield (Free Cash Flow Yield) const fcfYield = this.calculateFCFYield(data); // 5. 매직 포뮬러 종합 점수 const magicScore = this.calculateMagicScore(roic, earningsYield); // 6. 랭킹 시스템 (퀀타일 기반) const rankings = this.calculateRankings({ roic, earningsYield, adjustedROIC, fcfYield }); // 7. 품질 지표들 const qualityMetrics = this.calculateQualityMetrics(currentFund); // 8. 적정가 계산 (그린블라트 방식) const fairValue = this.calculateGreenblattFairValue( currentFund, earningsYield, roic, currentPrice ); // 9. 상승여력 const upside = this.calculateUpside(fairValue, currentPrice); // 10. 종합 평가 const comprehensiveScore = this.calculateComprehensiveScore({ magicScore, rankings, qualityMetrics, upside }); return { fairValue, method: '매직 포뮬러: 높은 ROIC + 높은 Earnings Yield', upside, recommendation: this.getGreenblattRecommendation( magicScore, comprehensiveScore, rankings ), // 핵심 지표들 roic, earningsYield, magicScore, adjustedROIC, fcfYield, // 랭킹 roicRank: rankings.roicRank, earningsYieldRank: rankings.earningsYieldRank, combinedRank: rankings.combinedRank, // 품질 지표 qualityScore: qualityMetrics.score, consistentProfitability: qualityMetrics.consistentProfitability, capitalEfficiency: qualityMetrics.capitalEfficiency, // 종합 comprehensiveScore, investmentGrade: this.getInvestmentGrade(comprehensiveScore) }; } private calculateROIC(fund: FinancialData, balanceSheet: any): number { // ROIC = EBIT / Invested Capital // EBIT 추정: EPS × 1.3 (세전이익 추정) const ebit = fund.eps * 1.3; // Invested Capital 추정: 시가총액의 80% (보수적) const investedCapital = fund.bps * 0.8; if (investedCapital <= 0) return 0; const roic = (ebit / investedCapital) * 100; return Math.round(roic * 100) / 100; } private calculateEarningsYield(fund: FinancialData, marketData: any): number { // 실제 그린블라트 공식: Earnings Yield = EBIT / Enterprise Value // EBIT = EPS × 1.43 (세후이익을 세전이익으로 변환, 법인세 30% 가정) // EV = Market Cap + Net Debt (순부채) if (!marketData || marketData.marketCap <= 0) return 0; // EBIT 계산 const ebit = fund.eps * 1.43; // 1/(1-0.3) = 1.43 // Enterprise Value 추정 // 한국 기업 평균 Net Debt/Market Cap = 0.3 const estimatedNetDebt = marketData.marketCap * 0.3; const enterpriseValue = marketData.marketCap + estimatedNetDebt; // Earnings Yield = EBIT / EV const earningsYield = (ebit * marketData.sharesOutstanding) / enterpriseValue * 100; return Math.round(earningsYield * 100) / 100; } private calculateAdjustedROIC(fund: FinancialData): number { // 한국 시장 조정 ROIC // ROE를 기반으로 조정 if (fund.bps <= 0) return 0; const roe = (fund.eps / fund.bps) * 100; // 레버리지 조정 (PBR로 추정) const leverageAdjustment = fund.pbr > 1.5 ? 0.8 : 1.0; return Math.round(roe * leverageAdjustment * 100) / 100; } private calculateFCFYield(data: any): number { // FCF Yield = FCF / Market Cap const fund = Array.isArray(data.financialData) ? data.financialData[0] : data.financialData; const marketCap = data.marketData?.marketCap || 1; // FCF 추정: EPS의 70% const fcf = fund.eps * 0.7; return Math.round((fcf / marketCap) * 10000 * 100) / 100; } private calculateMagicScore(roic: number, earningsYield: number): number { // 그린블라트 매직 포뮬러 점수 // 단순 합산이 아닌 가중 평균 const roicScore = Math.min(roic, 50); // 최대 50점 const eyScore = Math.min(earningsYield * 2, 50); // 최대 50점 return Math.round(roicScore + eyScore); } private calculateRankings(metrics: any): any { // 실제로는 전체 종목과 비교해야 하지만, // 절대 수치로 퀀타일 추정 const roicRank = this.getRank(metrics.roic, [5, 10, 15, 20, 30]); const eyRank = this.getRank(metrics.earningsYield, [3, 5, 7, 10, 15]); const combinedRank = Math.round((roicRank + eyRank) / 2); return { roicRank, earningsYieldRank: eyRank, combinedRank, percentile: this.getPercentile(combinedRank) }; } private getRank(value: number, thresholds: number[]): number { // 1-5 등급 (1이 최고) for (let i = thresholds.length - 1; i >= 0; i--) { if (value >= thresholds[i]) return 5 - i; } return 5; } private getPercentile(rank: number): number { // 랭킹을 퍼센타일로 변환 const percentiles = { 1: 95, 2: 80, 3: 60, 4: 40, 5: 20 }; return percentiles[rank] || 10; } private calculateQualityMetrics(fund: FinancialData): any { const metrics = { profitability: fund.eps > 0, efficiency: fund.per > 0 && fund.per < 20, valuation: fund.pbr > 0 && fund.pbr < 3, dividend: fund.div > 0, growth: true // 성장성 가정 }; const score = Object.values(metrics).filter(v => v).length * 20; return { score, consistentProfitability: metrics.profitability, capitalEfficiency: metrics.efficiency, attractiveValuation: metrics.valuation, dividendPaying: metrics.dividend, growthPotential: metrics.growth }; } private calculateGreenblattFairValue( fund: FinancialData, earningsYield: number, roic: number, currentPrice: number ): number { // 그린블라트 적정가 = EPS × 목표 PER // 목표 PER = 100 / 목표 수익률 // 목표 수익률 설정 (ROIC 기반) let targetYield = 10; // 기본 10% if (roic > 30) targetYield = 7; else if (roic > 20) targetYield = 8; else if (roic > 15) targetYield = 9; const targetPER = 100 / targetYield; const fairValue = fund.eps * targetPER; // 현재가 대비 조정 (극단값 방지) if (fairValue > currentPrice * 3) { return Math.round(currentPrice * 2.5); } return Math.round(fairValue); } private calculateComprehensiveScore(metrics: any): number { let score = 0; // 매직 점수 (40점) if (metrics.magicScore >= 80) score += 40; else if (metrics.magicScore >= 60) score += 30; else if (metrics.magicScore >= 40) score += 20; else if (metrics.magicScore >= 20) score += 10; // 랭킹 (30점) if (metrics.rankings.combinedRank <= 2) score += 30; else if (metrics.rankings.combinedRank <= 3) score += 20; else if (metrics.rankings.combinedRank <= 4) score += 10; // 품질 (20점) score += metrics.qualityMetrics.score * 0.2; // 상승여력 (10점) if (metrics.upside > 50) score += 10; else if (metrics.upside > 30) score += 7; else if (metrics.upside > 15) score += 5; return Math.round(score); } private getInvestmentGrade(score: number): string { if (score >= 80) return 'A+ (최우수)'; if (score >= 70) return 'A (우수)'; if (score >= 60) return 'B+ (양호)'; if (score >= 50) return 'B (보통)'; if (score >= 40) return 'C (주의)'; return 'D (위험)'; } private getGreenblattRecommendation( magicScore: number, comprehensiveScore: number, rankings: any ): 'Strong Buy' | 'Buy' | 'Hold' | 'Sell' | 'Strong Sell' { // 그린블라트 매직 포뮬러 기준 if (comprehensiveScore >= 80 && rankings.combinedRank <= 2) { return 'Strong Buy'; } if (comprehensiveScore >= 60 && magicScore >= 50) { return 'Buy'; } if (comprehensiveScore >= 40 || magicScore >= 30) { return 'Hold'; } if (comprehensiveScore >= 20) { return 'Sell'; } return 'Strong Sell'; } }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Mrbaeksang/korea-stock-analyzer-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server