Skip to main content
Glama

MCP Market Statistics Server

by whdghk1907
test_sentiment_tools.pyโ€ข20.5 kB
"""์‹ฌ๋ฆฌ ์ง€ํ‘œ ๋„๊ตฌ ํ…Œ์ŠคํŠธ""" import pytest import random import math from datetime import datetime, timedelta from unittest.mock import AsyncMock from src.tools.sentiment_tools import MarketSentimentTool from src.exceptions import DataValidationError, DatabaseConnectionError class TestMarketSentimentTool: """์‹œ์žฅ ์‹ฌ๋ฆฌ ์ง€ํ‘œ ๋„๊ตฌ ํ…Œ์ŠคํŠธ""" @pytest.fixture def mock_db_manager(self): """Mock ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋งค๋‹ˆ์ €""" return AsyncMock() @pytest.fixture def mock_cache_manager(self): """Mock ์บ์‹œ ๋งค๋‹ˆ์ €""" return AsyncMock() @pytest.fixture def sentiment_tool(self, mock_db_manager, mock_cache_manager): """์‹ฌ๋ฆฌ ์ง€ํ‘œ ๋„๊ตฌ ์ธ์Šคํ„ด์Šค""" return MarketSentimentTool(mock_db_manager, mock_cache_manager) @pytest.fixture def sample_sentiment_data(self): """์ƒ˜ํ”Œ ์‹ฌ๋ฆฌ ์ง€ํ‘œ ๋ฐ์ดํ„ฐ""" base_date = datetime.now().date() return { "put_call_ratio": [ { "date": base_date - timedelta(days=i), "put_volume": 85000000 + random.randint(-10000000, 10000000), "call_volume": 120000000 + random.randint(-15000000, 15000000), "put_call_ratio": 0.7 + random.uniform(-0.2, 0.2), "put_oi": 950000 + random.randint(-100000, 100000), "call_oi": 1200000 + random.randint(-150000, 150000) } for i in range(30) ], "vkospi_data": [ { "date": base_date - timedelta(days=i), "vkospi_value": 22.5 + random.uniform(-5.0, 5.0), "change": random.uniform(-3.0, 3.0), "change_rate": random.uniform(-15.0, 15.0), "high": 25.0 + random.uniform(-3.0, 3.0), "low": 20.0 + random.uniform(-3.0, 3.0) } for i in range(30) ], "market_indicators": [ { "date": base_date - timedelta(days=i), "kospi_close": 2650 + random.uniform(-100, 100), "kospi_change_rate": random.uniform(-3.0, 3.0), "volume_ratio": 0.8 + random.uniform(-0.3, 0.3), "advancing_issues": random.randint(600, 900), "declining_issues": random.randint(500, 800), "new_highs": random.randint(5, 50), "new_lows": random.randint(5, 45) } for i in range(30) ], "news_sentiment": [ { "date": base_date - timedelta(days=i), "positive_count": random.randint(20, 80), "negative_count": random.randint(15, 75), "neutral_count": random.randint(50, 150), "sentiment_score": random.uniform(-1.0, 1.0), "buzz_intensity": random.uniform(0.1, 1.0) } for i in range(30) ] } def test_tool_initialization(self, sentiment_tool, mock_db_manager, mock_cache_manager): """๋„๊ตฌ ์ดˆ๊ธฐํ™” ํ…Œ์ŠคํŠธ""" assert sentiment_tool.name == "get_market_sentiment" assert sentiment_tool.description is not None assert "์‹ฌ๋ฆฌ" in sentiment_tool.description or "๊ฐ์„ฑ" in sentiment_tool.description assert sentiment_tool.db_manager == mock_db_manager assert sentiment_tool.cache_manager == mock_cache_manager def test_tool_definition(self, sentiment_tool): """๋„๊ตฌ ์ •์˜ ํ…Œ์ŠคํŠธ""" definition = sentiment_tool.get_tool_definition() assert definition.name == "get_market_sentiment" assert definition.description is not None assert definition.inputSchema is not None # ์ž…๋ ฅ ์Šคํ‚ค๋งˆ ๊ฒ€์ฆ schema = definition.inputSchema assert schema["type"] == "object" assert "properties" in schema properties = schema["properties"] assert "market" in properties assert "indicators" in properties assert "period" in properties assert "include_fear_greed_index" in properties # indicators ํŒŒ๋ผ๋ฏธํ„ฐ ๊ฒ€์ฆ indicators_prop = properties["indicators"] assert indicators_prop["type"] == "array" assert "items" in indicators_prop @pytest.mark.asyncio async def test_execute_comprehensive_sentiment(self, sentiment_tool, sample_sentiment_data): """์ข…ํ•ฉ ์‹ฌ๋ฆฌ ์ง€ํ‘œ ๋ถ„์„ ํ…Œ์ŠคํŠธ""" # ์บ์‹œ ๋ฏธ์Šค sentiment_tool.cache_manager.get.return_value = None # ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์‘๋‹ต ์„ค์ • (์ˆœ์„œ๋Œ€๋กœ ์ฟผ๋ฆฌ๋  ๊ฒƒ์œผ๋กœ ์˜ˆ์ƒ) sentiment_tool.db_manager.fetch_all.side_effect = [ sample_sentiment_data["put_call_ratio"], sample_sentiment_data["vkospi_data"], sample_sentiment_data["market_indicators"], sample_sentiment_data["news_sentiment"] ] # ์‹คํ–‰ result = await sentiment_tool.execute({ "market": "KOSPI", "indicators": ["put_call_ratio", "vkospi", "market_breadth", "news_sentiment"], "period": "30d", "include_fear_greed_index": True }) # ๊ฒฐ๊ณผ ๊ฒ€์ฆ assert len(result) == 1 content = result[0] assert content.type == "text" # JSON ํŒŒ์‹ฑํ•˜์—ฌ ๋‚ด์šฉ ํ™•์ธ import json data = json.loads(content.text) assert "timestamp" in data assert "market" in data assert "period" in data assert "sentiment_indicators" in data assert "fear_greed_index" in data assert "overall_sentiment" in data # Put/Call Ratio ๊ฒ€์ฆ sentiment_indicators = data["sentiment_indicators"] assert "put_call_ratio" in sentiment_indicators pcr = sentiment_indicators["put_call_ratio"] assert "current_ratio" in pcr assert "interpretation" in pcr assert "trend" in pcr # VKOSPI ๊ฒ€์ฆ assert "vkospi" in sentiment_indicators vkospi = sentiment_indicators["vkospi"] assert "current_value" in vkospi assert "volatility_regime" in vkospi # Fear & Greed Index ๊ฒ€์ฆ fgi = data["fear_greed_index"] assert "score" in fgi assert "category" in fgi assert 0 <= fgi["score"] <= 100 assert fgi["category"] in ["๊ทน๋„ ๊ณตํฌ", "๊ณตํฌ", "์ค‘๋ฆฝ", "ํƒ์š•", "๊ทน๋„ ํƒ์š•"] @pytest.mark.asyncio async def test_execute_put_call_ratio_only(self, sentiment_tool, sample_sentiment_data): """Put/Call Ratio๋งŒ ๋ถ„์„ ํ…Œ์ŠคํŠธ""" sentiment_tool.cache_manager.get.return_value = None sentiment_tool.db_manager.fetch_all.return_value = sample_sentiment_data["put_call_ratio"] # ์‹คํ–‰ result = await sentiment_tool.execute({ "market": "KOSPI", "indicators": ["put_call_ratio"], "period": "30d" }) # ๊ฒฐ๊ณผ ๊ฒ€์ฆ content = result[0] import json data = json.loads(content.text) assert "sentiment_indicators" in data sentiment_indicators = data["sentiment_indicators"] assert "put_call_ratio" in sentiment_indicators assert len(sentiment_indicators) == 1 # ํ•˜๋‚˜์˜ ์ง€ํ‘œ๋งŒ @pytest.mark.asyncio async def test_execute_vkospi_analysis(self, sentiment_tool, sample_sentiment_data): """VKOSPI ๋ถ„์„ ํ…Œ์ŠคํŠธ""" sentiment_tool.cache_manager.get.return_value = None sentiment_tool.db_manager.fetch_all.return_value = sample_sentiment_data["vkospi_data"] # ์‹คํ–‰ result = await sentiment_tool.execute({ "market": "KOSPI", "indicators": ["vkospi"], "period": "30d" }) # ๊ฒฐ๊ณผ ๊ฒ€์ฆ content = result[0] import json data = json.loads(content.text) vkospi = data["sentiment_indicators"]["vkospi"] assert "current_value" in vkospi assert "volatility_regime" in vkospi assert "trend_analysis" in vkospi # ๋ณ€๋™์„ฑ ์ฒด์ œ ํ™•์ธ regime = vkospi["volatility_regime"] assert regime in ["๋‚ฎ์Œ", "๋ณดํ†ต", "๋†’์Œ", "๋งค์šฐ ๋†’์Œ"] @pytest.mark.asyncio async def test_market_breadth_sentiment(self, sentiment_tool, sample_sentiment_data): """์‹œ์žฅ ํญ ๊ธฐ๋ฐ˜ ์‹ฌ๋ฆฌ ๋ถ„์„ ํ…Œ์ŠคํŠธ""" sentiment_tool.cache_manager.get.return_value = None sentiment_tool.db_manager.fetch_all.return_value = sample_sentiment_data["market_indicators"] # ์‹คํ–‰ result = await sentiment_tool.execute({ "market": "KOSPI", "indicators": ["market_breadth"], "period": "30d" }) # ๊ฒฐ๊ณผ ๊ฒ€์ฆ content = result[0] import json data = json.loads(content.text) breadth = data["sentiment_indicators"]["market_breadth"] assert "advance_decline_sentiment" in breadth assert "volume_sentiment" in breadth assert "new_highs_lows_ratio" in breadth @pytest.mark.asyncio async def test_news_sentiment_analysis(self, sentiment_tool, sample_sentiment_data): """๋‰ด์Šค ์‹ฌ๋ฆฌ ๋ถ„์„ ํ…Œ์ŠคํŠธ""" sentiment_tool.cache_manager.get.return_value = None sentiment_tool.db_manager.fetch_all.return_value = sample_sentiment_data["news_sentiment"] # ์‹คํ–‰ result = await sentiment_tool.execute({ "market": "KOSPI", "indicators": ["news_sentiment"], "period": "7d", "include_buzz_analysis": True }) # ๊ฒฐ๊ณผ ๊ฒ€์ฆ content = result[0] import json data = json.loads(content.text) news = data["sentiment_indicators"]["news_sentiment"] assert "sentiment_score" in news assert "sentiment_trend" in news assert "buzz_analysis" in news # ๋ฒ„์ฆˆ ๋ถ„์„ ํ™•์ธ buzz = news["buzz_analysis"] assert "intensity_level" in buzz assert "trending_topics" in buzz def test_put_call_ratio_interpretation(self, sentiment_tool): """Put/Call Ratio ํ•ด์„ ํ…Œ์ŠคํŠธ""" # ๊ทน๋„ ๊ณตํฌ (๋†’์€ Put/Call Ratio) interpretation = sentiment_tool._interpret_put_call_ratio(1.2) assert "๊ณตํฌ" in interpretation # ์ค‘๋ฆฝ interpretation = sentiment_tool._interpret_put_call_ratio(0.8) assert "์ค‘๋ฆฝ" in interpretation or "๋ณดํ†ต" in interpretation # ํƒ์š• (๋‚ฎ์€ Put/Call Ratio) interpretation = sentiment_tool._interpret_put_call_ratio(0.4) assert "ํƒ์š•" in interpretation def test_vkospi_volatility_regime(self, sentiment_tool): """VKOSPI ๋ณ€๋™์„ฑ ์ฒด์ œ ๋ถ„๋ฅ˜ ํ…Œ์ŠคํŠธ""" # ๋‚ฎ์€ ๋ณ€๋™์„ฑ regime = sentiment_tool._classify_volatility_regime(15.0) assert regime == "๋‚ฎ์Œ" # ๋ณดํ†ต ๋ณ€๋™์„ฑ regime = sentiment_tool._classify_volatility_regime(22.0) assert regime == "๋ณดํ†ต" # ๋†’์€ ๋ณ€๋™์„ฑ regime = sentiment_tool._classify_volatility_regime(30.0) assert regime == "๋†’์Œ" # ๋งค์šฐ ๋†’์€ ๋ณ€๋™์„ฑ regime = sentiment_tool._classify_volatility_regime(40.0) assert regime == "๋งค์šฐ ๋†’์Œ" def test_fear_greed_index_calculation(self, sentiment_tool): """Fear & Greed Index ๊ณ„์‚ฐ ํ…Œ์ŠคํŠธ""" # ์ƒ˜ํ”Œ ์ง€ํ‘œ๋“ค indicators = { "put_call_ratio": {"score": 30, "weight": 0.25}, # ๊ณตํฌ์ชฝ "vkospi": {"score": 25, "weight": 0.20}, # ๊ณตํฌ์ชฝ "market_breadth": {"score": 70, "weight": 0.25}, # ํƒ์š•์ชฝ "news_sentiment": {"score": 60, "weight": 0.15}, # ์•ฝ๊ฐ„ ํƒ์š• "volume": {"score": 55, "weight": 0.15} # ์ค‘๋ฆฝ } fgi_score = sentiment_tool._calculate_fear_greed_index(indicators) assert 0 <= fgi_score <= 100 assert isinstance(fgi_score, (int, float)) def test_fear_greed_category(self, sentiment_tool): """Fear & Greed ์นดํ…Œ๊ณ ๋ฆฌ ๋ถ„๋ฅ˜ ํ…Œ์ŠคํŠธ""" # ๊ทน๋„ ๊ณตํฌ category = sentiment_tool._get_fear_greed_category(15) assert category == "๊ทน๋„ ๊ณตํฌ" # ๊ณตํฌ category = sentiment_tool._get_fear_greed_category(35) assert category == "๊ณตํฌ" # ์ค‘๋ฆฝ category = sentiment_tool._get_fear_greed_category(50) assert category == "์ค‘๋ฆฝ" # ํƒ์š• category = sentiment_tool._get_fear_greed_category(70) assert category == "ํƒ์š•" # ๊ทน๋„ ํƒ์š• category = sentiment_tool._get_fear_greed_category(90) assert category == "๊ทน๋„ ํƒ์š•" def test_sentiment_trend_analysis(self, sentiment_tool): """์‹ฌ๋ฆฌ ํŠธ๋ Œ๋“œ ๋ถ„์„ ํ…Œ์ŠคํŠธ""" # ์ƒ์Šน ํŠธ๋ Œ๋“œ values = [20, 25, 30, 35, 40] trend = sentiment_tool._analyze_sentiment_trend(values) assert trend["direction"] == "์ƒ์Šน" # ํ•˜๋ฝ ํŠธ๋ Œ๋“œ values = [60, 55, 50, 45, 40] trend = sentiment_tool._analyze_sentiment_trend(values) assert trend["direction"] == "ํ•˜๋ฝ" # ๋ณดํ•ฉ values = [50, 48, 52, 49, 51] trend = sentiment_tool._analyze_sentiment_trend(values) assert trend["direction"] == "๋ณดํ•ฉ" @pytest.mark.asyncio async def test_social_media_sentiment(self, sentiment_tool): """์†Œ์…œ ๋ฏธ๋””์–ด ์‹ฌ๋ฆฌ ๋ถ„์„ ํ…Œ์ŠคํŠธ""" # ์†Œ์…œ ๋ฏธ๋””์–ด ๋ฐ์ดํ„ฐ social_data = [ { "date": datetime.now().date() - timedelta(days=i), "platform": "twitter", "positive_mentions": random.randint(100, 500), "negative_mentions": random.randint(50, 300), "neutral_mentions": random.randint(200, 800), "sentiment_score": random.uniform(-1.0, 1.0), "engagement_rate": random.uniform(0.01, 0.15), "trending_keywords": ["KOSPI", "์ฃผ์‹", "ํˆฌ์ž", "์ฆ์‹œ"] } for i in range(7) ] sentiment_tool.cache_manager.get.return_value = None sentiment_tool.db_manager.fetch_all.return_value = social_data # ์‹คํ–‰ result = await sentiment_tool.execute({ "market": "KOSPI", "indicators": ["social_media"], "period": "7d" }) # ๊ฒฐ๊ณผ ๊ฒ€์ฆ content = result[0] import json data = json.loads(content.text) social = data["sentiment_indicators"]["social_media"] assert "sentiment_score" in social assert "engagement_trend" in social assert "trending_topics" in social @pytest.mark.asyncio async def test_cache_functionality(self, sentiment_tool): """์บ์‹œ ๊ธฐ๋Šฅ ํ…Œ์ŠคํŠธ""" # ์บ์‹œ ํžˆํŠธ ์‹œ๋‚˜๋ฆฌ์˜ค cached_data = { "timestamp": datetime.now().isoformat(), "market": "KOSPI", "sentiment_indicators": { "put_call_ratio": {"current_ratio": 0.75, "interpretation": "์ค‘๋ฆฝ"} }, "overall_sentiment": "์ค‘๋ฆฝ" } sentiment_tool.cache_manager.get.return_value = cached_data # ์‹คํ–‰ result = await sentiment_tool.execute({ "market": "KOSPI", "indicators": ["put_call_ratio"], "period": "30d" }) # ์บ์‹œ์—์„œ ๋ฐ์ดํ„ฐ ๋ฐ˜ํ™˜ ํ™•์ธ content = result[0] import json data = json.loads(content.text) assert data == cached_data # ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํ˜ธ์ถœ ์—†์Œ ํ™•์ธ sentiment_tool.db_manager.fetch_all.assert_not_called() @pytest.mark.asyncio async def test_error_handling(self, sentiment_tool): """์—๋Ÿฌ ์ฒ˜๋ฆฌ ํ…Œ์ŠคํŠธ""" sentiment_tool.cache_manager.get.return_value = None sentiment_tool.db_manager.fetch_all.side_effect = DatabaseConnectionError("DB ์—ฐ๊ฒฐ ์‹คํŒจ") with pytest.raises(DatabaseConnectionError): await sentiment_tool.execute({ "market": "KOSPI", "indicators": ["put_call_ratio"], "period": "30d" }) @pytest.mark.asyncio async def test_invalid_parameters(self, sentiment_tool): """์ž˜๋ชป๋œ ํŒŒ๋ผ๋ฏธํ„ฐ ํ…Œ์ŠคํŠธ""" # ์ž˜๋ชป๋œ ์‹œ์žฅ with pytest.raises(ValueError, match="Invalid market"): await sentiment_tool.execute({ "market": "INVALID", "indicators": ["put_call_ratio"], "period": "30d" }) # ๋นˆ ์ง€ํ‘œ ๋ชฉ๋ก with pytest.raises(ValueError, match="At least one indicator"): await sentiment_tool.execute({ "market": "KOSPI", "indicators": [], "period": "30d" }) # ์ž˜๋ชป๋œ ๊ธฐ๊ฐ„ with pytest.raises(ValueError, match="Invalid period"): await sentiment_tool.execute({ "market": "KOSPI", "indicators": ["put_call_ratio"], "period": "invalid" }) @pytest.mark.asyncio async def test_insufficient_data_handling(self, sentiment_tool): """๋ฐ์ดํ„ฐ ๋ถ€์กฑ ์ฒ˜๋ฆฌ ํ…Œ์ŠคํŠธ""" # ๋ฐ์ดํ„ฐ๊ฐ€ ๋ถ€์กฑํ•œ ๊ฒฝ์šฐ insufficient_data = [ { "date": datetime.now().date(), "put_volume": 85000000, "call_volume": 120000000, "put_call_ratio": 0.7 } ] sentiment_tool.cache_manager.get.return_value = None sentiment_tool.db_manager.fetch_all.return_value = insufficient_data result = await sentiment_tool.execute({ "market": "KOSPI", "indicators": ["put_call_ratio"], "period": "30d" }) content = result[0] import json data = json.loads(content.text) assert "warning" in data or "insufficient data" in str(data).lower() def test_sentiment_aggregation(self, sentiment_tool): """์‹ฌ๋ฆฌ ์ง€ํ‘œ ์ข…ํ•ฉ ํ…Œ์ŠคํŠธ""" # ๋‹ค์–‘ํ•œ ์ง€ํ‘œ๋“ค indicators = { "put_call_ratio": 0.85, # ์•ฝ๊ฐ„ ๊ณตํฌ "vkospi": 28.5, # ๋†’์€ ๋ณ€๋™์„ฑ (๊ณตํฌ) "advance_decline": 0.65, # ์•ฝ๊ฐ„ ๋ถ€์ •์  "news_sentiment": 0.2 # ์•ฝ๊ฐ„ ๊ธ์ •์  } overall = sentiment_tool._aggregate_sentiment_indicators(indicators) assert "score" in overall assert "sentiment" in overall assert "confidence" in overall assert 0 <= overall["score"] <= 100 assert overall["sentiment"] in ["๋งค์šฐ ๋ถ€์ •์ ", "๋ถ€์ •์ ", "์ค‘๋ฆฝ", "๊ธ์ •์ ", "๋งค์šฐ ๊ธ์ •์ "] @pytest.mark.asyncio async def test_historical_comparison(self, sentiment_tool, sample_sentiment_data): """๊ณผ๊ฑฐ ๋Œ€๋น„ ์‹ฌ๋ฆฌ ๋ณ€ํ™” ๋ถ„์„ ํ…Œ์ŠคํŠธ""" sentiment_tool.cache_manager.get.return_value = None sentiment_tool.db_manager.fetch_all.return_value = sample_sentiment_data["put_call_ratio"] # ์‹คํ–‰ (๊ณผ๊ฑฐ ๋น„๊ต ํฌํ•จ) result = await sentiment_tool.execute({ "market": "KOSPI", "indicators": ["put_call_ratio"], "period": "30d", "include_historical_comparison": True }) # ๊ฒฐ๊ณผ ๊ฒ€์ฆ content = result[0] import json data = json.loads(content.text) assert "historical_comparison" in data historical = data["historical_comparison"] assert "vs_1week_ago" in historical assert "vs_1month_ago" in historical assert "percentile_rank" in historical

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/whdghk1907/mcp-market-statistics'

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