From 72e0eb15a7a60e9f2fdbf2a9d290a116ed5ad866 Mon Sep 17 00:00:00 2001 From: rgunkar Date: Sat, 5 Jul 2025 19:15:09 +0530 Subject: [PATCH] Adding support for Indian Market --- cli/indian_cli.py | 485 ++++++++++++++++ docs/INDIAN_MARKET_README.md | 485 ++++++++++++++++ examples/indian_market_example.py | 282 ++++++++++ requirements.txt | 9 + tests/test_indian_market.py | 401 +++++++++++++ .../analysts/indian_fundamentals_analyst.py | 406 ++++++++++++++ .../agents/analysts/indian_market_analyst.py | 528 ++++++++++++++++++ .../agents/utils/indian_agent_toolkit.py | 423 ++++++++++++++ tradingagents/dataflows/indian_interface.py | 453 +++++++++++++++ .../dataflows/indian_market_utils.py | 503 +++++++++++++++++ tradingagents/dataflows/ticker_utils.py | 345 ++++++++++++ tradingagents/indian_config.py | 226 ++++++++ 12 files changed, 4546 insertions(+) create mode 100644 cli/indian_cli.py create mode 100644 docs/INDIAN_MARKET_README.md create mode 100644 examples/indian_market_example.py create mode 100644 tests/test_indian_market.py create mode 100644 tradingagents/agents/analysts/indian_fundamentals_analyst.py create mode 100644 tradingagents/agents/analysts/indian_market_analyst.py create mode 100644 tradingagents/agents/utils/indian_agent_toolkit.py create mode 100644 tradingagents/dataflows/indian_interface.py create mode 100644 tradingagents/dataflows/indian_market_utils.py create mode 100644 tradingagents/dataflows/ticker_utils.py create mode 100644 tradingagents/indian_config.py diff --git a/cli/indian_cli.py b/cli/indian_cli.py new file mode 100644 index 00000000..9e611ce7 --- /dev/null +++ b/cli/indian_cli.py @@ -0,0 +1,485 @@ +""" +Indian Market CLI Interface +Command-line interface for Indian stock market analysis and trading +""" + +import click +import json +import sys +from datetime import datetime, timedelta +from typing import Dict, Any, List +import logging + +# Setup logging +logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') +logger = logging.getLogger(__name__) + +try: + from tradingagents.agents.utils.indian_agent_toolkit import indian_toolkit + from tradingagents.indian_config import get_major_stocks, get_sector_stocks, INDIAN_SECTORS + from tradingagents.dataflows.ticker_utils import format_indian_ticker, validate_indian_ticker +except ImportError as e: + logger.error(f"Failed to import required modules: {e}") + sys.exit(1) + +@click.group() +@click.version_option(version='1.0.0') +def indian_cli(): + """Indian Stock Market Analysis CLI + + A comprehensive command-line interface for analyzing Indian stocks, + market conditions, and generating trading insights. + """ + pass + +@indian_cli.group() +def market(): + """Market analysis commands""" + pass + +@indian_cli.group() +def stock(): + """Individual stock analysis commands""" + pass + +@indian_cli.group() +def portfolio(): + """Portfolio management commands""" + pass + +@indian_cli.group() +def sector(): + """Sector analysis commands""" + pass + +@indian_cli.group() +def utils(): + """Utility commands""" + pass + +# Market Commands +@market.command() +@click.option('--date', default=None, help='Analysis date (YYYY-MM-DD), defaults to today') +@click.option('--output', type=click.Choice(['json', 'text']), default='text', help='Output format') +def overview(date, output): + """Get Indian market overview""" + try: + if date is None: + date = datetime.now().strftime("%Y-%m-%d") + + click.echo(f"šŸ“Š Getting Indian market overview for {date}...") + + # Get market overview + market_overview = indian_toolkit.get_market_overview(date) + + # Get market conditions analysis + market_conditions = indian_toolkit.analyze_market_conditions() + + if output == 'json': + result = { + "date": date, + "market_overview": market_overview, + "market_conditions": market_conditions + } + click.echo(json.dumps(result, indent=2)) + else: + click.echo("\n" + "="*80) + click.echo(f"šŸ‡®šŸ‡³ INDIAN MARKET OVERVIEW - {date}") + click.echo("="*80) + click.echo(market_overview) + click.echo("\n" + "="*80) + click.echo("šŸ“ˆ MARKET CONDITIONS ANALYSIS") + click.echo("="*80) + click.echo(market_conditions.get('market_conditions', 'Analysis not available')) + + # Display key metrics + if 'trading_bias' in market_conditions: + click.echo(f"\nšŸŽÆ Trading Bias: {market_conditions['trading_bias']}") + if 'confidence' in market_conditions: + click.echo(f"šŸ“Š Confidence Level: {market_conditions['confidence']:.1%}") + + except Exception as e: + click.echo(f"āŒ Error getting market overview: {e}", err=True) + sys.exit(1) + +@market.command() +@click.option('--output', type=click.Choice(['json', 'text']), default='text', help='Output format') +def status(): + """Check current market status""" + try: + status_info = indian_toolkit.check_market_status() + + if output == 'json': + click.echo(json.dumps(status_info, indent=2)) + else: + click.echo("\n" + "="*50) + click.echo("šŸ‡®šŸ‡³ INDIAN MARKET STATUS") + click.echo("="*50) + + market_status = status_info['market_status'] + is_open = status_info['is_market_open'] + + status_emoji = "🟢" if is_open else "šŸ”“" + click.echo(f"{status_emoji} Market Status: {market_status.upper()}") + click.echo(f"ā° Current Time: {status_info['current_time']}") + + trading_hours = status_info['trading_hours'] + click.echo(f"šŸ•˜ Trading Hours: {trading_hours['open']} - {trading_hours['close']} IST") + + if not is_open: + if market_status == 'pre_market': + click.echo("šŸ’” Market opens in a few minutes. Prepare your watchlist!") + elif market_status == 'closed': + click.echo("šŸ’” Market is closed. Good time for analysis and planning!") + elif market_status == 'closed_weekend': + click.echo("šŸ’” Weekend - Markets closed. Time for research!") + + except Exception as e: + click.echo(f"āŒ Error checking market status: {e}", err=True) + sys.exit(1) + +# Stock Commands +@stock.command() +@click.argument('symbol') +@click.option('--exchange', default='NSE', type=click.Choice(['NSE', 'BSE']), help='Exchange') +@click.option('--days', default=30, help='Number of days of data') +@click.option('--output', type=click.Choice(['json', 'text']), default='text', help='Output format') +def analyze(symbol, exchange, days, output): + """Comprehensive stock analysis""" + try: + symbol = symbol.upper() + click.echo(f"šŸ” Analyzing {symbol} on {exchange}...") + + # Get comprehensive analysis + analysis = indian_toolkit.analyze_fundamentals(symbol, exchange) + technical = indian_toolkit.analyze_technical(symbol, exchange, days) + risk_assessment = indian_toolkit.assess_stock_risk(symbol, exchange) + + if output == 'json': + result = { + "symbol": symbol, + "exchange": exchange, + "fundamental_analysis": analysis, + "technical_analysis": technical, + "risk_assessment": risk_assessment + } + click.echo(json.dumps(result, indent=2)) + else: + click.echo("\n" + "="*80) + click.echo(f"šŸ“Š COMPREHENSIVE ANALYSIS: {symbol}.{exchange}") + click.echo("="*80) + + # Fundamental Analysis + click.echo("\nšŸ¢ FUNDAMENTAL ANALYSIS") + click.echo("-" * 50) + if 'analysis' in analysis: + click.echo(analysis['analysis']) + + if 'recommendation' in analysis: + click.echo(f"\nšŸ’” Investment Recommendation:") + click.echo(analysis['recommendation']) + + # Technical Analysis + click.echo(f"\nšŸ“ˆ TECHNICAL ANALYSIS ({days} days)") + click.echo("-" * 50) + if 'technical_analysis' in technical: + click.echo(technical['technical_analysis']) + + # Risk Assessment + click.echo("\nāš ļø RISK ASSESSMENT") + click.echo("-" * 50) + if 'overall_risk_score' in risk_assessment: + risk_score = risk_assessment['overall_risk_score'] + click.echo(f"Risk Score: {risk_score:.1f}/10") + click.echo(f"Risk Level: {risk_assessment.get('recommendation', 'N/A')}") + + # Key Metrics Summary + click.echo("\nšŸ“‹ SUMMARY") + click.echo("-" * 50) + click.echo(f"Symbol: {symbol}.{exchange}") + click.echo(f"Analysis Date: {analysis.get('analysis_date', 'N/A')}") + click.echo(f"Fundamental Confidence: {analysis.get('confidence', 0):.1%}") + click.echo(f"Technical Confidence: {technical.get('confidence', 0):.1%}") + + except Exception as e: + click.echo(f"āŒ Error analyzing {symbol}: {e}", err=True) + sys.exit(1) + +@stock.command() +@click.argument('symbol') +@click.option('--exchange', default='NSE', type=click.Choice(['NSE', 'BSE']), help='Exchange') +@click.option('--output', type=click.Choice(['json', 'text']), default='text', help='Output format') +def quote(symbol, exchange, output): + """Get real-time stock quote""" + try: + symbol = symbol.upper() + click.echo(f"šŸ’¹ Getting quote for {symbol} on {exchange}...") + + quote_data = indian_toolkit.get_indian_stock_quote(symbol, exchange) + + if output == 'json': + result = { + "symbol": symbol, + "exchange": exchange, + "quote": quote_data + } + click.echo(json.dumps(result, indent=2)) + else: + click.echo("\n" + "="*50) + click.echo(f"šŸ’¹ REAL-TIME QUOTE: {symbol}.{exchange}") + click.echo("="*50) + click.echo(quote_data) + + except Exception as e: + click.echo(f"āŒ Error getting quote for {symbol}: {e}", err=True) + sys.exit(1) + +@stock.command() +@click.argument('symbol') +@click.option('--days', default=30, help='Number of days of historical data') +@click.option('--exchange', default='NSE', type=click.Choice(['NSE', 'BSE']), help='Exchange') +@click.option('--output', type=click.Choice(['json', 'text', 'csv']), default='text', help='Output format') +def data(symbol, days, exchange, output): + """Get historical stock data""" + try: + symbol = symbol.upper() + end_date = datetime.now().strftime("%Y-%m-%d") + start_date = (datetime.now() - timedelta(days=days)).strftime("%Y-%m-%d") + + click.echo(f"šŸ“Š Getting {days} days of data for {symbol} on {exchange}...") + + stock_data = indian_toolkit.get_indian_stock_data(symbol, start_date, end_date, exchange) + + if output == 'json': + result = { + "symbol": symbol, + "exchange": exchange, + "start_date": start_date, + "end_date": end_date, + "data": stock_data + } + click.echo(json.dumps(result, indent=2)) + elif output == 'csv': + click.echo(stock_data) + else: + click.echo("\n" + "="*60) + click.echo(f"šŸ“Š HISTORICAL DATA: {symbol}.{exchange}") + click.echo(f"Period: {start_date} to {end_date}") + click.echo("="*60) + click.echo(stock_data) + + except Exception as e: + click.echo(f"āŒ Error getting data for {symbol}: {e}", err=True) + sys.exit(1) + +# Sector Commands +@sector.command() +@click.argument('sector_name') +@click.option('--date', default=None, help='Analysis date (YYYY-MM-DD)') +@click.option('--output', type=click.Choice(['json', 'text']), default='text', help='Output format') +def analyze(sector_name, date, output): + """Analyze specific sector""" + try: + sector_name = sector_name.lower() + if date is None: + date = datetime.now().strftime("%Y-%m-%d") + + click.echo(f"šŸ­ Analyzing {sector_name.title()} sector...") + + sector_analysis = indian_toolkit.get_sector_analysis(sector_name, date) + sector_stocks = indian_toolkit.get_sector_stocks(sector_name) + + if output == 'json': + result = { + "sector": sector_name, + "analysis_date": date, + "analysis": sector_analysis, + "stocks": sector_stocks + } + click.echo(json.dumps(result, indent=2)) + else: + click.echo("\n" + "="*60) + click.echo(f"šŸ­ SECTOR ANALYSIS: {sector_name.upper()}") + click.echo("="*60) + click.echo(sector_analysis) + + if sector_stocks: + click.echo(f"\nšŸ“ˆ KEY STOCKS IN {sector_name.upper()} SECTOR:") + click.echo("-" * 40) + for i, stock in enumerate(sector_stocks[:10], 1): + click.echo(f"{i:2d}. {stock}") + if len(sector_stocks) > 10: + click.echo(f" ... and {len(sector_stocks) - 10} more") + + except Exception as e: + click.echo(f"āŒ Error analyzing sector {sector_name}: {e}", err=True) + sys.exit(1) + +@sector.command() +def list(): + """List available sectors""" + try: + click.echo("\nšŸ­ AVAILABLE SECTORS FOR ANALYSIS") + click.echo("="*50) + + for i, sector in enumerate(INDIAN_SECTORS.keys(), 1): + stock_count = len(INDIAN_SECTORS[sector]) + click.echo(f"{i:2d}. {sector.title():<15} ({stock_count} stocks)") + + click.echo(f"\nšŸ’” Use 'sector analyze ' to analyze a specific sector") + + except Exception as e: + click.echo(f"āŒ Error listing sectors: {e}", err=True) + sys.exit(1) + +# Portfolio Commands +@portfolio.command() +@click.option('--file', type=click.Path(exists=True), help='Portfolio JSON file') +@click.option('--output', type=click.Choice(['json', 'text']), default='text', help='Output format') +def analyze(file, output): + """Analyze portfolio performance""" + try: + if not file: + click.echo("āŒ Please provide a portfolio file with --file option", err=True) + sys.exit(1) + + click.echo(f"šŸ“Š Analyzing portfolio from {file}...") + + # Load portfolio data + with open(file, 'r') as f: + portfolio_data = json.load(f) + + holdings = portfolio_data.get('holdings', []) + + # Calculate portfolio metrics + metrics = indian_toolkit.calculate_portfolio_metrics(holdings) + + if output == 'json': + click.echo(json.dumps(metrics, indent=2)) + else: + click.echo("\n" + "="*60) + click.echo("šŸ“Š PORTFOLIO ANALYSIS") + click.echo("="*60) + + click.echo(f"Total Investment: ₹{metrics['total_investment']:,.2f}") + click.echo(f"Current Value: ₹{metrics['total_current_value']:,.2f}") + click.echo(f"Total P&L: ₹{metrics['total_pnl']:,.2f}") + click.echo(f"Total P&L %: {metrics['total_pnl_percentage']:.2f}%") + + click.echo(f"\nšŸ“ˆ INDIVIDUAL HOLDINGS:") + click.echo("-" * 60) + for holding in metrics['holdings']: + pnl_emoji = "🟢" if holding['pnl'] >= 0 else "šŸ”“" + click.echo(f"{pnl_emoji} {holding['symbol']:<10} | " + f"Qty: {holding['quantity']:>6} | " + f"P&L: ₹{holding['pnl']:>8.2f} ({holding['pnl_percentage']:>6.2f}%)") + + except Exception as e: + click.echo(f"āŒ Error analyzing portfolio: {e}", err=True) + sys.exit(1) + +@portfolio.command() +@click.argument('symbol') +@click.argument('entry_price', type=float) +@click.argument('stop_loss', type=float) +@click.argument('portfolio_value', type=float) +@click.option('--risk', default=2.0, help='Risk percentage (default: 2%)') +@click.option('--exchange', default='NSE', type=click.Choice(['NSE', 'BSE']), help='Exchange') +def position_size(symbol, entry_price, stop_loss, portfolio_value, risk, exchange): + """Calculate optimal position size""" + try: + symbol = symbol.upper() + risk_decimal = risk / 100 + + click.echo(f"šŸ“Š Calculating position size for {symbol}...") + + position_calc = indian_toolkit.calculate_position_size( + symbol, entry_price, stop_loss, portfolio_value, risk_decimal + ) + + if 'error' in position_calc: + click.echo(f"āŒ Error: {position_calc['error']}", err=True) + sys.exit(1) + + click.echo("\n" + "="*60) + click.echo(f"šŸ“Š POSITION SIZE CALCULATION: {symbol}.{exchange}") + click.echo("="*60) + click.echo(f"Entry Price: ₹{entry_price:,.2f}") + click.echo(f"Stop Loss: ₹{stop_loss:,.2f}") + click.echo(f"Portfolio Value: ₹{portfolio_value:,.2f}") + click.echo(f"Risk Tolerance: {risk}%") + + click.echo(f"\nšŸ“ˆ RECOMMENDATION:") + click.echo(f"Recommended Shares: {position_calc['recommended_shares']:,}") + click.echo(f"Position Value: ₹{position_calc['position_value']:,.2f}") + click.echo(f"Risk Amount: ₹{position_calc['risk_amount']:,.2f}") + click.echo(f"Position %: {position_calc['position_percentage']:.2f}%") + click.echo(f"Risk %: {position_calc['risk_percentage']:.2f}%") + click.echo(f"Risk-Reward: {position_calc['risk_reward_ratio']}") + + except Exception as e: + click.echo(f"āŒ Error calculating position size: {e}", err=True) + sys.exit(1) + +# Utility Commands +@utils.command() +@click.argument('symbol') +@click.option('--exchange', default='NSE', type=click.Choice(['NSE', 'BSE']), help='Exchange') +def validate(symbol, exchange): + """Validate ticker symbol""" + try: + formatted_ticker = format_indian_ticker(symbol, exchange) + is_valid = validate_indian_ticker(formatted_ticker) + + click.echo(f"\nšŸ” TICKER VALIDATION") + click.echo("="*30) + click.echo(f"Input: {symbol}") + click.echo(f"Formatted: {formatted_ticker}") + click.echo(f"Valid: {'āœ… Yes' if is_valid else 'āŒ No'}") + + except Exception as e: + click.echo(f"āŒ Error validating ticker: {e}", err=True) + sys.exit(1) + +@utils.command() +def stocks(): + """List major Indian stocks""" + try: + major_stocks = get_major_stocks() + + click.echo("\nšŸ¢ MAJOR INDIAN STOCKS") + click.echo("="*60) + + for symbol, info in major_stocks.items(): + click.echo(f"{symbol:<12} | {info['name']:<30} | {info['sector']:<15}") + + click.echo(f"\nšŸ“Š Total: {len(major_stocks)} stocks") + + except Exception as e: + click.echo(f"āŒ Error listing stocks: {e}", err=True) + sys.exit(1) + +@utils.command() +def config(): + """Show Indian market configuration""" + try: + from tradingagents.indian_config import get_indian_config + + config = get_indian_config() + + click.echo("\nāš™ļø INDIAN MARKET CONFIGURATION") + click.echo("="*50) + click.echo(f"Market Region: {config['market_region']}") + click.echo(f"Currency: {config['currency']}") + click.echo(f"Timezone: {config['timezone']}") + click.echo(f"Primary Exchange: {config['exchanges']['primary']}") + click.echo(f"Trading Hours: {config['trading_hours']['open']} - {config['trading_hours']['close']}") + click.echo(f"Settlement: {config['market_parameters']['settlement']}") + + except Exception as e: + click.echo(f"āŒ Error showing config: {e}", err=True) + sys.exit(1) + +# Main CLI entry point +if __name__ == '__main__': + indian_cli() \ No newline at end of file diff --git a/docs/INDIAN_MARKET_README.md b/docs/INDIAN_MARKET_README.md new file mode 100644 index 00000000..dfb7a5d1 --- /dev/null +++ b/docs/INDIAN_MARKET_README.md @@ -0,0 +1,485 @@ +# TradingAgents - Indian Stock Market Integration + +## Overview + +This module extends the TradingAgents framework to support the Indian stock market (NSE/BSE), providing comprehensive analysis capabilities tailored for Indian equities, market dynamics, and regulatory environment. + +## Features + +### šŸ‡®šŸ‡³ Indian Market Support +- **NSE (National Stock Exchange)** and **BSE (Bombay Stock Exchange)** integration +- Indian market hours and holidays +- INR currency support +- SEBI regulations compliance +- T+1 settlement cycle + +### šŸ“Š Data Sources +- **Primary**: Alpha Vantage (with Indian stocks support) +- **Secondary**: Yahoo Finance (for NSE/BSE tickers) +- **Fallback**: Direct NSE API (unofficial) +- **News**: Indian financial news sources +- **Sentiment**: Indian social media and forums + +### šŸ”§ Core Components + +#### 1. Configuration (`tradingagents/indian_config.py`) +- Market parameters and trading hours +- Major Indian stocks database +- Sector classifications +- Risk management parameters +- Market holidays calendar + +#### 2. Ticker Utilities (`tradingagents/dataflows/ticker_utils.py`) +- NSE/BSE ticker formatting (`.NS`, `.BO` suffixes) +- Cross-exchange ticker conversion +- Ticker validation and processing +- Support for major Indian stocks + +#### 3. Data Interface (`tradingagents/dataflows/indian_interface.py`) +- Unified data access layer +- Multiple data source fallbacks +- Indian market-specific data formatting +- Error handling and rate limiting + +#### 4. Market Analysts +- **Fundamentals Analyst**: Indian accounting standards, SEBI compliance +- **Market Analyst**: Technical analysis with Indian market patterns +- **Sector Analysis**: Indian industry dynamics + +#### 5. Agent Toolkit (`tradingagents/agents/utils/indian_agent_toolkit.py`) +- Comprehensive toolkit for Indian market operations +- Risk management and position sizing +- Portfolio analysis and tracking +- Market timing and execution + +#### 6. CLI Interface (`cli/indian_cli.py`) +- Command-line interface for analysis +- Market status and overview +- Stock and sector analysis +- Portfolio management + +## Installation + +### Prerequisites +```bash +# Install Python dependencies +pip install -r requirements.txt + +# Additional Indian market dependencies +pip install alpha-vantage click beautifulsoup4 lxml +``` + +### Environment Setup +```bash +# Set up API keys (optional but recommended) +export ALPHA_VANTAGE_API_KEY="your_alpha_vantage_key" +export NEWS_API_KEY="your_news_api_key" +export TWITTER_API_KEY="your_twitter_api_key" +``` + +## Quick Start + +### 1. Basic Usage +```python +from tradingagents.agents.utils.indian_agent_toolkit import indian_toolkit + +# Check market status +status = indian_toolkit.check_market_status() +print(f"Market is {'open' if status['is_market_open'] else 'closed'}") + +# Analyze a stock +analysis = indian_toolkit.analyze_fundamentals("RELIANCE", "NSE") +print(analysis['analysis']) + +# Get technical analysis +technical = indian_toolkit.analyze_technical("TCS", "NSE", lookback_days=30) +print(technical['technical_analysis']) +``` + +### 2. CLI Usage +```bash +# Check market status +python cli/indian_cli.py market status + +# Analyze a stock +python cli/indian_cli.py stock analyze RELIANCE --exchange NSE + +# Get market overview +python cli/indian_cli.py market overview + +# Analyze a sector +python cli/indian_cli.py sector analyze banking + +# Calculate position size +python cli/indian_cli.py portfolio position-size RELIANCE 2500 2400 1000000 --risk 2 +``` + +### 3. Example Script +```bash +# Run the comprehensive example +python examples/indian_market_example.py +``` + +## Supported Stocks + +### Major Indian Stocks +- **Banking**: HDFCBANK, ICICIBANK, SBIN, KOTAKBANK, AXISBANK +- **IT**: TCS, INFY, HCLTECH, WIPRO, TECHM +- **Energy**: RELIANCE, ONGC, IOC, BPCL +- **FMCG**: HINDUNILVR, ITC, NESTLEIND, BRITANNIA +- **Auto**: MARUTI, TATAMOTORS, M&M, BAJAJ-AUTO +- **Pharma**: SUNPHARMA, DRREDDY, CIPLA, DIVISLAB + +### Sectors Supported +- Banking, IT, FMCG, Auto, Pharma, Energy, Telecom, Metals, Cement, NBFC + +## API Reference + +### Core Functions + +#### Market Data +```python +# Get historical data +data = indian_toolkit.get_indian_stock_data("RELIANCE", "2024-01-01", "2024-12-31", "NSE") + +# Get real-time quote +quote = indian_toolkit.get_indian_stock_quote("TCS", "NSE") + +# Get fundamentals +fundamentals = indian_toolkit.get_indian_fundamentals("HDFCBANK", "NSE") +``` + +#### Analysis +```python +# Fundamental analysis +fund_analysis = indian_toolkit.analyze_fundamentals("INFY", "NSE") + +# Technical analysis +tech_analysis = indian_toolkit.analyze_technical("MARUTI", "NSE", lookback_days=60) + +# Market conditions +market_conditions = indian_toolkit.analyze_market_conditions() + +# Sector analysis +sector_analysis = indian_toolkit.get_sector_analysis("banking") +``` + +#### Risk Management +```python +# Position sizing +position = indian_toolkit.calculate_position_size( + symbol="RELIANCE", + entry_price=2500, + stop_loss=2400, + portfolio_value=1000000, + risk_percentage=0.02 +) + +# Risk assessment +risk = indian_toolkit.assess_stock_risk("TCS", "NSE") +``` + +#### Utilities +```python +# Ticker formatting +formatted = indian_toolkit.format_ticker("RELIANCE", "NSE") # Returns "RELIANCE.NS" + +# Ticker validation +is_valid = indian_toolkit.validate_ticker("TCS.NS") # Returns True + +# Get sector stocks +banking_stocks = indian_toolkit.get_sector_stocks("banking") +``` + +### Configuration Options + +#### Market Parameters +```python +from tradingagents.indian_config import get_indian_config + +config = get_indian_config() +print(config['trading_hours']) # {'open': '09:15', 'close': '15:30', ...} +print(config['market_parameters']['settlement']) # 'T+1' +print(config['risk_parameters']['max_position_size']) # 0.05 (5%) +``` + +#### Ticker Utilities +```python +from tradingagents.dataflows.ticker_utils import TickerManager + +manager = TickerManager() +result = manager.process_ticker("RELIANCE", "NSE") +print(result['formatted_ticker']) # 'RELIANCE.NS' +print(result['cross_exchange_ticker']) # '500325.BO' +``` + +## Integration with TradingAgents Framework + +### Graph Integration +The Indian market components integrate seamlessly with the existing TradingAgents graph-based system: + +```python +# Example integration with trading graph +from tradingagents.graph.trading_graph import TradingGraph +from tradingagents.agents.analysts.indian_fundamentals_analyst import IndianFundamentalsAnalyst +from tradingagents.agents.analysts.indian_market_analyst import IndianMarketAnalyst + +# Initialize analysts +fund_analyst = IndianFundamentalsAnalyst() +market_analyst = IndianMarketAnalyst() + +# Use in trading decisions +symbol = "RELIANCE" +fund_analysis = fund_analyst.analyze_fundamentals(symbol, "NSE") +market_analysis = market_analyst.analyze_stock_technical(symbol, "NSE") + +# Combine analyses for trading decision +# (Integration with existing graph logic) +``` + +### Agent Toolkit Integration +```python +# Use Indian toolkit in existing agents +from tradingagents.agents.utils.indian_agent_toolkit import indian_toolkit + +# Replace US data calls with Indian equivalents +indian_data = indian_toolkit.get_indian_stock_data(symbol, start_date, end_date, "NSE") +# Instead of: us_data = get_YFin_data(symbol, start_date, end_date) +``` + +## Testing + +### Run Tests +```bash +# Run all Indian market tests +python -m pytest tests/test_indian_market.py -v + +# Run specific test categories +python -m pytest tests/test_indian_market.py::TestIndianConfig -v +python -m pytest tests/test_indian_market.py::TestTickerUtils -v +``` + +### Test Coverage +- Configuration validation +- Ticker utilities (formatting, validation, conversion) +- Data source integration +- Analyst functionality +- Error handling +- Integration tests + +## CLI Commands Reference + +### Market Commands +```bash +# Market status +python cli/indian_cli.py market status + +# Market overview +python cli/indian_cli.py market overview --date 2024-01-15 +``` + +### Stock Commands +```bash +# Comprehensive analysis +python cli/indian_cli.py stock analyze RELIANCE --exchange NSE --days 30 + +# Get quote +python cli/indian_cli.py stock quote TCS --exchange NSE + +# Historical data +python cli/indian_cli.py stock data HDFCBANK --days 60 --output csv +``` + +### Sector Commands +```bash +# Sector analysis +python cli/indian_cli.py sector analyze banking --date 2024-01-15 + +# List available sectors +python cli/indian_cli.py sector list +``` + +### Portfolio Commands +```bash +# Position sizing +python cli/indian_cli.py portfolio position-size RELIANCE 2500 2400 1000000 --risk 2 + +# Portfolio analysis (requires portfolio JSON file) +python cli/indian_cli.py portfolio analyze --file my_portfolio.json +``` + +### Utility Commands +```bash +# Validate ticker +python cli/indian_cli.py utils validate RELIANCE --exchange NSE + +# List major stocks +python cli/indian_cli.py utils stocks + +# Show configuration +python cli/indian_cli.py utils config +``` + +## Data Sources and APIs + +### Primary: Alpha Vantage +- **Endpoint**: `https://www.alphavantage.co/query` +- **Rate Limit**: 5 calls/minute (free tier) +- **Coverage**: NSE/BSE stocks, fundamentals, technical indicators +- **Setup**: Set `ALPHA_VANTAGE_API_KEY` environment variable + +### Secondary: Yahoo Finance +- **Library**: `yfinance` +- **Rate Limit**: ~30 calls/minute +- **Coverage**: NSE (.NS) and BSE (.BO) tickers +- **Advantages**: Free, reliable, good historical data + +### Fallback: NSE Direct API +- **Endpoint**: `https://www.nseindia.com/api` +- **Rate Limit**: ~20 calls/minute +- **Coverage**: Real-time NSE data +- **Note**: Unofficial API, may require session management + +### News Sources (Planned) +- Economic Times API +- Moneycontrol scraping +- Business Standard RSS +- NSE/BSE announcements + +## Market-Specific Considerations + +### Indian Market Characteristics +- **Trading Hours**: 9:15 AM - 3:30 PM IST (Monday-Friday) +- **Pre-open Session**: 9:00 AM - 9:15 AM IST +- **Settlement**: T+1 (Trade + 1 day) +- **Circuit Breakers**: ±20% for individual stocks, ±10% for indices +- **Lot Sizes**: Vary by stock (usually 1 for equity) + +### Regulatory Environment +- **SEBI**: Securities and Exchange Board of India +- **Disclosure Requirements**: Quarterly results, annual reports +- **FII/DII Limits**: Foreign and domestic institutional investor limits +- **Insider Trading**: Strict regulations and monitoring + +### Currency and Conversion +- **Base Currency**: INR (Indian Rupees) +- **USD-INR Tracking**: Important for FII flows and global correlation +- **Currency Hedging**: Available for international exposure + +## Risk Management + +### Position Sizing +```python +# Conservative approach for Indian markets +position = indian_toolkit.calculate_position_size( + symbol="RELIANCE", + entry_price=2500, + stop_loss=2400, # 4% stop loss + portfolio_value=1000000, # 10 Lakh INR + risk_percentage=0.02 # 2% portfolio risk +) +``` + +### Risk Parameters +- **Maximum Position Size**: 5% of portfolio (configurable) +- **Default Stop Loss**: 8% (higher volatility adjustment) +- **Volatility Adjustment**: 1.2x (Indian markets more volatile) +- **Liquidity Threshold**: ₹10 Lakh daily volume minimum + +### Risk Assessment +- **Fundamental Risks**: Debt levels, promoter holding, pledge status +- **Technical Risks**: Volatility, support/resistance levels +- **Market Risks**: FII flows, currency movement, policy changes +- **Sector Risks**: Regulatory changes, competition, cyclical factors + +## Troubleshooting + +### Common Issues + +#### Import Errors +```bash +# Install missing dependencies +pip install -r requirements.txt +pip install alpha-vantage click beautifulsoup4 +``` + +#### API Rate Limits +```python +# Use multiple data sources +# Alpha Vantage: 5 calls/min +# Yahoo Finance: 30 calls/min +# NSE Direct: 20 calls/min +``` + +#### Data Quality Issues +```python +# Check data availability +data = indian_toolkit.get_indian_stock_data("SYMBOL", start_date, end_date) +if "Error" in data or "No data" in data: + print("Data not available, try different source or date range") +``` + +### Performance Optimization +- Use data caching for repeated requests +- Implement proper rate limiting +- Batch API calls where possible +- Use async operations for multiple stocks + +## Contributing + +### Adding New Stocks +1. Update `MAJOR_INDIAN_STOCKS` in `indian_config.py` +2. Add NSE-BSE mapping in `ticker_utils.py` +3. Update sector classifications +4. Add tests for new stocks + +### Adding New Data Sources +1. Create new API client in `indian_market_utils.py` +2. Add to data source fallback chain +3. Implement rate limiting and error handling +4. Add configuration options + +### Extending Analysis +1. Create new analyst class inheriting from base +2. Implement Indian market-specific logic +3. Add to agent toolkit +4. Create CLI commands + +## Roadmap + +### Phase 1 (Current) +- āœ… Basic NSE/BSE support +- āœ… Fundamental and technical analysis +- āœ… CLI interface +- āœ… Risk management tools + +### Phase 2 (Planned) +- šŸ”„ Real-time news integration +- šŸ”„ Social media sentiment analysis +- šŸ”„ Advanced technical indicators +- šŸ”„ Backtesting with Indian data + +### Phase 3 (Future) +- šŸ“‹ Options and derivatives support +- šŸ“‹ Mutual fund analysis +- šŸ“‹ IPO tracking and analysis +- šŸ“‹ Algorithmic trading integration + +## Support and Resources + +### Documentation +- [Indian Stock Market Basics](https://www.nseindia.com/) +- [SEBI Regulations](https://www.sebi.gov.in/) +- [Alpha Vantage API Docs](https://www.alphavantage.co/documentation/) + +### Community +- GitHub Issues: Report bugs and feature requests +- Discussions: Ask questions and share insights +- Examples: Check `examples/` directory for more use cases + +### Commercial Support +For enterprise features, custom integrations, or professional support, please contact the development team. + +--- + +**Disclaimer**: This software is for educational and research purposes. Always consult with qualified financial advisors before making investment decisions. The developers are not responsible for any financial losses incurred from using this software. \ No newline at end of file diff --git a/examples/indian_market_example.py b/examples/indian_market_example.py new file mode 100644 index 00000000..ba74c6c4 --- /dev/null +++ b/examples/indian_market_example.py @@ -0,0 +1,282 @@ +#!/usr/bin/env python3 +""" +Indian Market Analysis Example +Comprehensive example demonstrating Indian stock market analysis capabilities +""" + +import os +import sys +from datetime import datetime, timedelta +import json + +# Add project root to path +sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + +def main(): + """Main example function demonstrating Indian market analysis""" + + print("šŸ‡®šŸ‡³ TradingAgents - Indian Market Analysis Example") + print("=" * 60) + + try: + # Import Indian market modules + from tradingagents.agents.utils.indian_agent_toolkit import indian_toolkit + from tradingagents.indian_config import get_major_stocks, get_market_status + from tradingagents.dataflows.ticker_utils import format_indian_ticker + + print("āœ… Successfully imported Indian market modules") + + except ImportError as e: + print(f"āŒ Failed to import modules: {e}") + print("Please ensure all dependencies are installed:") + print("pip install -r requirements.txt") + return + + # Example 1: Market Status Check + print("\n" + "="*60) + print("šŸ“Š EXAMPLE 1: Market Status Check") + print("="*60) + + try: + market_status = indian_toolkit.check_market_status() + print(f"Market Status: {market_status['market_status']}") + print(f"Is Open: {market_status['is_market_open']}") + print(f"Current Time: {market_status['current_time']}") + print(f"Trading Hours: {market_status['trading_hours']['open']} - {market_status['trading_hours']['close']} IST") + except Exception as e: + print(f"Error checking market status: {e}") + + # Example 2: Stock Analysis + print("\n" + "="*60) + print("šŸ“ˆ EXAMPLE 2: Individual Stock Analysis") + print("="*60) + + # Analyze Reliance Industries + symbol = "RELIANCE" + exchange = "NSE" + + print(f"Analyzing {symbol} on {exchange}...") + + try: + # Get current quote + print(f"\nšŸ’¹ Current Quote for {symbol}:") + quote = indian_toolkit.get_indian_stock_quote(symbol, exchange) + print(quote[:500] + "..." if len(quote) > 500 else quote) + + # Get fundamental analysis + print(f"\nšŸ¢ Fundamental Analysis for {symbol}:") + fundamentals = indian_toolkit.analyze_fundamentals(symbol, exchange) + if 'analysis' in fundamentals: + analysis_text = fundamentals['analysis'] + print(analysis_text[:800] + "..." if len(analysis_text) > 800 else analysis_text) + + print(f"\nšŸ“Š Analysis Summary:") + print(f"- Confidence: {fundamentals.get('confidence', 0):.1%}") + print(f"- Risk Factors: {len(fundamentals.get('risk_factors', []))}") + print(f"- Opportunities: {len(fundamentals.get('opportunities', []))}") + + except Exception as e: + print(f"Error analyzing {symbol}: {e}") + + # Example 3: Technical Analysis + print("\n" + "="*60) + print("šŸ“ˆ EXAMPLE 3: Technical Analysis") + print("="*60) + + try: + # Perform technical analysis + technical = indian_toolkit.analyze_technical(symbol, exchange, lookback_days=30) + + print(f"Technical Analysis for {symbol}:") + if 'technical_analysis' in technical: + tech_text = technical['technical_analysis'] + print(tech_text[:600] + "..." if len(tech_text) > 600 else tech_text) + + print(f"\nšŸ“Š Technical Summary:") + print(f"- Confidence: {technical.get('confidence', 0):.1%}") + if 'trading_signals' in technical: + signals = technical['trading_signals'] + print(f"- Trading Signals: {len(signals)}") + for i, signal in enumerate(signals[:2], 1): + if 'signal_type' in signal: + print(f" {i}. {signal.get('signal_type', 'N/A')} - Confidence: {signal.get('confidence', 'N/A')}") + + except Exception as e: + print(f"Error in technical analysis: {e}") + + # Example 4: Sector Analysis + print("\n" + "="*60) + print("šŸ­ EXAMPLE 4: Sector Analysis") + print("="*60) + + try: + # Analyze Banking sector + sector = "banking" + print(f"Analyzing {sector.title()} sector...") + + sector_analysis = indian_toolkit.get_sector_analysis(sector) + print(sector_analysis[:600] + "..." if len(sector_analysis) > 600 else sector_analysis) + + # Get sector stocks + sector_stocks = indian_toolkit.get_sector_stocks(sector) + print(f"\nšŸ“ˆ Top stocks in {sector.title()} sector:") + for i, stock in enumerate(sector_stocks[:5], 1): + print(f" {i}. {stock}") + + except Exception as e: + print(f"Error in sector analysis: {e}") + + # Example 5: Portfolio Position Sizing + print("\n" + "="*60) + print("šŸ’° EXAMPLE 5: Position Sizing Calculation") + print("="*60) + + try: + # Calculate position size for a trade + entry_price = 2500.0 # INR + stop_loss = 2400.0 # INR + portfolio_value = 1000000.0 # 10 Lakh INR + risk_percentage = 0.02 # 2% + + position_calc = indian_toolkit.calculate_position_size( + symbol, entry_price, stop_loss, portfolio_value, risk_percentage + ) + + print(f"Position Sizing for {symbol}:") + print(f"- Entry Price: ₹{entry_price:,.2f}") + print(f"- Stop Loss: ₹{stop_loss:,.2f}") + print(f"- Portfolio Value: ₹{portfolio_value:,.2f}") + print(f"- Risk Tolerance: {risk_percentage*100}%") + print(f"\nšŸ“Š Recommendation:") + print(f"- Shares to Buy: {position_calc.get('recommended_shares', 0):,}") + print(f"- Position Value: ₹{position_calc.get('position_value', 0):,.2f}") + print(f"- Risk Amount: ₹{position_calc.get('risk_amount', 0):,.2f}") + print(f"- Risk %: {position_calc.get('risk_percentage', 0):.2f}%") + + except Exception as e: + print(f"Error in position sizing: {e}") + + # Example 6: Market Overview + print("\n" + "="*60) + print("šŸŒ EXAMPLE 6: Market Overview") + print("="*60) + + try: + # Get market overview + market_overview = indian_toolkit.get_market_overview() + print("Indian Market Overview:") + print(market_overview[:800] + "..." if len(market_overview) > 800 else market_overview) + + # Get market conditions + market_conditions = indian_toolkit.analyze_market_conditions() + print(f"\nšŸ“Š Market Conditions Summary:") + print(f"- Trading Bias: {market_conditions.get('trading_bias', 'N/A')}") + print(f"- Confidence: {market_conditions.get('confidence', 0):.1%}") + print(f"- Market Status: {market_conditions.get('market_status', 'N/A')}") + + except Exception as e: + print(f"Error in market overview: {e}") + + # Example 7: Ticker Utilities + print("\n" + "="*60) + print("šŸ”§ EXAMPLE 7: Ticker Utilities") + print("="*60) + + try: + # Demonstrate ticker formatting and validation + test_symbols = ["reliance", "TCS", "HDFC Bank", "infy"] + + print("Ticker Processing Examples:") + for symbol in test_symbols: + try: + formatted_nse = format_indian_ticker(symbol, "NSE") + formatted_bse = format_indian_ticker(symbol, "BSE") + is_valid = indian_toolkit.validate_ticker(formatted_nse) + + print(f"- {symbol:10} → NSE: {formatted_nse:12} | BSE: {formatted_bse:12} | Valid: {is_valid}") + except Exception as e: + print(f"- {symbol:10} → Error: {e}") + + # Show major stocks + major_stocks = indian_toolkit.get_major_stocks_list() + print(f"\nšŸ“Š Major Indian Stocks (showing first 5):") + for i, (symbol, info) in enumerate(list(major_stocks.items())[:5], 1): + print(f" {i}. {symbol:10} - {info['name'][:30]:30} ({info['sector']})") + + except Exception as e: + print(f"Error in ticker utilities: {e}") + + # Example 8: Risk Assessment + print("\n" + "="*60) + print("āš ļø EXAMPLE 8: Risk Assessment") + print("="*60) + + try: + # Assess risk for the stock + risk_assessment = indian_toolkit.assess_stock_risk(symbol, exchange) + + print(f"Risk Assessment for {symbol}:") + print(f"- Overall Risk Score: {risk_assessment.get('overall_risk_score', 0):.1f}/10") + print(f"- Risk Recommendation: {risk_assessment.get('recommendation', 'N/A')}") + + risk_factors = risk_assessment.get('risk_factors', []) + if risk_factors: + print(f"\nāš ļø Key Risk Factors:") + for i, risk in enumerate(risk_factors[:3], 1): + print(f" {i}. {risk}") + + except Exception as e: + print(f"Error in risk assessment: {e}") + + # Example 9: Portfolio Analysis (Mock Data) + print("\n" + "="*60) + print("šŸ“Š EXAMPLE 9: Portfolio Analysis (Mock Data)") + print("="*60) + + try: + # Create mock portfolio data + mock_portfolio = [ + {"symbol": "RELIANCE", "quantity": 100, "avg_price": 2400.0}, + {"symbol": "TCS", "quantity": 50, "avg_price": 3500.0}, + {"symbol": "HDFCBANK", "quantity": 75, "avg_price": 1600.0}, + {"symbol": "INFY", "quantity": 80, "avg_price": 1400.0} + ] + + portfolio_metrics = indian_toolkit.calculate_portfolio_metrics(mock_portfolio) + + print("Portfolio Analysis (Mock Data):") + print(f"- Total Investment: ₹{portfolio_metrics.get('total_investment', 0):,.2f}") + print(f"- Current Value: ₹{portfolio_metrics.get('total_current_value', 0):,.2f}") + print(f"- Total P&L: ₹{portfolio_metrics.get('total_pnl', 0):,.2f}") + print(f"- Total P&L %: {portfolio_metrics.get('total_pnl_percentage', 0):.2f}%") + + print(f"\nšŸ“ˆ Individual Holdings:") + for holding in portfolio_metrics.get('holdings', [])[:3]: + pnl_emoji = "🟢" if holding.get('pnl', 0) >= 0 else "šŸ”“" + print(f" {pnl_emoji} {holding.get('symbol', 'N/A'):10} | " + f"P&L: ₹{holding.get('pnl', 0):8.2f} ({holding.get('pnl_percentage', 0):6.2f}%)") + + except Exception as e: + print(f"Error in portfolio analysis: {e}") + + # Summary + print("\n" + "="*60) + print("āœ… EXAMPLE COMPLETED") + print("="*60) + print("This example demonstrated:") + print("1. āœ… Market status checking") + print("2. āœ… Individual stock analysis (fundamental & technical)") + print("3. āœ… Sector analysis") + print("4. āœ… Position sizing calculations") + print("5. āœ… Market overview and conditions") + print("6. āœ… Ticker utilities and validation") + print("7. āœ… Risk assessment") + print("8. āœ… Portfolio analysis") + print("\nšŸ’” Next Steps:") + print("- Set up API keys for live data (Alpha Vantage, etc.)") + print("- Try the CLI: python cli/indian_cli.py --help") + print("- Run tests: python -m pytest tests/test_indian_market.py") + print("- Explore the graph-based trading system integration") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index a6154cd2..16c0c363 100644 --- a/requirements.txt +++ b/requirements.txt @@ -24,3 +24,12 @@ rich questionary langchain_anthropic langchain-google-genai + +# Indian Market Dependencies +click>=8.0.0 +alpha-vantage>=2.3.1 +beautifulsoup4>=4.10.0 +lxml>=4.6.0 +selenium>=4.0.0 +requests-html>=0.10.0 +schedule>=1.2.0 diff --git a/tests/test_indian_market.py b/tests/test_indian_market.py new file mode 100644 index 00000000..a63360b5 --- /dev/null +++ b/tests/test_indian_market.py @@ -0,0 +1,401 @@ +""" +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) \ No newline at end of file diff --git a/tradingagents/agents/analysts/indian_fundamentals_analyst.py b/tradingagents/agents/analysts/indian_fundamentals_analyst.py new file mode 100644 index 00000000..a631f026 --- /dev/null +++ b/tradingagents/agents/analysts/indian_fundamentals_analyst.py @@ -0,0 +1,406 @@ +""" +Indian Fundamentals Analyst Agent +Specialized agent for analyzing Indian company fundamentals with local market context +""" + +from typing import Dict, Any, List +from datetime import datetime +import logging + +from tradingagents.agents.utils.agent_utils import AgentUtils +from tradingagents.agents.utils.agent_states import AgentState +from tradingagents.dataflows.indian_interface import ( + get_indian_fundamentals_interface, + get_indian_market_data_interface, + get_indian_sector_analysis +) +from tradingagents.indian_config import get_major_stocks, get_sector_stocks + +logger = logging.getLogger(__name__) + +class IndianFundamentalsAnalyst: + """ + Indian Fundamentals Analyst Agent + + Specializes in analyzing Indian company fundamentals with context of: + - Indian accounting standards and financial reporting + - SEBI regulations and compliance + - Indian market dynamics and sectoral trends + - Local economic factors and government policies + """ + + def __init__(self, agent_id: str = "indian_fundamentals_analyst"): + self.agent_id = agent_id + self.agent_utils = AgentUtils() + self.state = AgentState() + + # Indian market specific knowledge + self.major_stocks = get_major_stocks() + + # Key Indian financial metrics to focus on + self.key_metrics = [ + "marketCap", "trailingPE", "priceToBook", "dividendYield", + "eps", "revenue", "sector", "industry", "beta", + "profitMargins", "operatingMargins", "returnOnEquity", + "returnOnAssets", "debtToEquity", "currentRatio", + "quickRatio", "freeCashFlow", "totalCash", "totalDebt" + ] + + # Indian market context factors + self.indian_context_factors = [ + "FII/DII holdings", "Promoter holding", "Pledge percentage", + "Government ownership", "Regulatory environment", "Policy impact", + "Currency exposure", "Export dependency", "Domestic demand", + "Seasonal factors", "Competition landscape" + ] + + def analyze_fundamentals(self, + symbol: str, + exchange: str = "NSE", + analysis_date: str = None) -> Dict[str, Any]: + """ + Perform comprehensive fundamental analysis for Indian stock + + Args: + symbol: Stock symbol + exchange: Exchange (NSE/BSE) + analysis_date: Date of analysis + + Returns: + Dictionary with analysis results + """ + if analysis_date is None: + analysis_date = datetime.now().strftime("%Y-%m-%d") + + try: + # Get fundamental data + fundamentals_data = get_indian_fundamentals_interface(symbol, exchange) + + # Get sector analysis for context + if symbol.upper() in self.major_stocks: + sector = self.major_stocks[symbol.upper()]["sector"] + sector_analysis = get_indian_sector_analysis(sector, analysis_date) + else: + sector_analysis = "Sector analysis not available for this stock" + + # Generate analysis + analysis = self._generate_fundamental_analysis( + symbol, fundamentals_data, sector_analysis, analysis_date + ) + + return { + "agent_id": self.agent_id, + "symbol": symbol, + "exchange": exchange, + "analysis_date": analysis_date, + "analysis": analysis, + "confidence": self._calculate_confidence(fundamentals_data), + "recommendation": self._generate_recommendation(analysis), + "risk_factors": self._identify_risk_factors(symbol, fundamentals_data), + "opportunities": self._identify_opportunities(symbol, fundamentals_data) + } + + except Exception as e: + logger.error(f"Error analyzing fundamentals for {symbol}: {e}") + return { + "agent_id": self.agent_id, + "symbol": symbol, + "error": str(e), + "analysis": f"Unable to analyze fundamentals for {symbol}: {e}" + } + + def _generate_fundamental_analysis(self, + symbol: str, + fundamentals_data: str, + sector_analysis: str, + analysis_date: str) -> str: + """Generate comprehensive fundamental analysis""" + + prompt = f""" + As an expert Indian equity fundamentals analyst, provide a comprehensive analysis of {symbol} based on the following data: + + FUNDAMENTAL DATA: + {fundamentals_data} + + SECTOR CONTEXT: + {sector_analysis} + + ANALYSIS DATE: {analysis_date} + + Please provide your analysis covering: + + 1. FINANCIAL HEALTH ASSESSMENT: + - Revenue growth trends and sustainability + - Profitability metrics (gross, operating, net margins) + - Return ratios (ROE, ROA, ROCE) + - Debt levels and financial leverage + - Cash flow generation and quality + + 2. VALUATION ANALYSIS: + - Current valuation multiples (P/E, P/B, EV/EBITDA) + - Comparison with sector peers and historical averages + - Dividend yield and payout sustainability + - Price-to-earnings growth (PEG) ratio assessment + + 3. INDIAN MARKET SPECIFIC FACTORS: + - Regulatory environment and compliance status + - Government policy impact on the business + - FII/DII holding patterns and trends + - Promoter holding strength and pledge status + - Currency exposure and hedging strategies + + 4. BUSINESS QUALITY ASSESSMENT: + - Competitive positioning in Indian market + - Management quality and corporate governance + - Growth drivers and expansion plans + - Market share and competitive advantages + - ESG (Environmental, Social, Governance) factors + + 5. SECTOR AND MACRO CONTEXT: + - Industry growth prospects in India + - Regulatory changes affecting the sector + - Economic policy impact (GST, tax changes, etc.) + - Infrastructure development benefits + - Demographic trends and consumption patterns + + 6. RISK ASSESSMENT: + - Key business risks and mitigation strategies + - Regulatory and policy risks + - Market competition and disruption risks + - Financial risks (debt, liquidity, currency) + - Operational risks specific to Indian operations + + 7. INVESTMENT THESIS: + - Long-term growth potential + - Value creation opportunities + - Catalyst events and triggers + - Suitable investment horizon + - Risk-adjusted return expectations + + Provide specific, actionable insights with Indian market context. Use INR values where applicable and consider local market dynamics. + """ + + try: + analysis = self.agent_utils.query_gpt_single(prompt) + return analysis + except Exception as e: + logger.error(f"Error generating fundamental analysis: {e}") + return f"Error generating analysis: {e}" + + def _calculate_confidence(self, fundamentals_data: str) -> float: + """Calculate confidence level based on data quality""" + confidence_factors = [] + + # Check data completeness + if "Error" not in fundamentals_data and "No data" not in fundamentals_data: + confidence_factors.append(0.3) + + # Check for key metrics presence + key_metrics_present = sum(1 for metric in self.key_metrics + if metric.lower() in fundamentals_data.lower()) + confidence_factors.append(min(key_metrics_present / len(self.key_metrics), 0.4)) + + # Check for financial statements + if "income_statement" in fundamentals_data.lower(): + confidence_factors.append(0.2) + if "balance_sheet" in fundamentals_data.lower(): + confidence_factors.append(0.1) + + return min(sum(confidence_factors), 1.0) + + def _generate_recommendation(self, analysis: str) -> str: + """Generate investment recommendation based on analysis""" + + prompt = f""" + Based on the following fundamental analysis of an Indian stock, provide a clear investment recommendation: + + ANALYSIS: + {analysis} + + Provide a recommendation in the following format: + + RECOMMENDATION: [BUY/HOLD/SELL] + + TARGET PRICE: [Specific price target in INR with rationale] + + TIME HORIZON: [Short-term (3-6 months) / Medium-term (6-18 months) / Long-term (18+ months)] + + KEY RATIONALE: + - [3-5 key points supporting the recommendation] + + RISK CONSIDERATIONS: + - [2-3 main risks to the investment thesis] + + Keep the recommendation concise and actionable for Indian equity investors. + """ + + try: + recommendation = self.agent_utils.query_gpt_single(prompt) + return recommendation + except Exception as e: + logger.error(f"Error generating recommendation: {e}") + return f"Error generating recommendation: {e}" + + def _identify_risk_factors(self, symbol: str, fundamentals_data: str) -> List[str]: + """Identify key risk factors for the stock""" + + prompt = f""" + Based on the fundamental data for {symbol}, identify the top 5 risk factors for Indian investors: + + DATA: + {fundamentals_data} + + Focus on risks specific to: + - Indian market dynamics + - Regulatory environment + - Business model vulnerabilities + - Financial risks + - Sector-specific challenges + + Provide a list of 5 specific risk factors, each in one sentence. + """ + + try: + risks_text = self.agent_utils.query_gpt_single(prompt) + # Parse into list (simple implementation) + risks = [risk.strip() for risk in risks_text.split('\n') if risk.strip() and not risk.strip().startswith('#')] + return risks[:5] # Limit to top 5 + except Exception as e: + logger.error(f"Error identifying risks: {e}") + return [f"Error identifying risks: {e}"] + + def _identify_opportunities(self, symbol: str, fundamentals_data: str) -> List[str]: + """Identify key opportunities for the stock""" + + prompt = f""" + Based on the fundamental data for {symbol}, identify the top 5 opportunities for Indian investors: + + DATA: + {fundamentals_data} + + Focus on opportunities from: + - Indian market growth potential + - Policy tailwinds and government support + - Business expansion possibilities + - Competitive advantages + - Sector growth drivers + + Provide a list of 5 specific opportunities, each in one sentence. + """ + + try: + opportunities_text = self.agent_utils.query_gpt_single(prompt) + # Parse into list (simple implementation) + opportunities = [opp.strip() for opp in opportunities_text.split('\n') if opp.strip() and not opp.strip().startswith('#')] + return opportunities[:5] # Limit to top 5 + except Exception as e: + logger.error(f"Error identifying opportunities: {e}") + return [f"Error identifying opportunities: {e}"] + + def compare_with_peers(self, + symbol: str, + peer_symbols: List[str], + exchange: str = "NSE") -> Dict[str, Any]: + """ + Compare stock with sector peers + + Args: + symbol: Target stock symbol + peer_symbols: List of peer stock symbols + exchange: Exchange + + Returns: + Comparative analysis + """ + try: + # Get fundamental data for all stocks + all_stocks = [symbol] + peer_symbols + fundamentals_data = {} + + for stock in all_stocks: + fundamentals_data[stock] = get_indian_fundamentals_interface(stock, exchange) + + # Generate comparative analysis + comparison = self._generate_peer_comparison(symbol, fundamentals_data) + + return { + "agent_id": self.agent_id, + "target_symbol": symbol, + "peer_symbols": peer_symbols, + "comparison": comparison, + "analysis_date": datetime.now().strftime("%Y-%m-%d") + } + + except Exception as e: + logger.error(f"Error comparing with peers: {e}") + return { + "agent_id": self.agent_id, + "error": str(e), + "comparison": f"Unable to compare with peers: {e}" + } + + def _generate_peer_comparison(self, symbol: str, fundamentals_data: Dict[str, str]) -> str: + """Generate peer comparison analysis""" + + data_text = "\n\n".join([f"{stock}:\n{data}" for stock, data in fundamentals_data.items()]) + + prompt = f""" + Compare {symbol} with its sector peers based on the following fundamental data: + + {data_text} + + Provide a comprehensive peer comparison covering: + + 1. VALUATION METRICS COMPARISON: + - P/E ratios ranking + - P/B ratios comparison + - EV/EBITDA multiples + - Dividend yields + + 2. PROFITABILITY COMPARISON: + - Gross margins + - Operating margins + - Net margins + - Return on equity + + 3. FINANCIAL STRENGTH COMPARISON: + - Debt-to-equity ratios + - Current ratios + - Cash positions + - Interest coverage + + 4. GROWTH COMPARISON: + - Revenue growth rates + - Earnings growth + - Market share trends + - Expansion plans + + 5. RELATIVE POSITIONING: + - Strengths of {symbol} vs peers + - Weaknesses vs peers + - Unique value propositions + - Investment attractiveness ranking + + Conclude with which stock offers the best risk-adjusted returns for Indian investors. + """ + + try: + comparison = self.agent_utils.query_gpt_single(prompt) + return comparison + except Exception as e: + logger.error(f"Error generating peer comparison: {e}") + return f"Error generating peer comparison: {e}" + +# Example usage and testing +if __name__ == "__main__": + analyst = IndianFundamentalsAnalyst() + + # Test with Reliance Industries + result = analyst.analyze_fundamentals("RELIANCE", "NSE") + print("Analysis Result:") + print(result) + + # Test peer comparison + peer_result = analyst.compare_with_peers("RELIANCE", ["ONGC", "IOC", "BPCL"]) + print("\nPeer Comparison:") + print(peer_result) \ No newline at end of file diff --git a/tradingagents/agents/analysts/indian_market_analyst.py b/tradingagents/agents/analysts/indian_market_analyst.py new file mode 100644 index 00000000..f62de571 --- /dev/null +++ b/tradingagents/agents/analysts/indian_market_analyst.py @@ -0,0 +1,528 @@ +""" +Indian Market Analyst Agent +Specialized agent for analyzing Indian market dynamics, technical patterns, and trading opportunities +""" + +from typing import Dict, Any, List, Optional +from datetime import datetime, timedelta +import logging +import pandas as pd + +from tradingagents.agents.utils.agent_utils import AgentUtils +from tradingagents.agents.utils.agent_states import AgentState +from tradingagents.dataflows.indian_interface import ( + get_indian_market_data_interface, + get_indian_quote_interface, + get_indian_market_overview, + get_indian_technical_indicators +) +from tradingagents.indian_config import get_indian_config, is_market_open, get_market_status + +logger = logging.getLogger(__name__) + +class IndianMarketAnalyst: + """ + Indian Market Analyst Agent + + Specializes in: + - Indian market technical analysis + - Market sentiment and momentum + - Index analysis and correlation + - Trading patterns specific to Indian markets + - Market timing and entry/exit strategies + """ + + def __init__(self, agent_id: str = "indian_market_analyst"): + self.agent_id = agent_id + self.agent_utils = AgentUtils() + self.state = AgentState() + self.config = get_indian_config() + + # Indian market specific parameters + self.major_indices = self.config["benchmark_indices"]["broad_market"] + self.sectoral_indices = self.config["benchmark_indices"]["sectoral"] + self.trading_hours = self.config["trading_hours"] + + # Technical indicators to analyze + self.technical_indicators = [ + "sma_20", "sma_50", "sma_200", "ema_12", "ema_26", + "rsi_14", "macd", "bollinger_bands", "atr", "adx", + "stochastic", "williams_r", "cci", "mfi" + ] + + # Market breadth indicators + self.market_breadth_indicators = [ + "advance_decline_ratio", "new_highs_lows", "volume_analysis", + "india_vix", "fii_dii_flows", "sector_rotation" + ] + + def analyze_market_conditions(self, analysis_date: str = None) -> Dict[str, Any]: + """ + Analyze overall Indian market conditions + + Args: + analysis_date: Date for analysis + + Returns: + Market conditions analysis + """ + if analysis_date is None: + analysis_date = datetime.now().strftime("%Y-%m-%d") + + try: + # Get market overview + market_overview = get_indian_market_overview(analysis_date) + + # Get market status + market_status = get_market_status() + + # Analyze major indices + indices_analysis = self._analyze_major_indices(analysis_date) + + # Generate market conditions analysis + conditions_analysis = self._generate_market_conditions_analysis( + market_overview, indices_analysis, market_status, analysis_date + ) + + return { + "agent_id": self.agent_id, + "analysis_date": analysis_date, + "market_status": market_status, + "market_conditions": conditions_analysis, + "indices_analysis": indices_analysis, + "confidence": self._calculate_market_confidence(conditions_analysis), + "trading_bias": self._determine_trading_bias(conditions_analysis), + "key_levels": self._identify_key_levels(indices_analysis) + } + + except Exception as e: + logger.error(f"Error analyzing market conditions: {e}") + return { + "agent_id": self.agent_id, + "error": str(e), + "analysis": f"Unable to analyze market conditions: {e}" + } + + def analyze_stock_technical(self, + symbol: str, + exchange: str = "NSE", + analysis_date: str = None, + lookback_days: int = 60) -> Dict[str, Any]: + """ + Perform technical analysis on Indian stock + + Args: + symbol: Stock symbol + exchange: Exchange (NSE/BSE) + analysis_date: Date for analysis + lookback_days: Days of historical data to analyze + + Returns: + Technical analysis results + """ + if analysis_date is None: + analysis_date = datetime.now().strftime("%Y-%m-%d") + + try: + # Get historical data + start_date = (datetime.strptime(analysis_date, "%Y-%m-%d") - + timedelta(days=lookback_days)).strftime("%Y-%m-%d") + + market_data = get_indian_market_data_interface( + symbol, start_date, analysis_date, exchange + ) + + # Get current quote + current_quote = get_indian_quote_interface(symbol, exchange) + + # Generate technical analysis + technical_analysis = self._generate_technical_analysis( + symbol, market_data, current_quote, analysis_date, lookback_days + ) + + # Calculate support and resistance levels + support_resistance = self._calculate_support_resistance(market_data) + + return { + "agent_id": self.agent_id, + "symbol": symbol, + "exchange": exchange, + "analysis_date": analysis_date, + "technical_analysis": technical_analysis, + "support_resistance": support_resistance, + "trading_signals": self._generate_trading_signals(technical_analysis), + "risk_levels": self._calculate_risk_levels(symbol, market_data), + "confidence": self._calculate_technical_confidence(market_data) + } + + except Exception as e: + logger.error(f"Error analyzing stock technical for {symbol}: {e}") + return { + "agent_id": self.agent_id, + "symbol": symbol, + "error": str(e), + "analysis": f"Unable to analyze technical for {symbol}: {e}" + } + + def _analyze_major_indices(self, analysis_date: str) -> Dict[str, Any]: + """Analyze major Indian indices""" + indices_data = {} + + # Analyze Nifty 50 and Sensex + for index in self.major_indices: + try: + start_date = (datetime.strptime(analysis_date, "%Y-%m-%d") - + timedelta(days=30)).strftime("%Y-%m-%d") + + index_data = get_indian_market_data_interface( + index, start_date, analysis_date, "NSE" + ) + indices_data[index] = index_data + + except Exception as e: + logger.warning(f"Could not get data for index {index}: {e}") + indices_data[index] = f"Error getting data: {e}" + + return indices_data + + def _generate_market_conditions_analysis(self, + market_overview: str, + indices_analysis: Dict[str, Any], + market_status: str, + analysis_date: str) -> str: + """Generate comprehensive market conditions analysis""" + + indices_data_text = "\n\n".join([ + f"{index}:\n{data}" for index, data in indices_analysis.items() + ]) + + prompt = f""" + As an expert Indian market analyst, provide a comprehensive analysis of current market conditions: + + MARKET OVERVIEW: + {market_overview} + + INDICES DATA: + {indices_data_text} + + MARKET STATUS: {market_status} + ANALYSIS DATE: {analysis_date} + + Provide your analysis covering: + + 1. OVERALL MARKET SENTIMENT: + - Current market trend (bullish/bearish/sideways) + - Market momentum and strength + - Volatility assessment (India VIX context) + - Risk appetite indicators + + 2. TECHNICAL MARKET STRUCTURE: + - Key support and resistance levels for Nifty 50 + - Index correlation analysis + - Volume patterns and participation + - Breadth indicators (advance/decline) + + 3. SECTORAL ANALYSIS: + - Leading and lagging sectors + - Sector rotation patterns + - Relative strength analysis + - Sector-specific opportunities + + 4. INSTITUTIONAL ACTIVITY: + - FII/DII flow patterns + - Institutional buying/selling pressure + - Impact on market direction + - Liquidity conditions + + 5. GLOBAL CONTEXT: + - Impact of global markets on Indian indices + - Currency (USD-INR) influence + - Commodity price effects + - Geopolitical factors + + 6. TRADING ENVIRONMENT: + - Market volatility assessment + - Trading opportunities quality + - Risk management considerations + - Position sizing recommendations + + 7. NEAR-TERM OUTLOOK: + - Expected market direction (1-2 weeks) + - Key events and catalysts + - Potential market scenarios + - Trading strategy recommendations + + Focus on actionable insights for Indian equity traders and investors. + """ + + try: + analysis = self.agent_utils.query_gpt_single(prompt) + return analysis + except Exception as e: + logger.error(f"Error generating market conditions analysis: {e}") + return f"Error generating market analysis: {e}" + + def _generate_technical_analysis(self, + symbol: str, + market_data: str, + current_quote: str, + analysis_date: str, + lookback_days: int) -> str: + """Generate technical analysis for individual stock""" + + prompt = f""" + As an expert Indian equity technical analyst, provide comprehensive technical analysis for {symbol}: + + HISTORICAL DATA ({lookback_days} days): + {market_data} + + CURRENT QUOTE: + {current_quote} + + ANALYSIS DATE: {analysis_date} + + Provide detailed technical analysis covering: + + 1. PRICE ACTION ANALYSIS: + - Current trend direction and strength + - Key price levels and patterns + - Candlestick patterns and signals + - Volume-price relationship + + 2. MOVING AVERAGES: + - 20, 50, 200 SMA analysis + - EMA crossovers and signals + - Price position relative to moving averages + - Moving average support/resistance + + 3. MOMENTUM INDICATORS: + - RSI (14) analysis and divergences + - MACD signal and histogram + - Stochastic oscillator readings + - Rate of change indicators + + 4. VOLATILITY ANALYSIS: + - Bollinger Bands position + - Average True Range (ATR) + - Volatility breakouts or contractions + - Risk assessment based on volatility + + 5. VOLUME ANALYSIS: + - Volume trends and patterns + - Volume confirmation of price moves + - On-balance volume (OBV) + - Volume-based support/resistance + + 6. CHART PATTERNS: + - Identify any chart patterns (triangles, flags, etc.) + - Pattern completion levels + - Breakout/breakdown scenarios + - Target price projections + + 7. SUPPORT AND RESISTANCE: + - Key support levels + - Key resistance levels + - Fibonacci retracement levels + - Pivot points for intraday trading + + 8. TRADING SIGNALS: + - Buy/sell signals based on technical indicators + - Entry and exit points + - Stop-loss recommendations + - Target price levels + + Provide specific price levels in INR and actionable trading recommendations. + """ + + try: + analysis = self.agent_utils.query_gpt_single(prompt) + return analysis + except Exception as e: + logger.error(f"Error generating technical analysis: {e}") + return f"Error generating technical analysis: {e}" + + def _calculate_support_resistance(self, market_data: str) -> Dict[str, List[float]]: + """Calculate support and resistance levels""" + # This is a simplified implementation + # In production, would parse the CSV data and calculate actual levels + + try: + # Extract price information from market data + # For now, return placeholder levels + return { + "support_levels": [2400.0, 2350.0, 2300.0], # Example levels + "resistance_levels": [2500.0, 2550.0, 2600.0], + "pivot_point": 2450.0, + "fibonacci_levels": [2380.0, 2420.0, 2480.0, 2520.0] + } + except Exception as e: + logger.error(f"Error calculating support/resistance: {e}") + return { + "support_levels": [], + "resistance_levels": [], + "error": str(e) + } + + def _generate_trading_signals(self, technical_analysis: str) -> List[Dict[str, Any]]: + """Generate trading signals based on technical analysis""" + + prompt = f""" + Based on the following technical analysis, generate specific trading signals: + + TECHNICAL ANALYSIS: + {technical_analysis} + + Generate up to 3 trading signals in the following format for each signal: + + Signal Type: [BUY/SELL/HOLD] + Entry Price: [Specific price in INR] + Stop Loss: [Specific price in INR] + Target 1: [Specific price in INR] + Target 2: [Specific price in INR] + Risk-Reward Ratio: [X:Y] + Time Horizon: [Intraday/Short-term/Medium-term] + Confidence: [High/Medium/Low] + Rationale: [Brief explanation] + + Focus on high-probability setups with clear risk management. + """ + + try: + signals_text = self.agent_utils.query_gpt_single(prompt) + + # Parse signals (simplified implementation) + signals = [] + signal_blocks = signals_text.split("Signal Type:") + + for block in signal_blocks[1:]: # Skip first empty block + try: + lines = block.strip().split('\n') + signal = { + "signal_type": lines[0].strip(), + "entry_price": self._extract_price(lines[1]) if len(lines) > 1 else None, + "stop_loss": self._extract_price(lines[2]) if len(lines) > 2 else None, + "target_1": self._extract_price(lines[3]) if len(lines) > 3 else None, + "target_2": self._extract_price(lines[4]) if len(lines) > 4 else None, + "time_horizon": lines[6].split(':')[1].strip() if len(lines) > 6 else "Unknown", + "confidence": lines[7].split(':')[1].strip() if len(lines) > 7 else "Medium" + } + signals.append(signal) + except Exception as e: + logger.warning(f"Error parsing signal: {e}") + continue + + return signals[:3] # Return max 3 signals + + except Exception as e: + logger.error(f"Error generating trading signals: {e}") + return [{"error": str(e)}] + + def _extract_price(self, line: str) -> Optional[float]: + """Extract price from a line of text""" + try: + # Simple regex to find numbers + import re + matches = re.findall(r'\d+\.?\d*', line) + return float(matches[0]) if matches else None + except: + return None + + def _calculate_risk_levels(self, symbol: str, market_data: str) -> Dict[str, float]: + """Calculate risk levels for position sizing""" + + # Simplified risk calculation + # In production, would calculate based on actual volatility + + return { + "daily_atr": 50.0, # Average True Range + "volatility_percentile": 65.0, # Current volatility vs historical + "max_position_size": 0.05, # 5% of portfolio + "recommended_stop_loss": 0.08, # 8% stop loss + "risk_score": 6.5 # Out of 10 + } + + def _calculate_market_confidence(self, conditions_analysis: str) -> float: + """Calculate confidence in market analysis""" + + # Simple confidence scoring based on analysis content + confidence_factors = [] + + if "bullish" in conditions_analysis.lower(): + confidence_factors.append(0.3) + elif "bearish" in conditions_analysis.lower(): + confidence_factors.append(0.3) + + if "strong" in conditions_analysis.lower(): + confidence_factors.append(0.2) + + if "volume" in conditions_analysis.lower(): + confidence_factors.append(0.2) + + if "support" in conditions_analysis.lower() or "resistance" in conditions_analysis.lower(): + confidence_factors.append(0.3) + + return min(sum(confidence_factors), 1.0) + + def _calculate_technical_confidence(self, market_data: str) -> float: + """Calculate confidence in technical analysis""" + + # Check data quality + if "Error" in market_data or "No data" in market_data: + return 0.1 + + # Check data completeness (simplified) + if len(market_data) > 1000: # Reasonable amount of data + return 0.8 + elif len(market_data) > 500: + return 0.6 + else: + return 0.4 + + def _determine_trading_bias(self, conditions_analysis: str) -> str: + """Determine overall trading bias""" + + analysis_lower = conditions_analysis.lower() + + bullish_signals = ["bullish", "uptrend", "positive", "strong", "buy"] + bearish_signals = ["bearish", "downtrend", "negative", "weak", "sell"] + + bullish_count = sum(1 for signal in bullish_signals if signal in analysis_lower) + bearish_count = sum(1 for signal in bearish_signals if signal in analysis_lower) + + if bullish_count > bearish_count: + return "BULLISH" + elif bearish_count > bullish_count: + return "BEARISH" + else: + return "NEUTRAL" + + def _identify_key_levels(self, indices_analysis: Dict[str, Any]) -> Dict[str, List[float]]: + """Identify key levels for major indices""" + + # Simplified key levels identification + # In production, would parse actual price data + + return { + "nifty_50": { + "support": [24000, 23800, 23500], + "resistance": [24500, 24800, 25000] + }, + "bank_nifty": { + "support": [51000, 50500, 50000], + "resistance": [52000, 52500, 53000] + } + } + +# Example usage and testing +if __name__ == "__main__": + analyst = IndianMarketAnalyst() + + # Test market conditions analysis + market_result = analyst.analyze_market_conditions() + print("Market Conditions Analysis:") + print(market_result) + + # Test stock technical analysis + stock_result = analyst.analyze_stock_technical("RELIANCE", "NSE") + print("\nStock Technical Analysis:") + print(stock_result) \ No newline at end of file diff --git a/tradingagents/agents/utils/indian_agent_toolkit.py b/tradingagents/agents/utils/indian_agent_toolkit.py new file mode 100644 index 00000000..580fc148 --- /dev/null +++ b/tradingagents/agents/utils/indian_agent_toolkit.py @@ -0,0 +1,423 @@ +""" +Indian Agent Toolkit +Comprehensive toolkit for Indian market trading agents with specialized tools and functions +""" + +from typing import Annotated, Dict, List, Any, Optional +from datetime import datetime, timedelta +import logging + +from tradingagents.dataflows.indian_interface import ( + get_indian_market_data_interface, + get_indian_fundamentals_interface, + get_indian_quote_interface, + get_indian_news_interface, + get_indian_sentiment_interface, + get_indian_sector_analysis, + get_indian_market_overview, + get_indian_technical_indicators +) +from tradingagents.dataflows.ticker_utils import ( + format_indian_ticker, + validate_indian_ticker, + get_plain_symbol, + process_ticker_list +) +from tradingagents.indian_config import ( + get_indian_config, + get_major_stocks, + get_sector_stocks, + is_market_open, + get_market_status +) +from tradingagents.agents.analysts.indian_fundamentals_analyst import IndianFundamentalsAnalyst +from tradingagents.agents.analysts.indian_market_analyst import IndianMarketAnalyst + +logger = logging.getLogger(__name__) + +class IndianAgentToolkit: + """ + Comprehensive toolkit for Indian market trading agents + + Provides specialized tools for: + - Indian market data retrieval + - Fundamental and technical analysis + - News and sentiment analysis + - Risk management and position sizing + - Market timing and execution + """ + + def __init__(self): + self.config = get_indian_config() + self.major_stocks = get_major_stocks() + + # Initialize specialized analysts + self.fundamentals_analyst = IndianFundamentalsAnalyst() + self.market_analyst = IndianMarketAnalyst() + + # Market parameters + self.trading_hours = self.config["trading_hours"] + self.risk_parameters = self.config["risk_parameters"] + self.market_parameters = self.config["market_parameters"] + + # Market Data Tools + def get_indian_stock_data(self, + symbol: Annotated[str, "Indian stock symbol (e.g., RELIANCE, TCS)"], + start_date: Annotated[str, "Start date (YYYY-MM-DD)"], + end_date: Annotated[str, "End date (YYYY-MM-DD)"], + exchange: Annotated[str, "Exchange (NSE/BSE)"] = "NSE") -> str: + """Get historical stock data for Indian equity""" + return get_indian_market_data_interface(symbol, start_date, end_date, exchange) + + def get_indian_stock_quote(self, + symbol: Annotated[str, "Indian stock symbol"], + exchange: Annotated[str, "Exchange (NSE/BSE)"] = "NSE") -> str: + """Get real-time quote for Indian stock""" + return get_indian_quote_interface(symbol, exchange) + + def get_indian_fundamentals(self, + symbol: Annotated[str, "Indian stock symbol"], + exchange: Annotated[str, "Exchange (NSE/BSE)"] = "NSE") -> str: + """Get fundamental data for Indian company""" + return get_indian_fundamentals_interface(symbol, exchange) + + def get_indian_news(self, + symbol: Annotated[str, "Indian stock symbol"], + days_back: Annotated[int, "Days to look back"] = 7) -> str: + """Get Indian company news from local sources""" + curr_date = datetime.now().strftime("%Y-%m-%d") + return get_indian_news_interface(symbol, curr_date, days_back) + + def get_indian_sentiment(self, + symbol: Annotated[str, "Indian stock symbol"], + days_back: Annotated[int, "Days to look back"] = 7) -> str: + """Get Indian social media sentiment""" + curr_date = datetime.now().strftime("%Y-%m-%d") + return get_indian_sentiment_interface(symbol, curr_date, days_back) + + def get_sector_analysis(self, + sector: Annotated[str, "Sector name (banking, it, pharma, etc.)"], + analysis_date: Annotated[str, "Analysis date (YYYY-MM-DD)"] = None) -> str: + """Get Indian sector analysis""" + if analysis_date is None: + analysis_date = datetime.now().strftime("%Y-%m-%d") + return get_indian_sector_analysis(sector, analysis_date) + + def get_market_overview(self, + analysis_date: Annotated[str, "Analysis date (YYYY-MM-DD)"] = None) -> str: + """Get Indian market overview""" + if analysis_date is None: + analysis_date = datetime.now().strftime("%Y-%m-%d") + return get_indian_market_overview(analysis_date) + + # Analysis Tools + def analyze_fundamentals(self, + symbol: Annotated[str, "Indian stock symbol"], + exchange: Annotated[str, "Exchange (NSE/BSE)"] = "NSE") -> Dict[str, Any]: + """Perform comprehensive fundamental analysis""" + return self.fundamentals_analyst.analyze_fundamentals(symbol, exchange) + + def analyze_technical(self, + symbol: Annotated[str, "Indian stock symbol"], + exchange: Annotated[str, "Exchange (NSE/BSE)"] = "NSE", + lookback_days: Annotated[int, "Days of data to analyze"] = 60) -> Dict[str, Any]: + """Perform technical analysis""" + return self.market_analyst.analyze_stock_technical(symbol, exchange, lookback_days=lookback_days) + + def analyze_market_conditions(self) -> Dict[str, Any]: + """Analyze overall market conditions""" + return self.market_analyst.analyze_market_conditions() + + def compare_with_peers(self, + symbol: Annotated[str, "Target stock symbol"], + peer_symbols: Annotated[List[str], "List of peer symbols"], + exchange: Annotated[str, "Exchange (NSE/BSE)"] = "NSE") -> Dict[str, Any]: + """Compare stock with sector peers""" + return self.fundamentals_analyst.compare_with_peers(symbol, peer_symbols, exchange) + + # Risk Management Tools + def calculate_position_size(self, + symbol: Annotated[str, "Stock symbol"], + entry_price: Annotated[float, "Entry price in INR"], + stop_loss: Annotated[float, "Stop loss price in INR"], + portfolio_value: Annotated[float, "Total portfolio value in INR"], + risk_percentage: Annotated[float, "Risk percentage (0.01 = 1%)"] = 0.02) -> Dict[str, Any]: + """Calculate position size based on risk management rules""" + try: + # Calculate risk per share + risk_per_share = abs(entry_price - stop_loss) + + # Calculate maximum risk amount + max_risk_amount = portfolio_value * risk_percentage + + # Calculate position size + position_size = int(max_risk_amount / risk_per_share) + + # Apply Indian market constraints + max_position_value = portfolio_value * self.risk_parameters["max_position_size"] + max_shares_by_value = int(max_position_value / entry_price) + + # Take the minimum of both constraints + final_position_size = min(position_size, max_shares_by_value) + + # Calculate actual risk and position value + actual_risk = final_position_size * risk_per_share + position_value = final_position_size * entry_price + + return { + "symbol": symbol, + "entry_price": entry_price, + "stop_loss": stop_loss, + "recommended_shares": final_position_size, + "position_value": position_value, + "risk_amount": actual_risk, + "risk_percentage": (actual_risk / portfolio_value) * 100, + "position_percentage": (position_value / portfolio_value) * 100, + "risk_reward_ratio": f"1:{abs((entry_price - stop_loss) / risk_per_share):.2f}" + } + + except Exception as e: + logger.error(f"Error calculating position size: {e}") + return {"error": str(e)} + + def assess_stock_risk(self, + symbol: Annotated[str, "Stock symbol"], + exchange: Annotated[str, "Exchange (NSE/BSE)"] = "NSE") -> Dict[str, Any]: + """Assess risk factors for a stock""" + try: + # Get fundamental analysis for risk assessment + fund_analysis = self.fundamentals_analyst.analyze_fundamentals(symbol, exchange) + + # Get technical analysis for volatility assessment + tech_analysis = self.market_analyst.analyze_stock_technical(symbol, exchange) + + # Calculate risk score + risk_score = self._calculate_risk_score(fund_analysis, tech_analysis) + + return { + "symbol": symbol, + "exchange": exchange, + "overall_risk_score": risk_score, + "risk_factors": fund_analysis.get("risk_factors", []), + "technical_risk": tech_analysis.get("risk_levels", {}), + "recommendation": self._get_risk_recommendation(risk_score) + } + + except Exception as e: + logger.error(f"Error assessing stock risk: {e}") + return {"error": str(e)} + + # Market Timing Tools + def check_market_status(self) -> Dict[str, Any]: + """Check current Indian market status""" + return { + "market_status": get_market_status(), + "is_market_open": is_market_open(), + "trading_hours": self.trading_hours, + "current_time": datetime.now().strftime("%Y-%m-%d %H:%M:%S IST") + } + + def get_optimal_entry_time(self, + symbol: Annotated[str, "Stock symbol"], + strategy: Annotated[str, "Trading strategy (intraday/swing/long_term)"] = "intraday") -> Dict[str, Any]: + """Suggest optimal entry timing based on Indian market patterns""" + try: + market_status = get_market_status() + current_time = datetime.now() + + recommendations = [] + + if strategy == "intraday": + if market_status == "pre_open": + recommendations.append("Wait for market opening and first 15 minutes of price discovery") + elif market_status == "open": + hour = current_time.hour + if 9 <= hour <= 10: + recommendations.append("Good time for momentum trades - high volatility period") + elif 10 <= hour <= 14: + recommendations.append("Suitable for trend following strategies") + elif 14 <= hour <= 15: + recommendations.append("Closing hour - be cautious of volatility") + else: + recommendations.append("Market closed - prepare for next session") + + elif strategy == "swing": + recommendations.append("Consider entering on weekly support levels") + recommendations.append("Monitor for breakout patterns on daily charts") + + elif strategy == "long_term": + recommendations.append("Focus on fundamental value rather than timing") + recommendations.append("Consider systematic investment approach") + + return { + "symbol": symbol, + "strategy": strategy, + "market_status": market_status, + "recommendations": recommendations, + "current_time": current_time.strftime("%Y-%m-%d %H:%M:%S IST") + } + + except Exception as e: + logger.error(f"Error getting optimal entry time: {e}") + return {"error": str(e)} + + # Utility Tools + def format_ticker(self, + symbol: Annotated[str, "Stock symbol"], + exchange: Annotated[str, "Exchange (NSE/BSE)"] = "NSE") -> str: + """Format ticker for Indian exchanges""" + return format_indian_ticker(symbol, exchange) + + def validate_ticker(self, + ticker: Annotated[str, "Ticker to validate"]) -> bool: + """Validate Indian ticker format""" + return validate_indian_ticker(ticker) + + def get_sector_stocks(self, + sector: Annotated[str, "Sector name"]) -> List[str]: + """Get list of stocks in a sector""" + return get_sector_stocks(sector) + + def get_major_stocks_list(self) -> Dict[str, Dict[str, str]]: + """Get list of major Indian stocks""" + return self.major_stocks + + def process_multiple_tickers(self, + symbols: Annotated[List[str], "List of stock symbols"], + exchange: Annotated[str, "Exchange (NSE/BSE)"] = "NSE") -> List[Dict[str, Any]]: + """Process multiple ticker symbols""" + return process_ticker_list(symbols, exchange) + + # Portfolio Tools + def calculate_portfolio_metrics(self, + holdings: Annotated[List[Dict[str, Any]], "List of holdings with symbol, quantity, avg_price"], + current_date: Annotated[str, "Current date (YYYY-MM-DD)"] = None) -> Dict[str, Any]: + """Calculate portfolio metrics for Indian stocks""" + if current_date is None: + current_date = datetime.now().strftime("%Y-%m-%d") + + try: + total_investment = 0 + total_current_value = 0 + portfolio_details = [] + + for holding in holdings: + symbol = holding["symbol"] + quantity = holding["quantity"] + avg_price = holding["avg_price"] + + # Get current price + quote = get_indian_quote_interface(symbol) + # In production, would parse actual price from quote + current_price = avg_price * 1.05 # Placeholder - 5% gain + + investment = quantity * avg_price + current_value = quantity * current_price + pnl = current_value - investment + pnl_percentage = (pnl / investment) * 100 + + total_investment += investment + total_current_value += current_value + + portfolio_details.append({ + "symbol": symbol, + "quantity": quantity, + "avg_price": avg_price, + "current_price": current_price, + "investment": investment, + "current_value": current_value, + "pnl": pnl, + "pnl_percentage": pnl_percentage + }) + + total_pnl = total_current_value - total_investment + total_pnl_percentage = (total_pnl / total_investment) * 100 + + return { + "total_investment": total_investment, + "total_current_value": total_current_value, + "total_pnl": total_pnl, + "total_pnl_percentage": total_pnl_percentage, + "holdings": portfolio_details, + "analysis_date": current_date + } + + except Exception as e: + logger.error(f"Error calculating portfolio metrics: {e}") + return {"error": str(e)} + + # Helper Methods + def _calculate_risk_score(self, fund_analysis: Dict[str, Any], tech_analysis: Dict[str, Any]) -> float: + """Calculate overall risk score (0-10, higher is riskier)""" + risk_factors = [] + + # Fundamental risk factors + if fund_analysis.get("confidence", 0) < 0.5: + risk_factors.append(2.0) # Low confidence adds risk + + # Technical risk factors + tech_confidence = tech_analysis.get("confidence", 0) + if tech_confidence < 0.5: + risk_factors.append(1.5) + + # Market risk (simplified) + risk_factors.append(5.0) # Base market risk + + return min(sum(risk_factors), 10.0) + + def _get_risk_recommendation(self, risk_score: float) -> str: + """Get risk recommendation based on score""" + if risk_score <= 3: + return "LOW RISK - Suitable for conservative investors" + elif risk_score <= 6: + return "MEDIUM RISK - Suitable for moderate risk tolerance" + elif risk_score <= 8: + return "HIGH RISK - Suitable for aggressive investors only" + else: + return "VERY HIGH RISK - Speculative investment, exercise extreme caution" + +# Create global toolkit instance +indian_toolkit = IndianAgentToolkit() + +# Export functions for agent use +def get_indian_stock_data(symbol: str, start_date: str, end_date: str, exchange: str = "NSE") -> str: + """Get Indian stock data""" + return indian_toolkit.get_indian_stock_data(symbol, start_date, end_date, exchange) + +def get_indian_stock_quote(symbol: str, exchange: str = "NSE") -> str: + """Get Indian stock quote""" + return indian_toolkit.get_indian_stock_quote(symbol, exchange) + +def get_indian_fundamentals(symbol: str, exchange: str = "NSE") -> str: + """Get Indian fundamentals""" + return indian_toolkit.get_indian_fundamentals(symbol, exchange) + +def analyze_indian_stock(symbol: str, exchange: str = "NSE") -> Dict[str, Any]: + """Comprehensive Indian stock analysis""" + return { + "fundamental_analysis": indian_toolkit.analyze_fundamentals(symbol, exchange), + "technical_analysis": indian_toolkit.analyze_technical(symbol, exchange), + "risk_assessment": indian_toolkit.assess_stock_risk(symbol, exchange) + } + +def calculate_position_size(symbol: str, entry_price: float, stop_loss: float, + portfolio_value: float, risk_percentage: float = 0.02) -> Dict[str, Any]: + """Calculate position size for Indian stock""" + return indian_toolkit.calculate_position_size(symbol, entry_price, stop_loss, + portfolio_value, risk_percentage) + +def get_market_status() -> Dict[str, Any]: + """Get Indian market status""" + return indian_toolkit.check_market_status() + +# Export toolkit class +__all__ = [ + 'IndianAgentToolkit', + 'indian_toolkit', + 'get_indian_stock_data', + 'get_indian_stock_quote', + 'get_indian_fundamentals', + 'analyze_indian_stock', + 'calculate_position_size', + 'get_market_status' +] \ No newline at end of file diff --git a/tradingagents/dataflows/indian_interface.py b/tradingagents/dataflows/indian_interface.py new file mode 100644 index 00000000..1bddd1a9 --- /dev/null +++ b/tradingagents/dataflows/indian_interface.py @@ -0,0 +1,453 @@ +""" +Indian Market Data Interface +Integration layer for Indian market data with the TradingAgents framework +""" + +from typing import Annotated, Dict, Optional, List, Any +from datetime import datetime, timedelta +import pandas as pd +import logging +import os +import json + +from .indian_market_utils import ( + IndianMarketDataManager, + get_indian_market_data, + get_indian_fundamentals, + get_indian_quote +) +from .ticker_utils import ( + format_indian_ticker, + validate_indian_ticker, + get_plain_symbol, + TickerManager +) +from .config import get_config + +logger = logging.getLogger(__name__) + +# Initialize ticker manager +ticker_manager = TickerManager() + +def get_indian_market_data_interface( + symbol: Annotated[str, "Indian stock symbol (e.g., RELIANCE, TCS)"], + start_date: Annotated[str, "Start date in yyyy-mm-dd format"], + end_date: Annotated[str, "End date in yyyy-mm-dd format"], + exchange: Annotated[str, "Exchange: NSE or BSE"] = "NSE" +) -> str: + """ + Get Indian stock market data with multiple source fallbacks + + Args: + symbol: Stock symbol (e.g., 'RELIANCE', 'TCS') + start_date: Start date in yyyy-mm-dd format + end_date: End date in yyyy-mm-dd format + exchange: Exchange (NSE or BSE) + + Returns: + Formatted string with market data + """ + try: + # Validate dates + datetime.strptime(start_date, "%Y-%m-%d") + datetime.strptime(end_date, "%Y-%m-%d") + + # Process ticker + ticker_info = ticker_manager.process_ticker(symbol, exchange) + if not ticker_info["is_valid"]: + return f"Invalid ticker: {symbol}. Error: {ticker_info.get('error', 'Unknown error')}" + + # Get configuration + config = get_config() + + # Get market data + result = get_indian_market_data( + symbol=symbol, + start_date=start_date, + end_date=end_date, + exchange=exchange, + config=config + ) + + return result + + except ValueError as e: + error_msg = f"Date format error: {e}" + logger.error(error_msg) + return error_msg + except Exception as e: + error_msg = f"Error fetching market data for {symbol}: {e}" + logger.error(error_msg) + return error_msg + +def get_indian_fundamentals_interface( + symbol: Annotated[str, "Indian stock symbol (e.g., RELIANCE, TCS)"], + exchange: Annotated[str, "Exchange: NSE or BSE"] = "NSE" +) -> str: + """ + Get Indian company fundamental data + + Args: + symbol: Stock symbol (e.g., 'RELIANCE', 'TCS') + exchange: Exchange (NSE or BSE) + + Returns: + Formatted string with fundamental data + """ + try: + # Process ticker + ticker_info = ticker_manager.process_ticker(symbol, exchange) + if not ticker_info["is_valid"]: + return f"Invalid ticker: {symbol}. Error: {ticker_info.get('error', 'Unknown error')}" + + # Get configuration + config = get_config() + + # Get fundamental data + result = get_indian_fundamentals( + symbol=symbol, + exchange=exchange, + config=config + ) + + return result + + except Exception as e: + error_msg = f"Error fetching fundamentals for {symbol}: {e}" + logger.error(error_msg) + return error_msg + +def get_indian_quote_interface( + symbol: Annotated[str, "Indian stock symbol (e.g., RELIANCE, TCS)"], + exchange: Annotated[str, "Exchange: NSE or BSE"] = "NSE" +) -> str: + """ + Get Indian real-time quote data + + Args: + symbol: Stock symbol (e.g., 'RELIANCE', 'TCS') + exchange: Exchange (NSE or BSE) + + Returns: + Formatted string with quote data + """ + try: + # Process ticker + ticker_info = ticker_manager.process_ticker(symbol, exchange) + if not ticker_info["is_valid"]: + return f"Invalid ticker: {symbol}. Error: {ticker_info.get('error', 'Unknown error')}" + + # Get configuration + config = get_config() + + # Get quote data + result = get_indian_quote( + symbol=symbol, + exchange=exchange, + config=config + ) + + return result + + except Exception as e: + error_msg = f"Error fetching quote for {symbol}: {e}" + logger.error(error_msg) + return error_msg + +def get_indian_technical_indicators( + symbol: Annotated[str, "Indian stock symbol (e.g., RELIANCE, TCS)"], + indicator: Annotated[str, "Technical indicator (e.g., rsi, macd, sma)"], + curr_date: Annotated[str, "Current date in yyyy-mm-dd format"], + lookback_days: Annotated[int, "Number of days to look back"] = 30, + exchange: Annotated[str, "Exchange: NSE or BSE"] = "NSE" +) -> str: + """ + Get technical indicators for Indian stocks + + Args: + symbol: Stock symbol + indicator: Technical indicator name + curr_date: Current date + lookback_days: Number of days to look back + exchange: Exchange + + Returns: + Formatted string with indicator data + """ + try: + # Import stockstats here to avoid circular imports + from .stockstats_utils import StockstatsUtils + + # Process ticker + ticker_info = ticker_manager.process_ticker(symbol, exchange) + if not ticker_info["is_valid"]: + return f"Invalid ticker: {symbol}. Error: {ticker_info.get('error', 'Unknown error')}" + + # Calculate date range + end_date = datetime.strptime(curr_date, "%Y-%m-%d") + start_date = end_date - timedelta(days=lookback_days + 50) # Extra days for indicator calculation + + # Get market data first + market_data = get_indian_market_data_interface( + symbol=symbol, + start_date=start_date.strftime("%Y-%m-%d"), + end_date=curr_date, + exchange=exchange + ) + + if "Error" in market_data or "No data" in market_data: + return f"Cannot calculate {indicator}: {market_data}" + + # For now, return a placeholder - full integration with stockstats would need more work + result = f"# Technical Indicator: {indicator} for {ticker_info['formatted_ticker']}\n" + result += f"# Date: {curr_date}\n" + result += f"# Lookback period: {lookback_days} days\n\n" + result += f"Technical indicator calculation for {indicator} would be performed here.\n" + result += "This requires integration with the stockstats library using Indian market data.\n" + + return result + + except Exception as e: + error_msg = f"Error calculating {indicator} for {symbol}: {e}" + logger.error(error_msg) + return error_msg + +def get_indian_news_interface( + symbol: Annotated[str, "Indian stock symbol (e.g., RELIANCE, TCS)"], + curr_date: Annotated[str, "Current date in yyyy-mm-dd format"], + lookback_days: Annotated[int, "Number of days to look back"] = 7 +) -> str: + """ + Get Indian company news from local sources + + Args: + symbol: Stock symbol + curr_date: Current date + lookback_days: Number of days to look back + + Returns: + Formatted string with news data + """ + try: + # Process ticker + ticker_info = ticker_manager.process_ticker(symbol) + if not ticker_info["is_valid"]: + return f"Invalid ticker: {symbol}. Error: {ticker_info.get('error', 'Unknown error')}" + + # Get company name for news search + plain_symbol = ticker_info["plain_symbol"] + + # For now, return a placeholder for Indian news sources + result = f"# Indian News for {plain_symbol}\n" + result += f"# Date range: {(datetime.strptime(curr_date, '%Y-%m-%d') - timedelta(days=lookback_days)).strftime('%Y-%m-%d')} to {curr_date}\n\n" + + # Placeholder news items (in production, these would come from actual Indian news APIs) + result += "## Recent News Headlines:\n\n" + result += f"### Company-specific news for {plain_symbol} would be fetched from:\n" + result += "- Economic Times\n" + result += "- Moneycontrol\n" + result += "- Business Standard\n" + result += "- NSE/BSE announcements\n" + result += "- Company investor relations\n\n" + + result += "### Market and sector news would include:\n" + result += "- RBI policy announcements\n" + result += "- SEBI regulatory updates\n" + result += "- Sectoral developments\n" + result += "- FII/DII flow data\n" + result += "- Currency and commodity updates\n" + + return result + + except Exception as e: + error_msg = f"Error fetching news for {symbol}: {e}" + logger.error(error_msg) + return error_msg + +def get_indian_sentiment_interface( + symbol: Annotated[str, "Indian stock symbol (e.g., RELIANCE, TCS)"], + curr_date: Annotated[str, "Current date in yyyy-mm-dd format"], + lookback_days: Annotated[int, "Number of days to look back"] = 7 +) -> str: + """ + Get Indian social media sentiment data + + Args: + symbol: Stock symbol + curr_date: Current date + lookback_days: Number of days to look back + + Returns: + Formatted string with sentiment data + """ + try: + # Process ticker + ticker_info = ticker_manager.process_ticker(symbol) + if not ticker_info["is_valid"]: + return f"Invalid ticker: {symbol}. Error: {ticker_info.get('error', 'Unknown error')}" + + plain_symbol = ticker_info["plain_symbol"] + + # For now, return a placeholder for Indian sentiment sources + result = f"# Indian Social Media Sentiment for {plain_symbol}\n" + result += f"# Date range: {(datetime.strptime(curr_date, '%Y-%m-%d') - timedelta(days=lookback_days)).strftime('%Y-%m-%d')} to {curr_date}\n\n" + + result += "## Sentiment Analysis Sources:\n\n" + result += f"### Social Media Sentiment for {plain_symbol}:\n" + result += "- Twitter India discussions\n" + result += "- Indian stock forums (ValuePickr, etc.)\n" + result += "- Reddit India finance communities\n" + result += "- Telegram trading groups\n\n" + + result += "### Institutional Sentiment Indicators:\n" + result += "- FII/DII buying/selling patterns\n" + result += "- Mutual fund holdings changes\n" + result += "- Analyst recommendations from Indian brokerages\n" + result += "- Retail investor participation metrics\n" + + return result + + except Exception as e: + error_msg = f"Error fetching sentiment for {symbol}: {e}" + logger.error(error_msg) + return error_msg + +def get_indian_sector_analysis( + sector: Annotated[str, "Sector name (e.g., banking, it, pharma)"], + curr_date: Annotated[str, "Current date in yyyy-mm-dd format"] +) -> str: + """ + Get Indian sector analysis and performance + + Args: + sector: Sector name + curr_date: Current date + + Returns: + Formatted string with sector analysis + """ + try: + from tradingagents.indian_config import get_sector_stocks, INDIAN_SECTORS + + # Get stocks in the sector + sector_stocks = get_sector_stocks(sector.lower()) + + if not sector_stocks: + available_sectors = list(INDIAN_SECTORS.keys()) + return f"Sector '{sector}' not found. Available sectors: {', '.join(available_sectors)}" + + result = f"# Indian {sector.title()} Sector Analysis\n" + result += f"# Date: {curr_date}\n\n" + + result += f"## Key Stocks in {sector.title()} Sector:\n" + for stock in sector_stocks[:10]: # Limit to top 10 + result += f"- {stock}\n" + + result += f"\n## Sector Performance Metrics:\n" + result += "- Sectoral index performance vs Nifty 50\n" + result += "- Average P/E ratio for the sector\n" + result += "- FII/DII flows into sector stocks\n" + result += "- Government policy impact on sector\n" + result += "- Regulatory changes affecting the sector\n" + + result += f"\n## Recent Sector Developments:\n" + result += f"Sector-specific news and developments for {sector} would be analyzed here.\n" + + return result + + except Exception as e: + error_msg = f"Error analyzing sector {sector}: {e}" + logger.error(error_msg) + return error_msg + +def get_indian_market_overview( + curr_date: Annotated[str, "Current date in yyyy-mm-dd format"] +) -> str: + """ + Get Indian market overview and indices performance + + Args: + curr_date: Current date + + Returns: + Formatted string with market overview + """ + try: + result = f"# Indian Market Overview\n" + result += f"# Date: {curr_date}\n\n" + + result += "## Key Indices Performance:\n" + result += "- Nifty 50 (^NSEI)\n" + result += "- BSE Sensex (^BSESN)\n" + result += "- Nifty Bank (^CNXBANK)\n" + result += "- Nifty IT (^CNXIT)\n" + result += "- Nifty Auto (^CNXAUTO)\n" + result += "- Nifty Pharma (^CNXPHARMA)\n" + + result += "\n## Market Breadth:\n" + result += "- Advances vs Declines\n" + result += "- New highs vs New lows\n" + result += "- Volume analysis\n" + result += "- Market volatility (India VIX)\n" + + result += "\n## Key Market Drivers:\n" + result += "- RBI monetary policy stance\n" + result += "- Government fiscal policy\n" + result += "- Global market sentiment\n" + result += "- FII/DII flows\n" + result += "- Currency (USD-INR) movement\n" + result += "- Commodity prices impact\n" + + result += "\n## Regulatory Updates:\n" + result += "- SEBI policy changes\n" + result += "- Tax policy impacts\n" + result += "- Corporate governance updates\n" + + return result + + except Exception as e: + error_msg = f"Error generating market overview: {e}" + logger.error(error_msg) + return error_msg + +# Integration functions for existing framework compatibility +def get_YFin_data_indian( + symbol: Annotated[str, "Indian stock symbol"], + start_date: Annotated[str, "Start date in yyyy-mm-dd format"], + end_date: Annotated[str, "End date in yyyy-mm-dd format"] +) -> str: + """ + Compatibility wrapper for existing YFin interface + Automatically detects Indian tickers and routes to Indian data sources + """ + # Check if it's an Indian ticker + if validate_indian_ticker(symbol) or any(symbol.upper() in stocks for stocks in [ + list(range(20)) # Placeholder - would check against known Indian stocks + ]): + return get_indian_market_data_interface(symbol, start_date, end_date) + else: + # Fall back to original YFin implementation + from .interface import get_YFin_data + return get_YFin_data(symbol, start_date, end_date) + +def get_fundamentals_indian( + symbol: Annotated[str, "Indian stock symbol"] +) -> str: + """ + Compatibility wrapper for fundamentals data + """ + return get_indian_fundamentals_interface(symbol) + +# Export functions for use in agents +__all__ = [ + 'get_indian_market_data_interface', + 'get_indian_fundamentals_interface', + 'get_indian_quote_interface', + 'get_indian_technical_indicators', + 'get_indian_news_interface', + 'get_indian_sentiment_interface', + 'get_indian_sector_analysis', + 'get_indian_market_overview', + 'get_YFin_data_indian', + 'get_fundamentals_indian' +] \ No newline at end of file diff --git a/tradingagents/dataflows/indian_market_utils.py b/tradingagents/dataflows/indian_market_utils.py new file mode 100644 index 00000000..893bf751 --- /dev/null +++ b/tradingagents/dataflows/indian_market_utils.py @@ -0,0 +1,503 @@ +""" +Indian Market Data Utilities +Handles data fetching from multiple Indian market sources with fallbacks +""" + +import os +import requests +import pandas as pd +import yfinance as yf +from typing import Dict, List, Optional, Union, Any +from datetime import datetime, timedelta +import time +import logging +from functools import wraps +import json + +from .ticker_utils import TickerFormatter, TickerValidator, format_indian_ticker + +logger = logging.getLogger(__name__) + +class RateLimiter: + """Simple rate limiter for API calls""" + + def __init__(self, calls_per_minute: int = 60): + self.calls_per_minute = calls_per_minute + self.calls = [] + + def wait_if_needed(self): + """Wait if rate limit would be exceeded""" + now = time.time() + # Remove calls older than 1 minute + self.calls = [call_time for call_time in self.calls if now - call_time < 60] + + if len(self.calls) >= self.calls_per_minute: + sleep_time = 60 - (now - self.calls[0]) + if sleep_time > 0: + logger.info(f"Rate limit reached, sleeping for {sleep_time:.2f} seconds") + time.sleep(sleep_time) + + self.calls.append(now) + +def retry_on_failure(max_retries: int = 3, delay: float = 1.0): + """Decorator to retry function calls on failure""" + def decorator(func): + @wraps(func) + def wrapper(*args, **kwargs): + last_exception = None + for attempt in range(max_retries): + try: + return func(*args, **kwargs) + except Exception as e: + last_exception = e + if attempt < max_retries - 1: + logger.warning(f"Attempt {attempt + 1} failed for {func.__name__}: {e}") + time.sleep(delay * (2 ** attempt)) # Exponential backoff + else: + logger.error(f"All {max_retries} attempts failed for {func.__name__}") + raise last_exception + return wrapper + return decorator + +class AlphaVantageAPI: + """Alpha Vantage API client for Indian market data""" + + def __init__(self, api_key: str): + self.api_key = api_key + self.base_url = "https://www.alphavantage.co/query" + self.rate_limiter = RateLimiter(calls_per_minute=5) # Alpha Vantage free tier limit + + @retry_on_failure(max_retries=3) + def get_daily_data(self, symbol: str, outputsize: str = "compact") -> pd.DataFrame: + """ + Get daily stock data from Alpha Vantage + + Args: + symbol: Stock symbol (e.g., 'RELIANCE.NS') + outputsize: 'compact' (100 days) or 'full' (all available) + + Returns: + DataFrame with OHLCV data + """ + self.rate_limiter.wait_if_needed() + + params = { + 'function': 'TIME_SERIES_DAILY', + 'symbol': symbol, + 'outputsize': outputsize, + 'apikey': self.api_key + } + + response = requests.get(self.base_url, params=params) + response.raise_for_status() + + data = response.json() + + if 'Error Message' in data: + raise ValueError(f"Alpha Vantage error: {data['Error Message']}") + + if 'Note' in data: + raise ValueError(f"Alpha Vantage rate limit: {data['Note']}") + + time_series = data.get('Time Series (Daily)', {}) + if not time_series: + raise ValueError(f"No data found for symbol {symbol}") + + # Convert to DataFrame + df = pd.DataFrame.from_dict(time_series, orient='index') + df.columns = ['Open', 'High', 'Low', 'Close', 'Volume'] + df.index = pd.to_datetime(df.index) + df = df.astype(float) + df.sort_index(inplace=True) + + return df + + @retry_on_failure(max_retries=3) + def get_company_overview(self, symbol: str) -> Dict[str, Any]: + """Get company fundamental data""" + self.rate_limiter.wait_if_needed() + + params = { + 'function': 'OVERVIEW', + 'symbol': symbol, + 'apikey': self.api_key + } + + response = requests.get(self.base_url, params=params) + response.raise_for_status() + + data = response.json() + + if 'Error Message' in data: + raise ValueError(f"Alpha Vantage error: {data['Error Message']}") + + return data + +class YahooFinanceAPI: + """Yahoo Finance API client for Indian market data""" + + def __init__(self): + self.rate_limiter = RateLimiter(calls_per_minute=30) + + @retry_on_failure(max_retries=3) + def get_daily_data(self, symbol: str, period: str = "1y") -> pd.DataFrame: + """ + Get daily stock data from Yahoo Finance + + Args: + symbol: Stock symbol (e.g., 'RELIANCE.NS') + period: Time period ('1d', '5d', '1mo', '3mo', '6mo', '1y', '2y', '5y', '10y', 'ytd', 'max') + + Returns: + DataFrame with OHLCV data + """ + self.rate_limiter.wait_if_needed() + + ticker = yf.Ticker(symbol) + data = ticker.history(period=period) + + if data.empty: + raise ValueError(f"No data found for symbol {symbol}") + + # Remove timezone info for consistency + if data.index.tz is not None: + data.index = data.index.tz_localize(None) + + return data + + @retry_on_failure(max_retries=3) + def get_company_info(self, symbol: str) -> Dict[str, Any]: + """Get company information""" + self.rate_limiter.wait_if_needed() + + ticker = yf.Ticker(symbol) + info = ticker.info + + if not info or 'symbol' not in info: + raise ValueError(f"No company info found for symbol {symbol}") + + return info + + @retry_on_failure(max_retries=3) + def get_financial_statements(self, symbol: str) -> Dict[str, pd.DataFrame]: + """Get financial statements""" + self.rate_limiter.wait_if_needed() + + ticker = yf.Ticker(symbol) + + return { + 'income_statement': ticker.financials, + 'balance_sheet': ticker.balance_sheet, + 'cash_flow': ticker.cashflow + } + +class NSEDirectAPI: + """Direct NSE API client (unofficial)""" + + def __init__(self): + self.base_url = "https://www.nseindia.com/api" + self.session = requests.Session() + self.session.headers.update({ + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', + 'Accept': 'application/json', + 'Accept-Language': 'en-US,en;q=0.9', + }) + self.rate_limiter = RateLimiter(calls_per_minute=20) + + def _get_session_cookies(self): + """Get session cookies from NSE""" + try: + response = self.session.get("https://www.nseindia.com") + response.raise_for_status() + except Exception as e: + logger.warning(f"Failed to get NSE session cookies: {e}") + + @retry_on_failure(max_retries=2) + def get_quote(self, symbol: str) -> Dict[str, Any]: + """Get real-time quote from NSE""" + self.rate_limiter.wait_if_needed() + self._get_session_cookies() + + # Remove .NS suffix if present + clean_symbol = symbol.replace('.NS', '') + + url = f"{self.base_url}/quote-equity" + params = {'symbol': clean_symbol} + + response = self.session.get(url, params=params) + response.raise_for_status() + + return response.json() + +class IndianMarketDataManager: + """Main manager for Indian market data with multiple sources and fallbacks""" + + def __init__(self, config: Dict[str, Any]): + self.config = config + + # Initialize API clients + self.alpha_vantage = None + self.yahoo_finance = YahooFinanceAPI() + self.nse_direct = NSEDirectAPI() + + # Initialize Alpha Vantage if API key is available + av_key = config.get('api_keys', {}).get('alpha_vantage') + if av_key: + self.alpha_vantage = AlphaVantageAPI(av_key) + else: + logger.warning("Alpha Vantage API key not found, will use fallback sources") + + def get_market_data(self, + symbol: str, + start_date: str, + end_date: str, + exchange: str = "NSE") -> str: + """ + Get market data with multiple source fallbacks + + Args: + symbol: Stock symbol + start_date: Start date (YYYY-MM-DD) + end_date: End date (YYYY-MM-DD) + exchange: Exchange (NSE or BSE) + + Returns: + Formatted string with market data + """ + # Format ticker + formatted_ticker = format_indian_ticker(symbol, exchange) + + # Try multiple sources in order of preference + sources = [ + ("Alpha Vantage", self._get_alpha_vantage_data), + ("Yahoo Finance", self._get_yahoo_finance_data), + ] + + for source_name, source_func in sources: + try: + logger.info(f"Trying {source_name} for {formatted_ticker}") + df = source_func(formatted_ticker, start_date, end_date) + + if df is not None and not df.empty: + # Filter by date range + start_dt = pd.to_datetime(start_date) + end_dt = pd.to_datetime(end_date) + df = df[(df.index >= start_dt) & (df.index <= end_dt)] + + if not df.empty: + return self._format_market_data(df, formatted_ticker, source_name) + + except Exception as e: + logger.warning(f"{source_name} failed for {formatted_ticker}: {e}") + continue + + return f"No market data found for {formatted_ticker} between {start_date} and {end_date}" + + def _get_alpha_vantage_data(self, symbol: str, start_date: str, end_date: str) -> Optional[pd.DataFrame]: + """Get data from Alpha Vantage""" + if not self.alpha_vantage: + return None + + return self.alpha_vantage.get_daily_data(symbol, outputsize="full") + + def _get_yahoo_finance_data(self, symbol: str, start_date: str, end_date: str) -> Optional[pd.DataFrame]: + """Get data from Yahoo Finance""" + # Calculate period for Yahoo Finance + start_dt = pd.to_datetime(start_date) + end_dt = pd.to_datetime(end_date) + days_diff = (end_dt - start_dt).days + + if days_diff <= 5: + period = "5d" + elif days_diff <= 30: + period = "1mo" + elif days_diff <= 90: + period = "3mo" + elif days_diff <= 365: + period = "1y" + else: + period = "max" + + return self.yahoo_finance.get_daily_data(symbol, period) + + def _format_market_data(self, df: pd.DataFrame, symbol: str, source: str) -> str: + """Format market data for display""" + # Round numerical values + numeric_columns = ['Open', 'High', 'Low', 'Close', 'Volume'] + for col in numeric_columns: + if col in df.columns: + if col == 'Volume': + df[col] = df[col].astype(int) + else: + df[col] = df[col].round(2) + + # Convert to CSV string + csv_string = df.to_csv() + + # Add header information + header = f"# Indian Stock Data for {symbol}\n" + header += f"# Total records: {len(df)}\n" + header += f"# Data source: {source}\n" + header += f"# Retrieved on: {datetime.now().strftime('%Y-%m-%d %H:%M:%S IST')}\n\n" + + return header + csv_string + + def get_company_fundamentals(self, symbol: str, exchange: str = "NSE") -> str: + """ + Get company fundamental data + + Args: + symbol: Stock symbol + exchange: Exchange (NSE or BSE) + + Returns: + Formatted string with fundamental data + """ + formatted_ticker = format_indian_ticker(symbol, exchange) + + # Try multiple sources + sources = [ + ("Alpha Vantage", self._get_alpha_vantage_fundamentals), + ("Yahoo Finance", self._get_yahoo_finance_fundamentals), + ] + + for source_name, source_func in sources: + try: + logger.info(f"Trying {source_name} fundamentals for {formatted_ticker}") + data = source_func(formatted_ticker) + + if data: + return self._format_fundamentals_data(data, formatted_ticker, source_name) + + except Exception as e: + logger.warning(f"{source_name} fundamentals failed for {formatted_ticker}: {e}") + continue + + return f"No fundamental data found for {formatted_ticker}" + + def _get_alpha_vantage_fundamentals(self, symbol: str) -> Optional[Dict[str, Any]]: + """Get fundamentals from Alpha Vantage""" + if not self.alpha_vantage: + return None + + return self.alpha_vantage.get_company_overview(symbol) + + def _get_yahoo_finance_fundamentals(self, symbol: str) -> Optional[Dict[str, Any]]: + """Get fundamentals from Yahoo Finance""" + info = self.yahoo_finance.get_company_info(symbol) + + # Also get financial statements + try: + statements = self.yahoo_finance.get_financial_statements(symbol) + info['financial_statements'] = statements + except Exception as e: + logger.warning(f"Could not get financial statements: {e}") + + return info + + def _format_fundamentals_data(self, data: Dict[str, Any], symbol: str, source: str) -> str: + """Format fundamental data for display""" + result = f"# Fundamental Data for {symbol}\n" + result += f"# Data source: {source}\n" + result += f"# Retrieved on: {datetime.now().strftime('%Y-%m-%d %H:%M:%S IST')}\n\n" + + # Key metrics to highlight + key_metrics = [ + 'marketCap', 'MarketCapitalization', 'trailingPE', 'PERatio', + 'priceToBook', 'BookValue', 'dividendYield', 'DividendYield', + 'eps', 'EPS', 'revenue', 'RevenueTTM', 'sector', 'Sector', + 'industry', 'Industry', 'country', 'Country' + ] + + result += "## Key Metrics:\n" + for metric in key_metrics: + if metric in data and data[metric] not in [None, 'None', '']: + result += f"- {metric}: {data[metric]}\n" + + # Add financial statements if available + if 'financial_statements' in data: + statements = data['financial_statements'] + for stmt_name, stmt_df in statements.items(): + if not stmt_df.empty: + result += f"\n## {stmt_name.replace('_', ' ').title()}:\n" + result += stmt_df.head().to_string() + "\n" + + return result + + def get_real_time_quote(self, symbol: str, exchange: str = "NSE") -> str: + """ + Get real-time quote (NSE only) + + Args: + symbol: Stock symbol + exchange: Exchange (only NSE supported for real-time) + + Returns: + Formatted string with quote data + """ + if exchange.upper() != "NSE": + return "Real-time quotes only available for NSE stocks" + + try: + quote_data = self.nse_direct.get_quote(symbol) + return self._format_quote_data(quote_data, symbol) + except Exception as e: + logger.error(f"Failed to get real-time quote for {symbol}: {e}") + return f"Failed to get real-time quote for {symbol}: {e}" + + def _format_quote_data(self, data: Dict[str, Any], symbol: str) -> str: + """Format quote data for display""" + if 'priceInfo' not in data: + return f"No quote data available for {symbol}" + + price_info = data['priceInfo'] + + result = f"# Real-time Quote for {symbol}\n" + result += f"# Retrieved on: {datetime.now().strftime('%Y-%m-%d %H:%M:%S IST')}\n\n" + + result += "## Price Information:\n" + result += f"- Last Price: ₹{price_info.get('lastPrice', 'N/A')}\n" + result += f"- Change: ₹{price_info.get('change', 'N/A')}\n" + result += f"- % Change: {price_info.get('pChange', 'N/A')}%\n" + result += f"- Open: ₹{price_info.get('open', 'N/A')}\n" + result += f"- High: ₹{price_info.get('dayHigh', 'N/A')}\n" + result += f"- Low: ₹{price_info.get('dayLow', 'N/A')}\n" + result += f"- Previous Close: ₹{price_info.get('previousClose', 'N/A')}\n" + + return result + +# Convenience functions +def get_indian_market_data(symbol: str, + start_date: str, + end_date: str, + exchange: str = "NSE", + config: Optional[Dict[str, Any]] = None) -> str: + """Get Indian market data with fallbacks""" + if config is None: + from tradingagents.indian_config import get_indian_config + config = get_indian_config() + + manager = IndianMarketDataManager(config) + return manager.get_market_data(symbol, start_date, end_date, exchange) + +def get_indian_fundamentals(symbol: str, + exchange: str = "NSE", + config: Optional[Dict[str, Any]] = None) -> str: + """Get Indian company fundamentals""" + if config is None: + from tradingagents.indian_config import get_indian_config + config = get_indian_config() + + manager = IndianMarketDataManager(config) + return manager.get_company_fundamentals(symbol, exchange) + +def get_indian_quote(symbol: str, + exchange: str = "NSE", + config: Optional[Dict[str, Any]] = None) -> str: + """Get Indian real-time quote""" + if config is None: + from tradingagents.indian_config import get_indian_config + config = get_indian_config() + + manager = IndianMarketDataManager(config) + return manager.get_real_time_quote(symbol, exchange) \ No newline at end of file diff --git a/tradingagents/dataflows/ticker_utils.py b/tradingagents/dataflows/ticker_utils.py new file mode 100644 index 00000000..2e9de4dd --- /dev/null +++ b/tradingagents/dataflows/ticker_utils.py @@ -0,0 +1,345 @@ +""" +Ticker utilities for Indian stock exchanges (NSE/BSE) +Handles ticker formatting, validation, and exchange-specific operations +""" + +import re +from typing import Dict, List, Optional, Tuple, Union +from enum import Enum +import logging + +logger = logging.getLogger(__name__) + +class IndianExchange(Enum): + """Indian stock exchanges""" + NSE = "NSE" + BSE = "BSE" + +# NSE and BSE ticker patterns +NSE_PATTERN = re.compile(r'^[A-Z0-9&\-]+\.NS$') +BSE_PATTERN = re.compile(r'^[A-Z0-9&\-]+\.BO$') +PLAIN_PATTERN = re.compile(r'^[A-Z0-9&\-]+$') + +# Exchange suffixes +EXCHANGE_SUFFIXES = { + IndianExchange.NSE: ".NS", + IndianExchange.BSE: ".BO" +} + +# Common ticker mappings between exchanges +NSE_TO_BSE_MAPPING = { + "RELIANCE": "500325", + "TCS": "532540", + "HDFCBANK": "500180", + "INFY": "500209", + "ICICIBANK": "532174", + "HINDUNILVR": "500696", + "ITC": "500875", + "SBIN": "500112", + "BHARTIARTL": "532454", + "KOTAKBANK": "500247", + "LT": "500510", + "HCLTECH": "532281", + "ASIANPAINT": "500820", + "MARUTI": "532500", + "BAJFINANCE": "500034", + "WIPRO": "507685", + "NESTLEIND": "500790", + "ULTRACEMCO": "532538", + "TITAN": "500114", + "POWERGRID": "532898" +} + +# Reverse mapping +BSE_TO_NSE_MAPPING = {v: k for k, v in NSE_TO_BSE_MAPPING.items()} + +class TickerFormatter: + """Handles ticker formatting for Indian exchanges""" + + @staticmethod + def format_ticker(symbol: str, exchange: Union[str, IndianExchange] = IndianExchange.NSE) -> str: + """ + Format ticker symbol for specified exchange + + Args: + symbol: Raw ticker symbol (e.g., 'RELIANCE', 'TCS') + exchange: Target exchange (NSE or BSE) + + Returns: + Formatted ticker (e.g., 'RELIANCE.NS', 'TCS.NS') + """ + if isinstance(exchange, str): + exchange = IndianExchange(exchange.upper()) + + # Clean the symbol + clean_symbol = symbol.upper().strip() + + # Remove existing suffixes if present + if clean_symbol.endswith(('.NS', '.BO')): + clean_symbol = clean_symbol[:-3] + + # Add appropriate suffix + suffix = EXCHANGE_SUFFIXES[exchange] + return f"{clean_symbol}{suffix}" + + @staticmethod + def format_nse_ticker(symbol: str) -> str: + """Format ticker for NSE""" + return TickerFormatter.format_ticker(symbol, IndianExchange.NSE) + + @staticmethod + def format_bse_ticker(symbol: str) -> str: + """Format ticker for BSE""" + return TickerFormatter.format_ticker(symbol, IndianExchange.BSE) + + @staticmethod + def get_plain_symbol(ticker: str) -> str: + """ + Extract plain symbol from formatted ticker + + Args: + ticker: Formatted ticker (e.g., 'RELIANCE.NS') + + Returns: + Plain symbol (e.g., 'RELIANCE') + """ + if ticker.endswith(('.NS', '.BO')): + return ticker[:-3] + return ticker.upper() + +class TickerValidator: + """Validates ticker symbols and formats""" + + @staticmethod + def is_valid_nse_ticker(ticker: str) -> bool: + """Check if ticker is valid NSE format""" + return bool(NSE_PATTERN.match(ticker)) + + @staticmethod + def is_valid_bse_ticker(ticker: str) -> bool: + """Check if ticker is valid BSE format""" + return bool(BSE_PATTERN.match(ticker)) + + @staticmethod + def is_valid_indian_ticker(ticker: str) -> bool: + """Check if ticker is valid for any Indian exchange""" + return (TickerValidator.is_valid_nse_ticker(ticker) or + TickerValidator.is_valid_bse_ticker(ticker)) + + @staticmethod + def get_exchange_from_ticker(ticker: str) -> Optional[IndianExchange]: + """ + Determine exchange from ticker format + + Args: + ticker: Ticker symbol + + Returns: + Exchange enum or None if not recognized + """ + if TickerValidator.is_valid_nse_ticker(ticker): + return IndianExchange.NSE + elif TickerValidator.is_valid_bse_ticker(ticker): + return IndianExchange.BSE + return None + + @staticmethod + def validate_and_format(symbol: str, preferred_exchange: str = "NSE") -> Tuple[bool, str, str]: + """ + Validate symbol and return formatted ticker + + Args: + symbol: Input symbol + preferred_exchange: Preferred exchange if not specified + + Returns: + Tuple of (is_valid, formatted_ticker, exchange) + """ + try: + # Check if already formatted + exchange = TickerValidator.get_exchange_from_ticker(symbol) + if exchange: + return True, symbol, exchange.value + + # Try to format for preferred exchange + formatted = TickerFormatter.format_ticker(symbol, preferred_exchange) + return True, formatted, preferred_exchange.upper() + + except Exception as e: + logger.error(f"Error validating ticker {symbol}: {e}") + return False, symbol, "" + +class TickerConverter: + """Converts tickers between exchanges""" + + @staticmethod + def nse_to_bse(nse_symbol: str) -> Optional[str]: + """ + Convert NSE symbol to BSE equivalent + + Args: + nse_symbol: NSE symbol (with or without .NS suffix) + + Returns: + BSE ticker with .BO suffix or None if not found + """ + plain_symbol = TickerFormatter.get_plain_symbol(nse_symbol) + bse_code = NSE_TO_BSE_MAPPING.get(plain_symbol) + + if bse_code: + return f"{bse_code}.BO" + return None + + @staticmethod + def bse_to_nse(bse_symbol: str) -> Optional[str]: + """ + Convert BSE symbol to NSE equivalent + + Args: + bse_symbol: BSE symbol (with or without .BO suffix) + + Returns: + NSE ticker with .NS suffix or None if not found + """ + plain_symbol = TickerFormatter.get_plain_symbol(bse_symbol) + nse_symbol = BSE_TO_NSE_MAPPING.get(plain_symbol) + + if nse_symbol: + return f"{nse_symbol}.NS" + return None + + @staticmethod + def get_cross_exchange_ticker(ticker: str) -> Optional[str]: + """ + Get equivalent ticker on the other exchange + + Args: + ticker: Input ticker + + Returns: + Cross-exchange ticker or None if not found + """ + exchange = TickerValidator.get_exchange_from_ticker(ticker) + + if exchange == IndianExchange.NSE: + return TickerConverter.nse_to_bse(ticker) + elif exchange == IndianExchange.BSE: + return TickerConverter.bse_to_nse(ticker) + + return None + +class TickerManager: + """Main interface for ticker operations""" + + def __init__(self): + self.formatter = TickerFormatter() + self.validator = TickerValidator() + self.converter = TickerConverter() + + def process_ticker(self, symbol: str, exchange: str = "NSE") -> Dict[str, Union[str, bool]]: + """ + Process ticker symbol and return comprehensive information + + Args: + symbol: Input ticker symbol + exchange: Preferred exchange + + Returns: + Dictionary with ticker information + """ + result = { + "original": symbol, + "is_valid": False, + "formatted_ticker": "", + "plain_symbol": "", + "exchange": "", + "cross_exchange_ticker": None, + "error": None + } + + try: + # Validate and format + is_valid, formatted, detected_exchange = self.validator.validate_and_format( + symbol, exchange + ) + + if is_valid: + result.update({ + "is_valid": True, + "formatted_ticker": formatted, + "plain_symbol": self.formatter.get_plain_symbol(formatted), + "exchange": detected_exchange, + "cross_exchange_ticker": self.converter.get_cross_exchange_ticker(formatted) + }) + else: + result["error"] = f"Invalid ticker format: {symbol}" + + except Exception as e: + result["error"] = str(e) + logger.error(f"Error processing ticker {symbol}: {e}") + + return result + + def get_all_formats(self, symbol: str) -> Dict[str, Optional[str]]: + """ + Get ticker in all available formats + + Args: + symbol: Input symbol + + Returns: + Dictionary with all ticker formats + """ + plain = self.formatter.get_plain_symbol(symbol) + + return { + "plain": plain, + "nse": self.formatter.format_nse_ticker(plain), + "bse": self.formatter.format_bse_ticker(plain), + "bse_equivalent": self.converter.nse_to_bse(plain), + "nse_equivalent": self.converter.bse_to_nse(plain) + } + +# Convenience functions for common operations +def format_indian_ticker(symbol: str, exchange: str = "NSE") -> str: + """Format ticker for Indian exchange""" + return TickerFormatter.format_ticker(symbol, exchange) + +def validate_indian_ticker(ticker: str) -> bool: + """Validate Indian ticker format""" + return TickerValidator.is_valid_indian_ticker(ticker) + +def get_plain_symbol(ticker: str) -> str: + """Get plain symbol from formatted ticker""" + return TickerFormatter.get_plain_symbol(ticker) + +def process_ticker_list(symbols: List[str], exchange: str = "NSE") -> List[Dict[str, Union[str, bool]]]: + """Process multiple ticker symbols""" + manager = TickerManager() + return [manager.process_ticker(symbol, exchange) for symbol in symbols] + +# Predefined lists for validation +VALID_NSE_SYMBOLS = list(NSE_TO_BSE_MAPPING.keys()) +VALID_BSE_SYMBOLS = list(BSE_TO_NSE_MAPPING.keys()) + +def get_supported_symbols(exchange: str = "NSE") -> List[str]: + """Get list of supported symbols for exchange""" + if exchange.upper() == "NSE": + return VALID_NSE_SYMBOLS.copy() + elif exchange.upper() == "BSE": + return VALID_BSE_SYMBOLS.copy() + else: + return VALID_NSE_SYMBOLS + VALID_BSE_SYMBOLS + +# Example usage and testing +if __name__ == "__main__": + # Test the ticker utilities + manager = TickerManager() + + test_symbols = ["RELIANCE", "TCS.NS", "500325.BO", "INVALID"] + + for symbol in test_symbols: + result = manager.process_ticker(symbol) + print(f"Symbol: {symbol}") + print(f"Result: {result}") + print("-" * 50) \ No newline at end of file diff --git a/tradingagents/indian_config.py b/tradingagents/indian_config.py new file mode 100644 index 00000000..1b8a4a05 --- /dev/null +++ b/tradingagents/indian_config.py @@ -0,0 +1,226 @@ +import os +import pytz +from typing import Dict, List, Any +from tradingagents.default_config import DEFAULT_CONFIG + +# Indian market configuration +INDIAN_CONFIG = DEFAULT_CONFIG.copy() + +# Update with Indian market specific settings +INDIAN_CONFIG.update({ + # Market identification + "market_region": "india", + "currency": "INR", + "timezone": "Asia/Kolkata", + + # Trading hours (IST) + "trading_hours": { + "pre_open": "09:00", + "open": "09:15", + "close": "15:30", + "post_close": "16:00", + "timezone": "Asia/Kolkata" + }, + + # Indian exchanges + "exchanges": { + "primary": "NSE", + "secondary": "BSE", + "supported": ["NSE", "BSE"] + }, + + # Data sources configuration + "data_sources": { + "market_data": { + "primary": "alpha_vantage", + "secondary": "yahoo_finance", + "fallback": "manual_nse_api" + }, + "fundamental_data": { + "primary": "alpha_vantage", + "secondary": "yahoo_finance", + "indian_specific": "moneycontrol_scraper" + }, + "news_data": { + "primary": "google_news", + "indian_sources": ["economic_times", "moneycontrol", "business_standard"], + "government": ["rbi_announcements", "sebi_updates"] + }, + "sentiment_data": { + "social_media": ["twitter_india", "reddit_india"], + "forums": ["indian_stock_forums", "valuepickr"] + } + }, + + # API keys (to be set via environment variables) + "api_keys": { + "alpha_vantage": os.getenv("ALPHA_VANTAGE_API_KEY"), + "financial_modeling_prep": os.getenv("FMP_API_KEY"), + "polygon": os.getenv("POLYGON_API_KEY"), + "twitter": os.getenv("TWITTER_API_KEY"), + "news_api": os.getenv("NEWS_API_KEY") + }, + + # Indian market specific parameters + "market_parameters": { + "circuit_breakers": { + "individual_stock": {"upper": 0.20, "lower": 0.20}, # 20% circuit breakers + "index": {"upper": 0.10, "lower": 0.10} # 10% for indices + }, + "lot_sizes": { + # Will be populated dynamically or from file + "default": 1 + }, + "tick_sizes": { + "below_100": 0.05, + "100_to_1000": 0.05, + "above_1000": 0.05 + }, + "settlement": "T+1" # Indian market settlement cycle + }, + + # Indian indices for correlation analysis + "benchmark_indices": { + "broad_market": ["^NSEI", "^BSESN"], # Nifty 50, Sensex + "sectoral": { + "banking": "^CNXBANK", + "it": "^CNXIT", + "auto": "^CNXAUTO", + "pharma": "^CNXPHARMA", + "fmcg": "^CNXFMCG", + "metal": "^CNXMETAL", + "realty": "^CNXREALTY" + } + }, + + # Regulatory and compliance + "regulatory": { + "sebi_regulations": True, + "insider_trading_rules": True, + "disclosure_requirements": True, + "algorithmic_trading_approval": False # Set to True if approved + }, + + # Indian market holidays (major ones - should be updated annually) + "market_holidays_2024": [ + "2024-01-26", # Republic Day + "2024-03-08", # Holi + "2024-03-29", # Good Friday + "2024-04-11", # Eid ul Fitr + "2024-04-17", # Ram Navami + "2024-05-01", # Maharashtra Day + "2024-08-15", # Independence Day + "2024-08-26", # Janmashtami + "2024-10-02", # Gandhi Jayanti + "2024-10-31", # Diwali Laxmi Puja + "2024-11-01", # Diwali Balipratipada + "2024-11-15", # Guru Nanak Jayanti + ], + + # Risk management parameters for Indian market + "risk_parameters": { + "max_position_size": 0.05, # 5% of portfolio + "stop_loss_default": 0.08, # 8% stop loss + "volatility_adjustment": 1.2, # Indian markets are more volatile + "liquidity_threshold": 1000000, # Minimum daily volume in INR + "market_cap_preference": "large_cap" # Prefer large cap for stability + }, + + # Currency and conversion + "currency_settings": { + "base_currency": "INR", + "usd_inr_tracking": True, + "currency_hedging": False + } +}) + +# Major Indian stocks for testing and validation +MAJOR_INDIAN_STOCKS = { + # Large Cap - Nifty 50 constituents + "RELIANCE": {"name": "Reliance Industries Ltd", "sector": "Energy", "exchange": "NSE"}, + "TCS": {"name": "Tata Consultancy Services", "sector": "IT", "exchange": "NSE"}, + "HDFCBANK": {"name": "HDFC Bank Ltd", "sector": "Banking", "exchange": "NSE"}, + "INFY": {"name": "Infosys Ltd", "sector": "IT", "exchange": "NSE"}, + "ICICIBANK": {"name": "ICICI Bank Ltd", "sector": "Banking", "exchange": "NSE"}, + "HINDUNILVR": {"name": "Hindustan Unilever Ltd", "sector": "FMCG", "exchange": "NSE"}, + "ITC": {"name": "ITC Ltd", "sector": "FMCG", "exchange": "NSE"}, + "SBIN": {"name": "State Bank of India", "sector": "Banking", "exchange": "NSE"}, + "BHARTIARTL": {"name": "Bharti Airtel Ltd", "sector": "Telecom", "exchange": "NSE"}, + "KOTAKBANK": {"name": "Kotak Mahindra Bank", "sector": "Banking", "exchange": "NSE"}, + "LT": {"name": "Larsen & Toubro Ltd", "sector": "Infrastructure", "exchange": "NSE"}, + "HCLTECH": {"name": "HCL Technologies Ltd", "sector": "IT", "exchange": "NSE"}, + "ASIANPAINT": {"name": "Asian Paints Ltd", "sector": "Paints", "exchange": "NSE"}, + "MARUTI": {"name": "Maruti Suzuki India Ltd", "sector": "Auto", "exchange": "NSE"}, + "BAJFINANCE": {"name": "Bajaj Finance Ltd", "sector": "NBFC", "exchange": "NSE"}, + "WIPRO": {"name": "Wipro Ltd", "sector": "IT", "exchange": "NSE"}, + "NESTLEIND": {"name": "Nestle India Ltd", "sector": "FMCG", "exchange": "NSE"}, + "ULTRACEMCO": {"name": "UltraTech Cement Ltd", "sector": "Cement", "exchange": "NSE"}, + "TITAN": {"name": "Titan Company Ltd", "sector": "Jewellery", "exchange": "NSE"}, + "POWERGRID": {"name": "Power Grid Corporation", "sector": "Power", "exchange": "NSE"} +} + +# Sectoral classifications for Indian market +INDIAN_SECTORS = { + "banking": ["HDFCBANK", "ICICIBANK", "SBIN", "KOTAKBANK", "AXISBANK"], + "it": ["TCS", "INFY", "HCLTECH", "WIPRO", "TECHM"], + "fmcg": ["HINDUNILVR", "ITC", "NESTLEIND", "BRITANNIA"], + "auto": ["MARUTI", "TATAMOTORS", "M&M", "BAJAJ-AUTO"], + "pharma": ["SUNPHARMA", "DRREDDY", "CIPLA", "DIVISLAB"], + "energy": ["RELIANCE", "ONGC", "IOC", "BPCL"], + "telecom": ["BHARTIARTL", "JIOFINANCE"], + "metals": ["TATASTEEL", "HINDALCO", "VEDL", "JSW"], + "cement": ["ULTRACEMCO", "SHREECEM", "ACC"], + "nbfc": ["BAJFINANCE", "SBICARD", "CHOLAFIN"] +} + +def get_indian_config() -> Dict[str, Any]: + """Get the Indian market configuration""" + return INDIAN_CONFIG.copy() + +def get_major_stocks() -> Dict[str, Dict[str, str]]: + """Get major Indian stocks dictionary""" + return MAJOR_INDIAN_STOCKS.copy() + +def get_sector_stocks(sector: str) -> List[str]: + """Get stocks for a specific sector""" + return INDIAN_SECTORS.get(sector.lower(), []) + +def is_market_open() -> bool: + """Check if Indian market is currently open""" + import datetime + + ist = pytz.timezone('Asia/Kolkata') + now = datetime.datetime.now(ist) + + # Check if it's a weekday + if now.weekday() >= 5: # Saturday = 5, Sunday = 6 + return False + + # Check trading hours + market_open = now.replace(hour=9, minute=15, second=0, microsecond=0) + market_close = now.replace(hour=15, minute=30, second=0, microsecond=0) + + return market_open <= now <= market_close + +def get_market_status() -> str: + """Get current market status""" + import datetime + + ist = pytz.timezone('Asia/Kolkata') + now = datetime.datetime.now(ist) + + if now.weekday() >= 5: + return "closed_weekend" + + market_open = now.replace(hour=9, minute=15, second=0, microsecond=0) + market_close = now.replace(hour=15, minute=30, second=0, microsecond=0) + pre_open = now.replace(hour=9, minute=0, second=0, microsecond=0) + + if now < pre_open: + return "pre_market" + elif pre_open <= now < market_open: + return "pre_open" + elif market_open <= now <= market_close: + return "open" + else: + return "closed" \ No newline at end of file