Skip to main content
Glama

MCP Market Statistics Server

by whdghk1907
test_regime_tools.pyโ€ข21.1 kB
"""์‹œ์žฅ ๊ตญ๋ฉด ํŒ๋‹จ ๋„๊ตฌ ํ…Œ์ŠคํŠธ""" import pytest import random import math from datetime import datetime, timedelta from unittest.mock import AsyncMock, MagicMock from src.tools.regime_tools import MarketRegimeTool from src.exceptions import DataValidationError, DatabaseConnectionError class TestMarketRegimeTool: """์‹œ์žฅ ๊ตญ๋ฉด ํŒ๋‹จ ๋„๊ตฌ ํ…Œ์ŠคํŠธ""" @pytest.fixture def mock_db_manager(self): """Mock ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋งค๋‹ˆ์ €""" return AsyncMock() @pytest.fixture def mock_cache_manager(self): """Mock ์บ์‹œ ๋งค๋‹ˆ์ €""" return AsyncMock() @pytest.fixture def regime_tool(self, mock_db_manager, mock_cache_manager): """์‹œ์žฅ ๊ตญ๋ฉด ํŒ๋‹จ ๋„๊ตฌ ์ธ์Šคํ„ด์Šค""" return MarketRegimeTool(mock_db_manager, mock_cache_manager) @pytest.fixture def sample_regime_data(self): """์ƒ˜ํ”Œ ์‹œ์žฅ ๊ตญ๋ฉด ๋ฐ์ดํ„ฐ (๋‹ค์–‘ํ•œ ๊ตญ๋ฉด ํฌํ•จ)""" base_date = datetime.now().date() data = [] # 120์ผ ๋ฐ์ดํ„ฐ - ์—ฌ๋Ÿฌ ๊ตญ๋ฉด์„ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ for i in range(120): date = base_date - timedelta(days=i) if i < 30: # Bull market (์ตœ๊ทผ 30์ผ) price_trend = 0.002 # ์ƒ์Šน ์ถ”์„ธ volatility = 0.012 # ๋‚ฎ์€ ๋ณ€๋™์„ฑ volume_factor = 1.1 # ์•ฝ๊ฐ„ ๋†’์€ ๊ฑฐ๋ž˜๋Ÿ‰ elif i < 60: # Sideways market (30-60์ผ) price_trend = 0.0005 # ์•ฝํ•œ ์ƒ์Šน volatility = 0.008 # ๋งค์šฐ ๋‚ฎ์€ ๋ณ€๋™์„ฑ volume_factor = 0.9 # ๋‚ฎ์€ ๊ฑฐ๋ž˜๋Ÿ‰ elif i < 90: # Volatile market (60-90์ผ) price_trend = 0.001 # ํ˜ผ์žฌ volatility = 0.025 # ๋†’์€ ๋ณ€๋™์„ฑ volume_factor = 1.3 # ๋†’์€ ๊ฑฐ๋ž˜๋Ÿ‰ else: # Bear market (90-120์ผ) price_trend = -0.0015 # ํ•˜๋ฝ ์ถ”์„ธ volatility = 0.018 # ์ค‘๊ฐ„ ๋ณ€๋™์„ฑ volume_factor = 1.2 # ๋†’์€ ๊ฑฐ๋ž˜๋Ÿ‰ # ์‹œ์žฅ ๋ฐ์ดํ„ฐ ์ƒ์„ฑ base_price = 2650 daily_return = random.gauss(price_trend, volatility) volume = int(450000000 * volume_factor * (1 + random.gauss(0, 0.2))) data.append({ "date": date, "market": "KOSPI", "close_price": base_price, "daily_return": daily_return, "volume": max(volume, 100000000), "volatility": volatility + random.uniform(-0.003, 0.003), "rsi": 50 + random.gauss(0, 15), "macd": random.gauss(0, 2), "bollinger_position": random.uniform(0, 1), "vix": 20 + random.gauss(0, 8), "put_call_ratio": 0.8 + random.uniform(-0.3, 0.3), "advance_decline_ratio": 1.0 + random.gauss(0, 0.3) }) return data def test_tool_initialization(self, regime_tool, mock_db_manager, mock_cache_manager): """๋„๊ตฌ ์ดˆ๊ธฐํ™” ํ…Œ์ŠคํŠธ""" assert regime_tool.name == "get_market_regime" assert regime_tool.description is not None assert "๊ตญ๋ฉด" in regime_tool.description or "regime" in regime_tool.description.lower() assert regime_tool.db_manager == mock_db_manager assert regime_tool.cache_manager == mock_cache_manager def test_tool_definition(self, regime_tool): """๋„๊ตฌ ์ •์˜ ํ…Œ์ŠคํŠธ""" definition = regime_tool.get_tool_definition() assert definition.name == "get_market_regime" 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 "analysis_methods" in properties assert "lookback_period" in properties assert "regime_types" in properties assert "include_predictions" in properties # analysis_methods ํŒŒ๋ผ๋ฏธํ„ฐ ๊ฒ€์ฆ methods_prop = properties["analysis_methods"] assert methods_prop["type"] == "array" assert "hmm" in str(methods_prop) assert "statistical" in str(methods_prop) assert "technical" in str(methods_prop) @pytest.mark.asyncio async def test_execute_hmm_regime_analysis(self, regime_tool, sample_regime_data): """HMM ๊ธฐ๋ฐ˜ ์‹œ์žฅ ๊ตญ๋ฉด ๋ถ„์„ ํ…Œ์ŠคํŠธ""" # ์บ์‹œ ๋ฏธ์Šค regime_tool.cache_manager.get.return_value = None # ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์‘๋‹ต ์„ค์ • regime_tool.db_manager.fetch_all.return_value = sample_regime_data # ์‹คํ–‰ result = await regime_tool.execute({ "market": "KOSPI", "analysis_methods": ["hmm"], "lookback_period": "120d", "n_regimes": 3 }) # ๊ฒฐ๊ณผ ๊ฒ€์ฆ 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 "regime_analysis_results" in data # HMM ๊ฒฐ๊ณผ ๊ฒ€์ฆ results = data["regime_analysis_results"] assert "hmm" in results hmm_results = results["hmm"] assert "current_regime" in hmm_results assert "regime_probabilities" in hmm_results assert "regime_history" in hmm_results assert "transition_matrix" in hmm_results assert "regime_characteristics" in hmm_results @pytest.mark.asyncio async def test_execute_statistical_regime_analysis(self, regime_tool, sample_regime_data): """ํ†ต๊ณ„์  ์‹œ์žฅ ๊ตญ๋ฉด ๋ถ„์„ ํ…Œ์ŠคํŠธ""" regime_tool.cache_manager.get.return_value = None regime_tool.db_manager.fetch_all.return_value = sample_regime_data # ์‹คํ–‰ result = await regime_tool.execute({ "market": "KOSPI", "analysis_methods": ["statistical"], "lookback_period": "90d", "volatility_threshold": 0.02 }) # ๊ฒฐ๊ณผ ๊ฒ€์ฆ content = result[0] import json data = json.loads(content.text) results = data["regime_analysis_results"] assert "statistical" in results stat_results = results["statistical"] assert "volatility_regime" in stat_results assert "return_regime" in stat_results assert "volume_regime" in stat_results assert "regime_classification" in stat_results @pytest.mark.asyncio async def test_execute_technical_regime_analysis(self, regime_tool, sample_regime_data): """๊ธฐ์ˆ ์  ์ง€ํ‘œ ๊ธฐ๋ฐ˜ ์‹œ์žฅ ๊ตญ๋ฉด ๋ถ„์„ ํ…Œ์ŠคํŠธ""" regime_tool.cache_manager.get.return_value = None regime_tool.db_manager.fetch_all.return_value = sample_regime_data # ์‹คํ–‰ result = await regime_tool.execute({ "market": "KOSPI", "analysis_methods": ["technical"], "lookback_period": "60d" }) # ๊ฒฐ๊ณผ ๊ฒ€์ฆ content = result[0] import json data = json.loads(content.text) results = data["regime_analysis_results"] assert "technical" in results tech_results = results["technical"] assert "trend_regime" in tech_results assert "momentum_regime" in tech_results assert "overbought_oversold" in tech_results assert "support_resistance_levels" in tech_results @pytest.mark.asyncio async def test_comprehensive_regime_analysis(self, regime_tool, sample_regime_data): """์ข…ํ•ฉ ์‹œ์žฅ ๊ตญ๋ฉด ๋ถ„์„ ํ…Œ์ŠคํŠธ (๋ชจ๋“  ๋ฐฉ๋ฒ•)""" regime_tool.cache_manager.get.return_value = None regime_tool.db_manager.fetch_all.return_value = sample_regime_data # ์‹คํ–‰ result = await regime_tool.execute({ "market": "KOSPI", "analysis_methods": ["hmm", "statistical", "technical"], "lookback_period": "120d", "n_regimes": 4, "include_predictions": True, "include_strategy_mapping": True, "prediction_horizon": 10 }) # ๊ฒฐ๊ณผ ๊ฒ€์ฆ content = result[0] import json data = json.loads(content.text) assert "regime_analysis_results" in data assert "regime_consensus" in data assert "predictions" in data assert "strategy_mapping" in data # ๋ชจ๋“  ๋ถ„์„ ๋ฐฉ๋ฒ• ๊ฒฐ๊ณผ ํ™•์ธ results = data["regime_analysis_results"] assert "hmm" in results assert "statistical" in results assert "technical" in results # ์ปจ์„ผ์„œ์Šค ํ™•์ธ consensus = data["regime_consensus"] assert "overall_regime" in consensus assert "confidence_score" in consensus assert "regime_strength" in consensus def test_hmm_model_training(self, regime_tool): """HMM ๋ชจ๋ธ ํ›ˆ๋ จ ํ…Œ์ŠคํŠธ""" # ์‹œ๊ณ„์—ด ๋ฐ์ดํ„ฐ (๋ณ€๋™์„ฑ์ด ๋‹ค๋ฅธ 3๊ฐœ ๊ตฌ๊ฐ„) returns = ( [random.gauss(0.001, 0.01) for _ in range(30)] + # ์ €๋ณ€๋™์„ฑ [random.gauss(0.002, 0.02) for _ in range(30)] + # ์ค‘๋ณ€๋™์„ฑ [random.gauss(-0.001, 0.015) for _ in range(30)] # ํ•˜๋ฝ+์ค‘๋ณ€๋™์„ฑ ) model = regime_tool._train_hmm_model(returns, n_regimes=3) assert "states" in model assert "transition_matrix" in model assert "emission_params" in model assert len(model["states"]) == len(returns) assert len(model["transition_matrix"]) == 3 assert len(model["transition_matrix"][0]) == 3 def test_volatility_regime_detection(self, regime_tool): """๋ณ€๋™์„ฑ ์ฒด์ œ ํƒ์ง€ ํ…Œ์ŠคํŠธ""" # ๋ณ€๋™ํ•˜๋Š” ๋ณ€๋™์„ฑ ๋ฐ์ดํ„ฐ volatilities = [0.01] * 20 + [0.03] * 20 + [0.015] * 20 regime = regime_tool._detect_volatility_regime(volatilities, threshold=0.02) assert "current_regime" in regime assert "regime_history" in regime assert regime["current_regime"] in ["low", "medium", "high"] def test_return_regime_classification(self, regime_tool): """์ˆ˜์ต๋ฅ  ์ฒด์ œ ๋ถ„๋ฅ˜ ํ…Œ์ŠคํŠธ""" # Bull, Bear, Sideways ํŒจํ„ด returns = [0.002] * 20 + [-0.0015] * 20 + [0.0003] * 20 regime = regime_tool._classify_return_regime(returns) assert "current_regime" in regime assert "regime_probability" in regime assert regime["current_regime"] in ["bull", "bear", "sideways"] assert 0 <= regime["regime_probability"] <= 1 def test_technical_indicator_analysis(self, regime_tool): """๊ธฐ์ˆ ์  ์ง€ํ‘œ ๋ถ„์„ ํ…Œ์ŠคํŠธ""" # ์ƒ˜ํ”Œ ๊ธฐ์ˆ  ์ง€ํ‘œ ๋ฐ์ดํ„ฐ technical_data = [ {"rsi": 70, "macd": 2.5, "bollinger_position": 0.9}, # Overbought {"rsi": 30, "macd": -1.8, "bollinger_position": 0.1}, # Oversold {"rsi": 50, "macd": 0.2, "bollinger_position": 0.5} # Neutral ] analysis = regime_tool._analyze_technical_indicators(technical_data) assert "trend_regime" in analysis assert "momentum_regime" in analysis assert "overbought_oversold" in analysis def test_regime_transition_detection(self, regime_tool): """๊ตญ๋ฉด ์ „ํ™˜ ํƒ์ง€ ํ…Œ์ŠคํŠธ""" # ๊ตญ๋ฉด ์ „ํ™˜ ์‹œ๊ณ„์—ด (0 -> 1 -> 2) regime_sequence = [0] * 30 + [1] * 30 + [2] * 30 transitions = regime_tool._detect_regime_transitions(regime_sequence) assert len(transitions) >= 2 # ์ตœ์†Œ 2๋ฒˆ์˜ ์ „ํ™˜ assert all("from_regime" in t and "to_regime" in t for t in transitions) assert all("transition_date" in t for t in transitions) def test_regime_persistence_analysis(self, regime_tool): """๊ตญ๋ฉด ์ง€์†์„ฑ ๋ถ„์„ ํ…Œ์ŠคํŠธ""" # ๋‹ค์–‘ํ•œ ์ง€์†์„ฑ์„ ๊ฐ€์ง„ ๊ตญ๋ฉด regime_sequence = [0] * 50 + [1] * 20 + [2] * 30 persistence = regime_tool._analyze_regime_persistence(regime_sequence) assert "average_duration" in persistence assert "regime_stability" in persistence assert "transition_frequency" in persistence assert persistence["average_duration"] > 0 def test_market_regime_prediction(self, regime_tool): """์‹œ์žฅ ๊ตญ๋ฉด ์˜ˆ์ธก ํ…Œ์ŠคํŠธ""" # HMM ๋ชจ๋ธ ๊ฒฐ๊ณผ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ hmm_model = { "states": [0, 1, 0, 1, 1, 2], "transition_matrix": [ [0.7, 0.2, 0.1], [0.3, 0.5, 0.2], [0.1, 0.3, 0.6] ] } predictions = regime_tool._predict_regime_changes(hmm_model, horizon=5) assert "predicted_regimes" in predictions assert "probability_forecast" in predictions assert "regime_change_probability" in predictions assert len(predictions["predicted_regimes"]) == 5 def test_investment_strategy_mapping(self, regime_tool): """ํˆฌ์ž ์ „๋žต ๋งคํ•‘ ํ…Œ์ŠคํŠธ""" current_regime = { "overall_regime": "bull_market", "volatility_level": "medium", "momentum": "positive" } strategies = regime_tool._map_investment_strategies(current_regime) assert "recommended_strategies" in strategies assert "asset_allocation" in strategies assert "risk_management" in strategies assert len(strategies["recommended_strategies"]) > 0 def test_regime_confidence_scoring(self, regime_tool): """๊ตญ๋ฉด ์‹ ๋ขฐ๋„ ์ ์ˆ˜ ํ…Œ์ŠคํŠธ""" # ์—ฌ๋Ÿฌ ๋ฐฉ๋ฒ•์˜ ๊ฒฐ๊ณผ analysis_results = { "hmm": {"current_regime": "bull", "confidence": 0.8}, "statistical": {"current_regime": "bull", "confidence": 0.7}, "technical": {"current_regime": "neutral", "confidence": 0.6} } consensus = regime_tool._calculate_regime_consensus(analysis_results) assert "overall_regime" in consensus assert "confidence_score" in consensus assert "method_agreement" in consensus assert 0 <= consensus["confidence_score"] <= 1 def test_volatility_clustering(self, regime_tool): """๋ณ€๋™์„ฑ ํด๋Ÿฌ์Šคํ„ฐ๋ง ํ…Œ์ŠคํŠธ""" # GARCH ํšจ๊ณผ๊ฐ€ ์žˆ๋Š” ๋ณ€๋™์„ฑ ๋ฐ์ดํ„ฐ volatilities = [] prev_vol = 0.015 for i in range(50): shock = random.gauss(0, 0.002) new_vol = 0.8 * prev_vol + 0.2 * abs(shock) volatilities.append(new_vol) prev_vol = new_vol clusters = regime_tool._detect_volatility_clustering(volatilities) assert "cluster_periods" in clusters assert "garch_effects" in clusters assert len(clusters["cluster_periods"]) > 0 @pytest.mark.asyncio async def test_regime_backtest_framework(self, regime_tool, sample_regime_data): """๊ตญ๋ฉด ๋ฐฑํ…Œ์ŠคํŒ… ํ”„๋ ˆ์ž„์›Œํฌ ํ…Œ์ŠคํŠธ""" # ๋ฐฑํ…Œ์ŠคํŒ…์šฉ ๊ณผ๊ฑฐ ๋ฐ์ดํ„ฐ historical_data = sample_regime_data[-60:] # ์ตœ๊ทผ 60์ผ ์ œ์™ธ regime_tool.cache_manager.get.return_value = None regime_tool.db_manager.fetch_all.return_value = sample_regime_data result = await regime_tool.execute({ "market": "KOSPI", "analysis_methods": ["hmm"], "lookback_period": "120d", "include_backtesting": True, "backtest_period": "60d" }) content = result[0] import json data = json.loads(content.text) if "backtesting_results" in data: backtest = data["backtesting_results"] assert "accuracy_metrics" in backtest assert "strategy_performance" in backtest assert "regime_prediction_accuracy" in backtest["accuracy_metrics"] @pytest.mark.asyncio async def test_cache_functionality(self, regime_tool): """์บ์‹œ ๊ธฐ๋Šฅ ํ…Œ์ŠคํŠธ""" # ์บ์‹œ ํžˆํŠธ ์‹œ๋‚˜๋ฆฌ์˜ค cached_data = { "timestamp": datetime.now().isoformat(), "market": "KOSPI", "regime_analysis_results": { "hmm": {"current_regime": "bull_market"} } } regime_tool.cache_manager.get.return_value = cached_data # ์‹คํ–‰ result = await regime_tool.execute({ "market": "KOSPI", "analysis_methods": ["hmm"], "lookback_period": "60d" }) # ์บ์‹œ์—์„œ ๋ฐ์ดํ„ฐ ๋ฐ˜ํ™˜ ํ™•์ธ content = result[0] import json data = json.loads(content.text) assert data == cached_data # ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํ˜ธ์ถœ ์—†์Œ ํ™•์ธ regime_tool.db_manager.fetch_all.assert_not_called() @pytest.mark.asyncio async def test_error_handling(self, regime_tool): """์—๋Ÿฌ ์ฒ˜๋ฆฌ ํ…Œ์ŠคํŠธ""" regime_tool.cache_manager.get.return_value = None regime_tool.db_manager.fetch_all.side_effect = DatabaseConnectionError("DB ์—ฐ๊ฒฐ ์‹คํŒจ") with pytest.raises(DatabaseConnectionError): await regime_tool.execute({ "market": "KOSPI", "analysis_methods": ["hmm"] }) @pytest.mark.asyncio async def test_invalid_parameters(self, regime_tool): """์ž˜๋ชป๋œ ํŒŒ๋ผ๋ฏธํ„ฐ ํ…Œ์ŠคํŠธ""" # ์ž˜๋ชป๋œ ์‹œ์žฅ with pytest.raises(ValueError, match="Invalid market"): await regime_tool.execute({ "market": "INVALID", "analysis_methods": ["hmm"] }) # ๋นˆ ๋ถ„์„ ๋ฐฉ๋ฒ• ๋ชฉ๋ก with pytest.raises(ValueError, match="At least one analysis method"): await regime_tool.execute({ "market": "KOSPI", "analysis_methods": [] }) # ์ž˜๋ชป๋œ ๊ตญ๋ฉด ์ˆ˜ with pytest.raises(ValueError, match="Invalid number of regimes"): await regime_tool.execute({ "market": "KOSPI", "analysis_methods": ["hmm"], "n_regimes": 10 }) @pytest.mark.asyncio async def test_insufficient_data_handling(self, regime_tool): """๋ฐ์ดํ„ฐ ๋ถ€์กฑ ์ฒ˜๋ฆฌ ํ…Œ์ŠคํŠธ""" # ๋ฐ์ดํ„ฐ๊ฐ€ ๋ถ€์กฑํ•œ ๊ฒฝ์šฐ (10์ผ ๋ฐ์ดํ„ฐ) insufficient_data = [ { "date": datetime.now().date() - timedelta(days=i), "market": "KOSPI", "close_price": 2650, "daily_return": 0.001, "volume": 450000000, "volatility": 0.015 } for i in range(10) ] regime_tool.cache_manager.get.return_value = None regime_tool.db_manager.fetch_all.return_value = insufficient_data result = await regime_tool.execute({ "market": "KOSPI", "analysis_methods": ["hmm"], "lookback_period": "60d" }) content = result[0] import json data = json.loads(content.text) assert "warning" in data or "insufficient data" in str(data).lower() def test_feature_engineering(self, regime_tool, sample_regime_data): """๊ตญ๋ฉด ๋ถ„์„์„ ์œ„ํ•œ ํ”ผ์ฒ˜ ์—”์ง€๋‹ˆ์–ด๋ง ํ…Œ์ŠคํŠธ""" features = regime_tool._engineer_regime_features(sample_regime_data[:30]) assert len(features) == 30 assert "volatility_ma" in features[0] assert "return_ma" in features[0] assert "volume_ratio" in features[0] assert "momentum_score" in features[0] def test_regime_characterization(self, regime_tool): """๊ตญ๋ฉด ํŠน์„ฑํ™” ํ…Œ์ŠคํŠธ""" # 3๊ฐœ ๊ตญ๋ฉด์˜ ํŠน์„ฑ ๋ฐ์ดํ„ฐ regime_data = { 0: {"returns": [0.002, 0.003, 0.001], "volatilities": [0.01, 0.012, 0.009]}, 1: {"returns": [-0.001, -0.002, 0.001], "volatilities": [0.025, 0.030, 0.022]}, 2: {"returns": [0.0005, -0.0003, 0.0008], "volatilities": [0.008, 0.007, 0.009]} } characteristics = regime_tool._characterize_regimes(regime_data) assert len(characteristics) == 3 for regime_id, char in characteristics.items(): assert "mean_return" in char assert "mean_volatility" in char assert "regime_label" in char

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