Adding support for Indian Market

This commit is contained in:
rgunkar 2025-07-05 19:15:09 +05:30
parent a438acdbbd
commit 72e0eb15a7
12 changed files with 4546 additions and 0 deletions

485
cli/indian_cli.py Normal file
View File

@ -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 <sector_name>' 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()

View File

@ -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.

View File

@ -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()

View File

@ -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

401
tests/test_indian_market.py Normal file
View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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'
]

View File

@ -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'
]

View File

@ -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)

View File

@ -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)

View File

@ -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"