401 lines
15 KiB
Python
401 lines
15 KiB
Python
"""
|
|
Test Suite for Indian Market Functionality
|
|
Comprehensive tests for Indian stock market features
|
|
"""
|
|
|
|
import unittest
|
|
import pytest
|
|
from datetime import datetime, timedelta
|
|
from unittest.mock import Mock, patch, MagicMock
|
|
import sys
|
|
import os
|
|
|
|
# Add project root to path
|
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
|
|
from tradingagents.indian_config import (
|
|
get_indian_config,
|
|
get_major_stocks,
|
|
get_sector_stocks,
|
|
is_market_open,
|
|
get_market_status
|
|
)
|
|
from tradingagents.dataflows.ticker_utils import (
|
|
TickerFormatter,
|
|
TickerValidator,
|
|
TickerConverter,
|
|
TickerManager,
|
|
format_indian_ticker,
|
|
validate_indian_ticker
|
|
)
|
|
|
|
class TestIndianConfig(unittest.TestCase):
|
|
"""Test Indian market configuration"""
|
|
|
|
def test_get_indian_config(self):
|
|
"""Test getting Indian configuration"""
|
|
config = get_indian_config()
|
|
|
|
self.assertIsInstance(config, dict)
|
|
self.assertEqual(config["market_region"], "india")
|
|
self.assertEqual(config["currency"], "INR")
|
|
self.assertEqual(config["timezone"], "Asia/Kolkata")
|
|
self.assertIn("NSE", config["exchanges"]["supported"])
|
|
self.assertIn("BSE", config["exchanges"]["supported"])
|
|
|
|
def test_get_major_stocks(self):
|
|
"""Test getting major stocks list"""
|
|
stocks = get_major_stocks()
|
|
|
|
self.assertIsInstance(stocks, dict)
|
|
self.assertIn("RELIANCE", stocks)
|
|
self.assertIn("TCS", stocks)
|
|
self.assertIn("HDFCBANK", stocks)
|
|
|
|
# Check stock structure
|
|
reliance = stocks["RELIANCE"]
|
|
self.assertIn("name", reliance)
|
|
self.assertIn("sector", reliance)
|
|
self.assertIn("exchange", reliance)
|
|
|
|
def test_get_sector_stocks(self):
|
|
"""Test getting sector stocks"""
|
|
banking_stocks = get_sector_stocks("banking")
|
|
it_stocks = get_sector_stocks("it")
|
|
|
|
self.assertIsInstance(banking_stocks, list)
|
|
self.assertIsInstance(it_stocks, list)
|
|
self.assertIn("HDFCBANK", banking_stocks)
|
|
self.assertIn("TCS", it_stocks)
|
|
|
|
# Test non-existent sector
|
|
invalid_stocks = get_sector_stocks("invalid_sector")
|
|
self.assertEqual(invalid_stocks, [])
|
|
|
|
@patch('tradingagents.indian_config.datetime')
|
|
def test_market_status_functions(self, mock_datetime):
|
|
"""Test market status and timing functions"""
|
|
# Mock a weekday during market hours (Tuesday 10:00 AM IST)
|
|
mock_now = Mock()
|
|
mock_now.weekday.return_value = 1 # Tuesday
|
|
mock_now.hour = 10
|
|
mock_now.minute = 0
|
|
mock_datetime.datetime.now.return_value = mock_now
|
|
mock_datetime.datetime.now().replace.return_value = mock_now
|
|
|
|
# Test during market hours
|
|
# Note: These tests might need adjustment based on actual implementation
|
|
status = get_market_status()
|
|
self.assertIsInstance(status, str)
|
|
|
|
# Test weekend
|
|
mock_now.weekday.return_value = 5 # Saturday
|
|
status = get_market_status()
|
|
self.assertEqual(status, "closed_weekend")
|
|
|
|
class TestTickerUtils(unittest.TestCase):
|
|
"""Test ticker utilities"""
|
|
|
|
def test_ticker_formatter(self):
|
|
"""Test ticker formatting"""
|
|
# Test NSE formatting
|
|
nse_ticker = TickerFormatter.format_ticker("RELIANCE", "NSE")
|
|
self.assertEqual(nse_ticker, "RELIANCE.NS")
|
|
|
|
# Test BSE formatting
|
|
bse_ticker = TickerFormatter.format_ticker("RELIANCE", "BSE")
|
|
self.assertEqual(bse_ticker, "RELIANCE.BO")
|
|
|
|
# Test with already formatted ticker
|
|
formatted_ticker = TickerFormatter.format_ticker("TCS.NS", "NSE")
|
|
self.assertEqual(formatted_ticker, "TCS.NS")
|
|
|
|
# Test plain symbol extraction
|
|
plain = TickerFormatter.get_plain_symbol("RELIANCE.NS")
|
|
self.assertEqual(plain, "RELIANCE")
|
|
|
|
plain = TickerFormatter.get_plain_symbol("RELIANCE")
|
|
self.assertEqual(plain, "RELIANCE")
|
|
|
|
def test_ticker_validator(self):
|
|
"""Test ticker validation"""
|
|
# Test valid NSE tickers
|
|
self.assertTrue(TickerValidator.is_valid_nse_ticker("RELIANCE.NS"))
|
|
self.assertTrue(TickerValidator.is_valid_nse_ticker("TCS.NS"))
|
|
|
|
# Test valid BSE tickers
|
|
self.assertTrue(TickerValidator.is_valid_bse_ticker("500325.BO"))
|
|
self.assertTrue(TickerValidator.is_valid_bse_ticker("532540.BO"))
|
|
|
|
# Test invalid formats
|
|
self.assertFalse(TickerValidator.is_valid_nse_ticker("RELIANCE"))
|
|
self.assertFalse(TickerValidator.is_valid_bse_ticker("RELIANCE.NS"))
|
|
|
|
# Test general validation
|
|
self.assertTrue(TickerValidator.is_valid_indian_ticker("RELIANCE.NS"))
|
|
self.assertTrue(TickerValidator.is_valid_indian_ticker("500325.BO"))
|
|
self.assertFalse(TickerValidator.is_valid_indian_ticker("AAPL"))
|
|
|
|
def test_ticker_converter(self):
|
|
"""Test ticker conversion between exchanges"""
|
|
# Test NSE to BSE conversion
|
|
bse_ticker = TickerConverter.nse_to_bse("RELIANCE")
|
|
self.assertEqual(bse_ticker, "500325.BO")
|
|
|
|
bse_ticker = TickerConverter.nse_to_bse("RELIANCE.NS")
|
|
self.assertEqual(bse_ticker, "500325.BO")
|
|
|
|
# Test BSE to NSE conversion
|
|
nse_ticker = TickerConverter.bse_to_nse("500325")
|
|
self.assertEqual(nse_ticker, "RELIANCE.NS")
|
|
|
|
nse_ticker = TickerConverter.bse_to_nse("500325.BO")
|
|
self.assertEqual(nse_ticker, "RELIANCE.NS")
|
|
|
|
# Test cross-exchange conversion
|
|
cross_ticker = TickerConverter.get_cross_exchange_ticker("RELIANCE.NS")
|
|
self.assertEqual(cross_ticker, "500325.BO")
|
|
|
|
cross_ticker = TickerConverter.get_cross_exchange_ticker("500325.BO")
|
|
self.assertEqual(cross_ticker, "RELIANCE.NS")
|
|
|
|
# Test non-existent mapping
|
|
result = TickerConverter.nse_to_bse("NONEXISTENT")
|
|
self.assertIsNone(result)
|
|
|
|
def test_ticker_manager(self):
|
|
"""Test ticker manager functionality"""
|
|
manager = TickerManager()
|
|
|
|
# Test ticker processing
|
|
result = manager.process_ticker("RELIANCE", "NSE")
|
|
|
|
self.assertIsInstance(result, dict)
|
|
self.assertTrue(result["is_valid"])
|
|
self.assertEqual(result["formatted_ticker"], "RELIANCE.NS")
|
|
self.assertEqual(result["plain_symbol"], "RELIANCE")
|
|
self.assertEqual(result["exchange"], "NSE")
|
|
self.assertEqual(result["cross_exchange_ticker"], "500325.BO")
|
|
|
|
# Test all formats
|
|
formats = manager.get_all_formats("RELIANCE")
|
|
|
|
self.assertEqual(formats["plain"], "RELIANCE")
|
|
self.assertEqual(formats["nse"], "RELIANCE.NS")
|
|
self.assertEqual(formats["bse"], "RELIANCE.BO")
|
|
self.assertEqual(formats["bse_equivalent"], "500325.BO")
|
|
|
|
def test_convenience_functions(self):
|
|
"""Test convenience functions"""
|
|
# Test formatting
|
|
ticker = format_indian_ticker("RELIANCE", "NSE")
|
|
self.assertEqual(ticker, "RELIANCE.NS")
|
|
|
|
# Test validation
|
|
is_valid = validate_indian_ticker("RELIANCE.NS")
|
|
self.assertTrue(is_valid)
|
|
|
|
is_valid = validate_indian_ticker("INVALID")
|
|
self.assertFalse(is_valid)
|
|
|
|
class TestIndianMarketUtils(unittest.TestCase):
|
|
"""Test Indian market data utilities"""
|
|
|
|
@patch('tradingagents.dataflows.indian_market_utils.requests.get')
|
|
def test_alpha_vantage_api(self, mock_get):
|
|
"""Test Alpha Vantage API client"""
|
|
from tradingagents.dataflows.indian_market_utils import AlphaVantageAPI
|
|
|
|
# Mock successful response
|
|
mock_response = Mock()
|
|
mock_response.json.return_value = {
|
|
"Time Series (Daily)": {
|
|
"2024-01-01": {
|
|
"1. open": "100.0",
|
|
"2. high": "105.0",
|
|
"3. low": "99.0",
|
|
"4. close": "103.0",
|
|
"5. volume": "1000000"
|
|
}
|
|
}
|
|
}
|
|
mock_response.raise_for_status.return_value = None
|
|
mock_get.return_value = mock_response
|
|
|
|
api = AlphaVantageAPI("test_key")
|
|
|
|
# Test rate limiter initialization
|
|
self.assertIsNotNone(api.rate_limiter)
|
|
|
|
# Test data retrieval
|
|
df = api.get_daily_data("RELIANCE.NS")
|
|
|
|
self.assertIsNotNone(df)
|
|
# Additional assertions would depend on pandas DataFrame structure
|
|
|
|
@patch('yfinance.Ticker')
|
|
def test_yahoo_finance_api(self, mock_ticker):
|
|
"""Test Yahoo Finance API client"""
|
|
from tradingagents.dataflows.indian_market_utils import YahooFinanceAPI
|
|
|
|
# Mock yfinance response
|
|
mock_ticker_instance = Mock()
|
|
mock_ticker_instance.history.return_value = Mock() # Mock DataFrame
|
|
mock_ticker_instance.info = {"symbol": "RELIANCE.NS", "longName": "Reliance Industries"}
|
|
mock_ticker.return_value = mock_ticker_instance
|
|
|
|
api = YahooFinanceAPI()
|
|
|
|
# Test company info retrieval
|
|
info = api.get_company_info("RELIANCE.NS")
|
|
|
|
self.assertIsInstance(info, dict)
|
|
self.assertEqual(info["symbol"], "RELIANCE.NS")
|
|
|
|
class TestIndianAgentToolkit(unittest.TestCase):
|
|
"""Test Indian agent toolkit"""
|
|
|
|
def setUp(self):
|
|
"""Set up test fixtures"""
|
|
# Mock the toolkit to avoid actual API calls
|
|
self.toolkit_patcher = patch('tradingagents.agents.utils.indian_agent_toolkit.indian_toolkit')
|
|
self.mock_toolkit = self.toolkit_patcher.start()
|
|
|
|
def tearDown(self):
|
|
"""Clean up test fixtures"""
|
|
self.toolkit_patcher.stop()
|
|
|
|
def test_toolkit_initialization(self):
|
|
"""Test toolkit initialization"""
|
|
from tradingagents.agents.utils.indian_agent_toolkit import IndianAgentToolkit
|
|
|
|
# This will test the import and basic initialization
|
|
# Actual functionality testing would require mocking external dependencies
|
|
try:
|
|
toolkit = IndianAgentToolkit()
|
|
self.assertIsNotNone(toolkit)
|
|
except Exception as e:
|
|
# If initialization fails due to missing dependencies, that's expected in test environment
|
|
self.assertIsInstance(e, (ImportError, AttributeError))
|
|
|
|
class TestIndianAnalysts(unittest.TestCase):
|
|
"""Test Indian market analysts"""
|
|
|
|
@patch('tradingagents.agents.utils.agent_utils.AgentUtils')
|
|
def test_fundamentals_analyst_initialization(self, mock_agent_utils):
|
|
"""Test fundamentals analyst initialization"""
|
|
from tradingagents.agents.analysts.indian_fundamentals_analyst import IndianFundamentalsAnalyst
|
|
|
|
try:
|
|
analyst = IndianFundamentalsAnalyst()
|
|
self.assertEqual(analyst.agent_id, "indian_fundamentals_analyst")
|
|
self.assertIsNotNone(analyst.major_stocks)
|
|
self.assertIsInstance(analyst.key_metrics, list)
|
|
except ImportError:
|
|
# Skip if dependencies not available
|
|
self.skipTest("Dependencies not available for analyst testing")
|
|
|
|
@patch('tradingagents.agents.utils.agent_utils.AgentUtils')
|
|
def test_market_analyst_initialization(self, mock_agent_utils):
|
|
"""Test market analyst initialization"""
|
|
from tradingagents.agents.analysts.indian_market_analyst import IndianMarketAnalyst
|
|
|
|
try:
|
|
analyst = IndianMarketAnalyst()
|
|
self.assertEqual(analyst.agent_id, "indian_market_analyst")
|
|
self.assertIsNotNone(analyst.config)
|
|
self.assertIsInstance(analyst.technical_indicators, list)
|
|
except ImportError:
|
|
# Skip if dependencies not available
|
|
self.skipTest("Dependencies not available for analyst testing")
|
|
|
|
class TestIntegration(unittest.TestCase):
|
|
"""Integration tests for Indian market functionality"""
|
|
|
|
def test_end_to_end_ticker_processing(self):
|
|
"""Test end-to-end ticker processing"""
|
|
# Test the complete flow from raw symbol to formatted ticker
|
|
raw_symbol = "reliance"
|
|
|
|
# Format ticker
|
|
formatted = format_indian_ticker(raw_symbol, "NSE")
|
|
self.assertEqual(formatted, "RELIANCE.NS")
|
|
|
|
# Validate ticker
|
|
is_valid = validate_indian_ticker(formatted)
|
|
self.assertTrue(is_valid)
|
|
|
|
# Get cross-exchange equivalent
|
|
manager = TickerManager()
|
|
result = manager.process_ticker(raw_symbol, "NSE")
|
|
|
|
self.assertTrue(result["is_valid"])
|
|
self.assertEqual(result["cross_exchange_ticker"], "500325.BO")
|
|
|
|
def test_config_consistency(self):
|
|
"""Test configuration consistency across modules"""
|
|
config = get_indian_config()
|
|
major_stocks = get_major_stocks()
|
|
|
|
# Ensure major stocks are consistent with config
|
|
self.assertIsInstance(config, dict)
|
|
self.assertIsInstance(major_stocks, dict)
|
|
|
|
# Check that all major stocks have required fields
|
|
for symbol, info in major_stocks.items():
|
|
self.assertIn("name", info)
|
|
self.assertIn("sector", info)
|
|
self.assertIn("exchange", info)
|
|
self.assertIn(info["exchange"], config["exchanges"]["supported"])
|
|
|
|
class TestErrorHandling(unittest.TestCase):
|
|
"""Test error handling in Indian market functionality"""
|
|
|
|
def test_invalid_ticker_handling(self):
|
|
"""Test handling of invalid tickers"""
|
|
manager = TickerManager()
|
|
|
|
# Test with invalid symbol
|
|
result = manager.process_ticker("INVALID_SYMBOL_123", "NSE")
|
|
|
|
# Should still process but may have limitations
|
|
self.assertIsInstance(result, dict)
|
|
self.assertIn("formatted_ticker", result)
|
|
|
|
def test_invalid_sector_handling(self):
|
|
"""Test handling of invalid sectors"""
|
|
stocks = get_sector_stocks("invalid_sector")
|
|
self.assertEqual(stocks, [])
|
|
|
|
def test_api_error_handling(self):
|
|
"""Test API error handling"""
|
|
from tradingagents.dataflows.indian_market_utils import AlphaVantageAPI
|
|
|
|
# Test with invalid API key
|
|
api = AlphaVantageAPI("invalid_key")
|
|
|
|
# This should handle errors gracefully
|
|
# In a real test, we'd mock the API response to return an error
|
|
self.assertIsNotNone(api.api_key)
|
|
|
|
# Test data for mocking
|
|
SAMPLE_MARKET_DATA = """
|
|
Date,Open,High,Low,Close,Volume
|
|
2024-01-01,100.0,105.0,99.0,103.0,1000000
|
|
2024-01-02,103.0,108.0,102.0,107.0,1200000
|
|
2024-01-03,107.0,110.0,105.0,109.0,900000
|
|
"""
|
|
|
|
SAMPLE_FUNDAMENTALS_DATA = {
|
|
"symbol": "RELIANCE.NS",
|
|
"marketCap": 1500000000000,
|
|
"trailingPE": 15.5,
|
|
"priceToBook": 2.1,
|
|
"dividendYield": 0.035,
|
|
"sector": "Energy",
|
|
"industry": "Oil & Gas"
|
|
}
|
|
|
|
if __name__ == '__main__':
|
|
# Run tests
|
|
unittest.main(verbosity=2) |