Merge pull request #532 from 0marMarie/llm-engineering-contributions-omar
Week4: Ai Stock Trading
This commit is contained in:
325
week4/community-contributions/ai_stock_trading/README.md
Normal file
325
week4/community-contributions/ai_stock_trading/README.md
Normal file
@@ -0,0 +1,325 @@
|
||||
# 📈 AI Stock Trading & Sharia Compliance Platform
|
||||
|
||||
A comprehensive **Streamlit-based** web application that provides AI-powered stock analysis with Islamic Sharia compliance assessment. This professional-grade platform combines real-time financial data from USA and Egyptian markets, advanced technical analysis, and institutional-quality AI-driven insights to help users make informed investment decisions while adhering to Islamic finance principles.
|
||||
|
||||
## 📸 Application Screenshots
|
||||
|
||||
### Home View
|
||||

|
||||
*Main application interface with market selection and stock input*
|
||||
|
||||
### Chat Interface
|
||||

|
||||
*Interactive chat for trading advice, Sharia compliance, and stock analysis*
|
||||
|
||||
### Dashboard View
|
||||

|
||||
*Comprehensive dashboard with KPIs, charts, and real-time metrics*
|
||||
|
||||
## 🎯 Key Features
|
||||
|
||||
### 📊 **Comprehensive Stock Analysis**
|
||||
- Real-time data fetching from multiple markets (USA, Egypt)
|
||||
- Advanced technical indicators (RSI, MACD, Bollinger Bands, Moving Averages)
|
||||
- Risk assessment and volatility analysis
|
||||
- Performance metrics across multiple time periods
|
||||
|
||||
### 🤖 **AI-Powered Trading Decisions**
|
||||
- GPT-4 powered investment recommendations
|
||||
- Buy/Hold/Sell signals with confidence scores
|
||||
- Price targets and stop-loss suggestions
|
||||
- Algorithmic + AI combined decision making
|
||||
|
||||
### ☪️ **Sharia Compliance Checking**
|
||||
- Islamic finance principles assessment
|
||||
- Halal/Haram rulings with detailed reasoning
|
||||
- Business activity and financial ratio screening
|
||||
- Alternative investment suggestions
|
||||
|
||||
### 💬 **Natural Language Interface**
|
||||
- Interactive chat interface for stock discussions
|
||||
- Ask questions in plain English
|
||||
- Context-aware responses about selected stocks
|
||||
- Quick action buttons for common queries
|
||||
|
||||
### 📈 **Interactive Dashboards**
|
||||
- Comprehensive metrics dashboard
|
||||
- Multiple chart types (Price, Performance, Risk, Trading Signals)
|
||||
- Real-time data visualization with Plotly
|
||||
- Exportable analysis reports
|
||||
- Real-time price charts with volume data
|
||||
- Professional matplotlib-based visualizations
|
||||
- Price statistics and performance metrics
|
||||
- Responsive chart interface
|
||||
|
||||
### 🖥️ **Professional Interface**
|
||||
- Clean, modern Streamlit web interface
|
||||
- Multi-market support (USA & Egyptian stocks)
|
||||
- Interactive chat interface with context awareness
|
||||
- Real-time KPI dashboard with currency formatting
|
||||
- Quick action buttons for common analysis tasks
|
||||
|
||||
## 🚀 Quick Start
|
||||
|
||||
### Prerequisites
|
||||
|
||||
Ensure you have Python 3.8+ installed on your system.
|
||||
|
||||
### Installation
|
||||
|
||||
1. **Clone or download this project**
|
||||
```bash
|
||||
git clone <repository-url>
|
||||
cd ai_stock_trading
|
||||
```
|
||||
|
||||
2. **Install dependencies**
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
3. **Set up environment variables**
|
||||
Create a `.env` file in the project root:
|
||||
```bash
|
||||
OPENAI_API_KEY=your-api-key-here
|
||||
```
|
||||
|
||||
### Running the Application
|
||||
|
||||
1. **Launch the Streamlit app**
|
||||
```bash
|
||||
streamlit run main_app.py
|
||||
```
|
||||
|
||||
2. **Access the web interface** at `http://localhost:8501`
|
||||
|
||||
3. **Select your market** (USA or Egypt) from the sidebar
|
||||
|
||||
4. **Enter a stock symbol** and start analyzing!
|
||||
|
||||
## 📖 How to Use
|
||||
|
||||
1. **Select Market**: Choose between USA or Egypt from the sidebar
|
||||
2. **Enter Stock Symbol**: Input a ticker (e.g., AAPL for USA, ABUK.CA for Egypt)
|
||||
3. **View Dashboard**: See real-time KPIs, price charts, and key metrics
|
||||
4. **Use Chat Interface**: Ask questions or request specific analysis:
|
||||
- "Give me trading advice for AAPL"
|
||||
- "Is this stock Sharia compliant?"
|
||||
- "What's the price target?"
|
||||
5. **Review Professional Analysis**:
|
||||
- **Trading Recommendations**: Institutional-grade BUY/HOLD/SELL advice
|
||||
- **Sharia Compliance**: Comprehensive Islamic finance screening
|
||||
- **Technical Analysis**: Advanced indicators and risk assessment
|
||||
|
||||
### Example Tickers to Try
|
||||
|
||||
#### USA Market
|
||||
| Ticker | Company | Sector | Expected Sharia Status |
|
||||
|--------|---------|--------|-----------------------|
|
||||
| **AAPL** | Apple Inc. | Technology | ✅ Likely Halal |
|
||||
| **MSFT** | Microsoft Corp. | Technology | ✅ Likely Halal |
|
||||
| **GOOGL** | Alphabet Inc. | Technology | ✅ Likely Halal |
|
||||
| **JNJ** | Johnson & Johnson | Healthcare | ✅ Likely Halal |
|
||||
| **BAC** | Bank of America | Banking | ❌ Likely Haram |
|
||||
| **JPM** | JPMorgan Chase | Banking | ❌ Likely Haram |
|
||||
|
||||
#### Egypt Market
|
||||
| Ticker | Company | Sector | Expected Sharia Status |
|
||||
|--------|---------|--------|-----------------------|
|
||||
| **ABUK.CA** | Abu Qir Fertilizers | Industrial | ✅ Likely Halal |
|
||||
| **ETEL.CA** | Egyptian Telecom | Telecom | ✅ Likely Halal |
|
||||
| **HRHO.CA** | Hassan Allam Holding | Construction | ✅ Likely Halal |
|
||||
| **CIB.CA** | Commercial Intl Bank | Banking | ❌ Likely Haram |
|
||||
|
||||
## 🔧 Technical Implementation
|
||||
|
||||
### Modular Architecture
|
||||
|
||||
The platform is built with a clean, modular architecture using separate tool modules:
|
||||
|
||||
#### 1. **Stock Fetching Module** (`tools/fetching.py`)
|
||||
- **Multi-Market Support**: USA (75+ stocks) and Egypt (50+ stocks) with proper currency handling
|
||||
- **Real-Time Data**: Uses yfinance API with robust error handling
|
||||
- **Currency Formatting**: Automatic USD/EGP formatting based on market
|
||||
- **Stock Info Enrichment**: Company details, market cap, sector classification
|
||||
|
||||
#### 2. **Technical Analysis Module** (`tools/analysis.py`)
|
||||
- **Advanced Indicators**: RSI, MACD, Bollinger Bands, Moving Averages
|
||||
- **Risk Metrics**: Volatility analysis, Sharpe ratio, maximum drawdown
|
||||
- **Performance Analysis**: Multi-timeframe returns and trend analysis
|
||||
- **Professional Calculations**: Annualized metrics and statistical analysis
|
||||
|
||||
#### 3. **Trading Decisions Module** (`tools/trading_decisions.py`)
|
||||
- **Institutional-Grade AI**: Senior analyst persona with 15+ years experience
|
||||
- **Professional Standards**: BUY/HOLD/SELL with confidence, price targets, stop-loss
|
||||
- **Risk Management**: Risk-reward ratios, time horizons, risk assessment
|
||||
- **Robust JSON Parsing**: Handles malformed AI responses with fallback logic
|
||||
|
||||
#### 4. **Sharia Compliance Module** (`tools/sharia_compliance.py`)
|
||||
- **Comprehensive Screening**: Business activities, financial ratios, trading practices
|
||||
- **AAOIFI Standards**: Debt-to-assets < 33%, interest income < 5%
|
||||
- **Prohibited Activities**: 50+ categories including banking, gambling, alcohol
|
||||
- **User-Triggered Analysis**: Only shows when specifically requested
|
||||
|
||||
#### 5. **Charting Module** (`tools/charting.py`)
|
||||
- **Professional Visualizations**: Plotly-based interactive charts
|
||||
- **Multiple Chart Types**: Price, volume, technical indicators
|
||||
- **Responsive Design**: Mobile-friendly chart rendering
|
||||
- **Export Capabilities**: PNG/HTML export functionality
|
||||
|
||||
#### 6. **Main Application** (`main_app.py`)
|
||||
- **Streamlit Interface**: Modern, responsive web application
|
||||
- **Chat Integration**: Context-aware conversational interface
|
||||
- **Real-Time KPIs**: Live dashboard with key metrics
|
||||
- **Session Management**: Persistent data across user interactions
|
||||
|
||||
### AI Integration
|
||||
|
||||
The platform leverages OpenAI's GPT-4o-mini with specialized prompts:
|
||||
|
||||
#### Trading Analysis Prompts
|
||||
- **Senior Analyst Persona**: 15+ years institutional experience
|
||||
- **Professional Standards**: Risk-reward ratios, logical price targets
|
||||
- **Structured Output**: JSON format with validation and error handling
|
||||
- **Technical Focus**: Based on RSI, MACD, trend analysis, volume patterns
|
||||
|
||||
#### Sharia Compliance Prompts
|
||||
- **Islamic Scholar Approach**: Follows AAOIFI and DSN standards
|
||||
- **Comprehensive Screening**: Business activities, financial ratios, trading practices
|
||||
- **Scholarly Reasoning**: Detailed justification with Islamic finance principles
|
||||
- **Confidence Scoring**: Quantified certainty levels for rulings
|
||||
|
||||
## 📊 Sample Analysis Output
|
||||
|
||||
### Trade Recommendation Example
|
||||
```
|
||||
RECOMMENDATION: BUY
|
||||
|
||||
Based on the analysis of AAPL:
|
||||
• 1Y return of +15.2% shows strong performance
|
||||
• Volatility of 24.3% indicates manageable risk
|
||||
• Recent 1M return of +5.8% shows positive momentum
|
||||
• Strong volume indicates healthy trading activity
|
||||
|
||||
Key factors supporting BUY decision:
|
||||
- Consistent positive returns across timeframes
|
||||
- Volatility within acceptable range for tech stocks
|
||||
- Strong market position and fundamentals
|
||||
```
|
||||
|
||||
### Sharia Assessment Example
|
||||
```json
|
||||
{
|
||||
"ruling": "HALAL",
|
||||
"confidence": 85,
|
||||
"justification": "Apple Inc. primarily operates in technology hardware and software, which are permissible under Islamic law. The company's main revenue sources (iPhone, Mac, services) do not involve prohibited activities such as gambling, alcohol, or interest-based banking."
|
||||
}
|
||||
```
|
||||
|
||||
## ⚠️ Important Disclaimers
|
||||
|
||||
### Financial Disclaimer
|
||||
- **This tool is for educational purposes only**
|
||||
- **Not professional financial advice**
|
||||
- **Past performance does not guarantee future results**
|
||||
- **Consult qualified financial advisors before making investment decisions**
|
||||
|
||||
### Sharia Compliance Disclaimer
|
||||
- **Consult qualified Islamic scholars for authoritative rulings**
|
||||
- **AI assessments are preliminary and may have limitations**
|
||||
- **Consider multiple sources for Sharia compliance verification**
|
||||
- **Individual scholarly interpretations may vary**
|
||||
|
||||
### Technical Limitations
|
||||
- **Data accuracy depends on yfinance API availability**
|
||||
- **OpenAI API calls consume credits/tokens**
|
||||
- **Network connectivity required for real-time data**
|
||||
- **Analysis speed depends on API response times**
|
||||
|
||||
## 🔧 Customization
|
||||
|
||||
### Adding New Analysis Periods
|
||||
```python
|
||||
periods = ["1mo", "3mo", "6mo", "1y", "2y", "5y"] # Modify as needed
|
||||
```
|
||||
|
||||
### Modifying Sharia Criteria
|
||||
```python
|
||||
# Update the Sharia assessment prompt with additional criteria
|
||||
prompt = f"""
|
||||
Additional criteria:
|
||||
- Debt-to-market cap ratio analysis
|
||||
- Revenue source breakdown
|
||||
- ESG factors consideration
|
||||
"""
|
||||
```
|
||||
|
||||
### Styling the Interface
|
||||
```python
|
||||
demo = create_interface()
|
||||
demo.launch(theme="huggingface") # Try different themes
|
||||
```
|
||||
|
||||
## 📚 Dependencies
|
||||
|
||||
- **yfinance**: Real-time financial data
|
||||
- **openai**: AI-powered analysis
|
||||
- **pandas**: Data manipulation
|
||||
- **matplotlib**: Chart generation
|
||||
- **gradio**: Web interface
|
||||
- **requests**: HTTP requests
|
||||
- **beautifulsoup4**: Web scraping
|
||||
- **numpy**: Numerical computations
|
||||
|
||||
## 🤝 Contributing
|
||||
|
||||
Contributions are welcome! Please feel free to submit issues, feature requests, or pull requests.
|
||||
|
||||
### Areas for Enhancement
|
||||
- Additional technical indicators
|
||||
- More sophisticated Sharia screening
|
||||
- Portfolio analysis features
|
||||
- Historical backtesting
|
||||
- Mobile-responsive design
|
||||
|
||||
### 🔮 Future Work: MCP Integration
|
||||
|
||||
We plan to implement a **Model Context Protocol (MCP) layer** to make all trading tools accessible as standardized MCP tools:
|
||||
|
||||
#### Planned MCP Tools:
|
||||
- **`stock_fetcher`** - Real-time market data retrieval for USA/Egypt markets
|
||||
- **`technical_analyzer`** - Advanced technical analysis with 20+ indicators
|
||||
- **`sharia_checker`** - Islamic finance compliance screening
|
||||
- **`trading_advisor`** - AI-powered institutional-grade recommendations
|
||||
- **`risk_assessor`** - Portfolio risk analysis and management
|
||||
- **`chart_generator`** - Professional financial visualizations
|
||||
|
||||
#### Benefits of MCP Integration:
|
||||
- **Standardized Interface**: Consistent tool access across different AI systems
|
||||
- **Interoperability**: Easy integration with other MCP-compatible platforms
|
||||
- **Scalability**: Modular architecture for adding new financial tools
|
||||
- **Reusability**: Tools can be used independently or combined
|
||||
- **Professional Integration**: Compatible with institutional trading platforms
|
||||
|
||||
This will enable the platform to serve as a comprehensive financial analysis toolkit that can be integrated into various AI-powered trading systems and workflows.
|
||||
|
||||
## 📄 License
|
||||
|
||||
This project is for educational purposes. Please ensure compliance with:
|
||||
- OpenAI API usage terms
|
||||
- Yahoo Finance data usage policies
|
||||
- Local financial regulations
|
||||
- Islamic finance guidelines
|
||||
|
||||
## 🙏 Acknowledgments
|
||||
|
||||
- **yfinance** for providing free financial data API
|
||||
- **OpenAI** for GPT-4o-mini language model
|
||||
- **Gradio** for the intuitive web interface framework
|
||||
- **Islamic finance scholars** for Sharia compliance frameworks
|
||||
|
||||
---
|
||||
|
||||
**Made with ❤️ for the Muslim tech community and ethical investing enthusiasts**
|
||||
|
||||
*"And Allah knows best" - وَاللَّهُ أَعْلَمُ*
|
||||
@@ -0,0 +1,3 @@
|
||||
"""
|
||||
UI Components for the Stock Trading Platform
|
||||
"""
|
||||
@@ -0,0 +1,156 @@
|
||||
"""
|
||||
Clean, professional chat interface component
|
||||
"""
|
||||
|
||||
import streamlit as st
|
||||
from core.ai_assistant import ai_assistant
|
||||
from core.data_service import data_service
|
||||
|
||||
|
||||
class ChatInterface:
|
||||
"""Professional chat interface for stock analysis"""
|
||||
|
||||
@staticmethod
|
||||
def render(symbol: str, country: str):
|
||||
"""Render the chat interface"""
|
||||
if not symbol:
|
||||
st.warning("⚠️ Please select a stock from the sidebar.")
|
||||
return
|
||||
|
||||
# Display chat history
|
||||
if 'chat_history' not in st.session_state:
|
||||
st.session_state.chat_history = []
|
||||
|
||||
if st.session_state.chat_history:
|
||||
for message in st.session_state.chat_history:
|
||||
if message['role'] == 'user':
|
||||
st.chat_message("user").write(message['content'])
|
||||
else:
|
||||
st.chat_message("assistant").write(message['content'])
|
||||
else:
|
||||
# Clean welcome message
|
||||
basic_info = data_service.get_basic_info(symbol, country)
|
||||
company_name = basic_info.get('company_name', symbol)
|
||||
welcome_msg = f"👋 Hello! I'm your AI assistant for **{company_name} ({symbol})**. Ask me anything!"
|
||||
st.chat_message("assistant").write(welcome_msg)
|
||||
|
||||
# Chat input
|
||||
user_input = st.chat_input("Ask about price, trends, analysis, trading recommendations...")
|
||||
|
||||
if user_input:
|
||||
# Add user message
|
||||
st.session_state.chat_history.append({'role': 'user', 'content': user_input})
|
||||
|
||||
# Generate AI response (only loads data if tools are called)
|
||||
with st.spinner("Thinking..."):
|
||||
ai_response = ai_assistant.generate_response(user_input, symbol, country)
|
||||
|
||||
# Add AI response
|
||||
st.session_state.chat_history.append({'role': 'assistant', 'content': ai_response})
|
||||
st.rerun()
|
||||
|
||||
# Quick actions (collapsed by default)
|
||||
ChatInterface._render_quick_actions(symbol, country)
|
||||
|
||||
@staticmethod
|
||||
def _render_quick_actions(symbol: str, country: str):
|
||||
"""Render quick action buttons"""
|
||||
with st.expander("🚀 Quick Actions", expanded=False):
|
||||
col1, col2, col3, col4 = st.columns(4)
|
||||
|
||||
with col1:
|
||||
if st.button("📈 Price Info", use_container_width=True):
|
||||
ChatInterface._add_price_info(symbol, country)
|
||||
st.rerun()
|
||||
|
||||
with col2:
|
||||
if st.button("📊 30-Day Analysis", use_container_width=True):
|
||||
ChatInterface._add_medium_term_analysis(symbol)
|
||||
st.rerun()
|
||||
|
||||
with col3:
|
||||
if st.button("💰 Trading Rec", use_container_width=True):
|
||||
ChatInterface._add_trading_recommendation(symbol, country)
|
||||
st.rerun()
|
||||
|
||||
with col4:
|
||||
if st.button("☪️ Sharia", use_container_width=True):
|
||||
ChatInterface._add_sharia_compliance(symbol, country)
|
||||
st.rerun()
|
||||
|
||||
@staticmethod
|
||||
def _add_price_info(symbol: str, country: str):
|
||||
"""Add current price info to chat"""
|
||||
basic_info = data_service.get_basic_info(symbol, country)
|
||||
|
||||
current_price = basic_info.get('current_price', 0)
|
||||
market_cap = basic_info.get('market_cap', 0)
|
||||
sector = basic_info.get('sector', 'N/A')
|
||||
|
||||
message = f"""📈 **Current Price Info for {symbol}:**
|
||||
|
||||
💰 **Price:** ${current_price:.2f}
|
||||
🏢 **Market Cap:** ${market_cap:,.0f}
|
||||
🏭 **Sector:** {sector}"""
|
||||
|
||||
st.session_state.chat_history.append({'role': 'assistant', 'content': message})
|
||||
|
||||
@staticmethod
|
||||
def _add_medium_term_analysis(symbol: str):
|
||||
"""Add 30-day analysis to chat"""
|
||||
analysis = data_service.get_analysis(symbol, "1mo")
|
||||
|
||||
if 'error' in analysis:
|
||||
message = f"❌ **30-Day Analysis:** {analysis['error']}"
|
||||
else:
|
||||
return_pct = analysis.get('total_return_pct', 0)
|
||||
volatility = analysis.get('volatility_annualized', 0)
|
||||
trend = analysis.get('trend_direction', 'neutral')
|
||||
|
||||
message = f"""📊 **30-Day Analysis for {symbol}:**
|
||||
|
||||
📈 **Return:** {return_pct:.2f}%
|
||||
📉 **Volatility:** {volatility:.1f}% (annualized)
|
||||
🎯 **Trend:** {trend.title()}"""
|
||||
|
||||
st.session_state.chat_history.append({'role': 'assistant', 'content': message})
|
||||
|
||||
@staticmethod
|
||||
def _add_trading_recommendation(symbol: str, country: str):
|
||||
"""Add trading recommendation to chat"""
|
||||
trading = data_service.get_trading_recommendation(symbol, country)
|
||||
|
||||
if 'error' in trading:
|
||||
message = f"❌ **Trading Recommendation:** {trading['error']}"
|
||||
else:
|
||||
rec = trading.get('recommendation', 'HOLD')
|
||||
conf = trading.get('confidence', 0.5) * 100
|
||||
reasoning = trading.get('reasoning', 'No reasoning available')
|
||||
|
||||
message = f"""💰 **Trading Recommendation for {symbol}:**
|
||||
|
||||
🎯 **Action:** {rec}
|
||||
📊 **Confidence:** {conf:.0f}%
|
||||
💭 **Reasoning:** {reasoning[:200]}..."""
|
||||
|
||||
st.session_state.chat_history.append({'role': 'assistant', 'content': message})
|
||||
|
||||
@staticmethod
|
||||
def _add_sharia_compliance(symbol: str, country: str):
|
||||
"""Add Sharia compliance to chat"""
|
||||
sharia = data_service.get_sharia_compliance(symbol, country)
|
||||
|
||||
if 'error' in sharia:
|
||||
message = f"❌ **Sharia Compliance:** {sharia['error']}"
|
||||
else:
|
||||
ruling = sharia.get('ruling', 'UNCERTAIN')
|
||||
conf = sharia.get('confidence', 0.5) * 100
|
||||
|
||||
status_emoji = "✅" if ruling == "HALAL" else "❌" if ruling == "HARAM" else "⚠️"
|
||||
|
||||
message = f"""☪️ **Sharia Compliance for {symbol}:**
|
||||
|
||||
{status_emoji} **Ruling:** {ruling}
|
||||
📊 **Confidence:** {conf:.0f}%"""
|
||||
|
||||
st.session_state.chat_history.append({'role': 'assistant', 'content': message})
|
||||
@@ -0,0 +1,3 @@
|
||||
"""
|
||||
Core module for AI Stock Trading Platform
|
||||
"""
|
||||
@@ -0,0 +1,275 @@
|
||||
import os
|
||||
from typing import Dict, Any, List
|
||||
from openai import OpenAI
|
||||
from .data_service import data_service
|
||||
|
||||
|
||||
class AIAssistant:
|
||||
"""Enhanced AI assistant with comprehensive stock analysis tools"""
|
||||
|
||||
def __init__(self):
|
||||
self.client = OpenAI(api_key=os.getenv('OPENAI_API_KEY'))
|
||||
|
||||
def get_enhanced_tools(self) -> List[Dict[str, Any]]:
|
||||
"""Get comprehensive tool definitions for OpenAI function calling"""
|
||||
return [
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "get_current_price_info",
|
||||
"description": "Get current price, basic metrics, and company info",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"symbol": {"type": "string", "description": "Stock symbol"}
|
||||
},
|
||||
"required": ["symbol"]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "get_short_term_analysis",
|
||||
"description": "Get 10-day technical analysis and short-term trends",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"symbol": {"type": "string", "description": "Stock symbol"}
|
||||
},
|
||||
"required": ["symbol"]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "get_medium_term_analysis",
|
||||
"description": "Get 30-day technical analysis and medium-term trends",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"symbol": {"type": "string", "description": "Stock symbol"}
|
||||
},
|
||||
"required": ["symbol"]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "get_long_term_analysis",
|
||||
"description": "Get 90-day technical analysis and long-term trends",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"symbol": {"type": "string", "description": "Stock symbol"}
|
||||
},
|
||||
"required": ["symbol"]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "get_comprehensive_analysis",
|
||||
"description": "Get full 1-year technical analysis with all indicators",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"symbol": {"type": "string", "description": "Stock symbol"}
|
||||
},
|
||||
"required": ["symbol"]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "get_trading_recommendation",
|
||||
"description": "Get buy/hold/sell recommendation with price targets and reasoning",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"symbol": {"type": "string", "description": "Stock symbol"}
|
||||
},
|
||||
"required": ["symbol"]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "get_sharia_compliance",
|
||||
"description": "Get Islamic finance compliance analysis",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"symbol": {"type": "string", "description": "Stock symbol"}
|
||||
},
|
||||
"required": ["symbol"]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "compare_time_periods",
|
||||
"description": "Compare performance across multiple time periods (10d, 30d, 90d)",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"symbol": {"type": "string", "description": "Stock symbol"}
|
||||
},
|
||||
"required": ["symbol"]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
def generate_response(self, user_input: str, symbol: str, country: str) -> str:
|
||||
"""Generate AI response with enhanced tool calling"""
|
||||
try:
|
||||
# Get basic info without heavy loading
|
||||
basic_info = data_service.get_basic_info(symbol, country)
|
||||
|
||||
system_msg = f"""You are a professional financial advisor assistant for {symbol}.
|
||||
|
||||
IMPORTANT: Only call tools when users specifically request:
|
||||
- Price information or basic metrics → get_current_price_info
|
||||
- Short-term analysis (10 days) → get_short_term_analysis
|
||||
- Medium-term analysis (30 days) → get_medium_term_analysis
|
||||
- Long-term analysis (90 days) → get_long_term_analysis
|
||||
- Comprehensive analysis (1 year) → get_comprehensive_analysis
|
||||
- Trading recommendations → get_trading_recommendation
|
||||
- Sharia compliance → get_sharia_compliance
|
||||
- Time period comparisons → compare_time_periods
|
||||
|
||||
For general questions about the company, market commentary, or basic information, respond directly without calling tools.
|
||||
Keep responses concise and professional."""
|
||||
|
||||
user_msg = f"""Stock: {symbol} ({basic_info.get('company_name', 'N/A')})
|
||||
Country: {country}
|
||||
Sector: {basic_info.get('sector', 'N/A')}
|
||||
User Question: {user_input}"""
|
||||
|
||||
response = self.client.chat.completions.create(
|
||||
model="gpt-4o-mini",
|
||||
messages=[
|
||||
{"role": "system", "content": system_msg},
|
||||
{"role": "user", "content": user_msg}
|
||||
],
|
||||
tools=self.get_enhanced_tools(), # type: ignore
|
||||
tool_choice="auto",
|
||||
temperature=0.7,
|
||||
max_tokens=600
|
||||
)
|
||||
|
||||
message = response.choices[0].message
|
||||
|
||||
if message.tool_calls:
|
||||
return self._handle_tool_calls(message.tool_calls, user_input, symbol, country)
|
||||
|
||||
return message.content or "I apologize, but I couldn't generate a response."
|
||||
|
||||
except Exception as e:
|
||||
return f"Sorry, I encountered an error: {str(e)}"
|
||||
|
||||
def _handle_tool_calls(self, tool_calls, user_input: str, symbol: str, country: str) -> str:
|
||||
"""Handle tool calls and generate final response"""
|
||||
tool_results = []
|
||||
|
||||
for tool_call in tool_calls:
|
||||
function_name = tool_call.function.name
|
||||
|
||||
try:
|
||||
if function_name == "get_current_price_info":
|
||||
basic_info = data_service.get_basic_info(symbol, country)
|
||||
current_price = basic_info.get('current_price', 0)
|
||||
market_cap = basic_info.get('market_cap', 0)
|
||||
tool_results.append(f"Current Price: ${current_price:.2f}, Market Cap: ${market_cap:,.0f}")
|
||||
|
||||
elif function_name == "get_short_term_analysis":
|
||||
analysis = data_service.get_analysis(symbol, "10d")
|
||||
if 'error' not in analysis:
|
||||
return_pct = analysis.get('total_return_pct', 0)
|
||||
volatility = analysis.get('volatility_annualized', 0)
|
||||
tool_results.append(f"10-Day Analysis: Return {return_pct:.2f}%, Volatility {volatility:.1f}%")
|
||||
else:
|
||||
tool_results.append("10-Day Analysis: Data unavailable")
|
||||
|
||||
elif function_name == "get_medium_term_analysis":
|
||||
analysis = data_service.get_analysis(symbol, "1mo")
|
||||
if 'error' not in analysis:
|
||||
return_pct = analysis.get('total_return_pct', 0)
|
||||
trend = analysis.get('trend_direction', 'neutral')
|
||||
tool_results.append(f"30-Day Analysis: Return {return_pct:.2f}%, Trend {trend}")
|
||||
else:
|
||||
tool_results.append("30-Day Analysis: Data unavailable")
|
||||
|
||||
elif function_name == "get_long_term_analysis":
|
||||
analysis = data_service.get_analysis(symbol, "3mo")
|
||||
if 'error' not in analysis:
|
||||
return_pct = analysis.get('total_return_pct', 0)
|
||||
sharpe = analysis.get('sharpe_ratio', 0)
|
||||
tool_results.append(f"90-Day Analysis: Return {return_pct:.2f}%, Sharpe {sharpe:.2f}")
|
||||
else:
|
||||
tool_results.append("90-Day Analysis: Data unavailable")
|
||||
|
||||
elif function_name == "get_comprehensive_analysis":
|
||||
analysis = data_service.get_analysis(symbol, "1y")
|
||||
if 'error' not in analysis:
|
||||
return_pct = analysis.get('total_return_pct', 0)
|
||||
max_drawdown = analysis.get('max_drawdown', 0)
|
||||
rsi = analysis.get('rsi', 50)
|
||||
tool_results.append(f"1-Year Analysis: Return {return_pct:.2f}%, Max Drawdown {max_drawdown:.1f}%, RSI {rsi:.1f}")
|
||||
else:
|
||||
tool_results.append("1-Year Analysis: Data unavailable")
|
||||
|
||||
elif function_name == "get_trading_recommendation":
|
||||
trading = data_service.get_trading_recommendation(symbol, country)
|
||||
if 'error' not in trading:
|
||||
rec = trading.get('recommendation', 'HOLD')
|
||||
conf = trading.get('confidence', 0.5) * 100
|
||||
tool_results.append(f"Trading: {rec} ({conf:.0f}% confidence)")
|
||||
else:
|
||||
tool_results.append("Trading: Analysis unavailable")
|
||||
|
||||
elif function_name == "get_sharia_compliance":
|
||||
sharia = data_service.get_sharia_compliance(symbol, country)
|
||||
if 'error' not in sharia:
|
||||
ruling = sharia.get('ruling', 'UNCERTAIN')
|
||||
conf = sharia.get('confidence', 0.5) * 100
|
||||
tool_results.append(f"Sharia: {ruling} ({conf:.0f}% confidence)")
|
||||
else:
|
||||
tool_results.append("Sharia: Analysis unavailable")
|
||||
|
||||
elif function_name == "compare_time_periods":
|
||||
periods = ["10d", "1mo", "3mo"]
|
||||
comparisons = []
|
||||
for period in periods:
|
||||
analysis = data_service.get_analysis(symbol, period)
|
||||
if 'error' not in analysis:
|
||||
return_pct = analysis.get('total_return_pct', 0)
|
||||
comparisons.append(f"{period}: {return_pct:.2f}%")
|
||||
tool_results.append(f"Period Comparison: {', '.join(comparisons)}")
|
||||
|
||||
except Exception as e:
|
||||
tool_results.append(f"{function_name}: Error - {str(e)}")
|
||||
|
||||
# Generate final response
|
||||
final_response = self.client.chat.completions.create(
|
||||
model="gpt-4o-mini",
|
||||
messages=[
|
||||
{"role": "system", "content": "Provide a concise, professional response based on the tool results. Focus on actionable insights."},
|
||||
{"role": "user", "content": f"Question: {user_input}\n\nTool Results: {' | '.join(tool_results)}"}
|
||||
],
|
||||
temperature=0.7,
|
||||
max_tokens=500
|
||||
)
|
||||
|
||||
return final_response.choices[0].message.content or "I couldn't generate a response."
|
||||
|
||||
|
||||
# Global instance
|
||||
ai_assistant = AIAssistant()
|
||||
@@ -0,0 +1,119 @@
|
||||
import streamlit as st
|
||||
from typing import Dict, Any, Optional
|
||||
from tools.fetching import stock_fetcher
|
||||
from tools.analysis import stock_analyzer
|
||||
from tools.trading_decisions import trading_engine
|
||||
from tools.sharia_compliance import sharia_checker
|
||||
|
||||
|
||||
class DataService:
|
||||
"""Centralized data service for efficient stock data management"""
|
||||
|
||||
@staticmethod
|
||||
def get_basic_info(symbol: str, country: str) -> Dict[str, Any]:
|
||||
"""Get only basic stock info - no heavy analysis"""
|
||||
cache_key = f"{symbol}_basic"
|
||||
|
||||
if cache_key not in st.session_state:
|
||||
try:
|
||||
stock_info = stock_fetcher.get_stock_info(symbol, country)
|
||||
st.session_state[cache_key] = stock_info
|
||||
except Exception as e:
|
||||
st.session_state[cache_key] = {
|
||||
'company_name': symbol,
|
||||
'error': str(e)
|
||||
}
|
||||
|
||||
return st.session_state[cache_key]
|
||||
|
||||
@staticmethod
|
||||
def get_price_data(symbol: str, period: str = "1y") -> Dict[str, Any]:
|
||||
"""Get price data for specific period"""
|
||||
cache_key = f"{symbol}_data_{period}"
|
||||
|
||||
if cache_key not in st.session_state:
|
||||
try:
|
||||
data = stock_fetcher.fetch_stock_data(symbol, period=period)
|
||||
st.session_state[cache_key] = data
|
||||
except Exception as e:
|
||||
st.session_state[cache_key] = None
|
||||
st.error(f"Failed to load {period} data: {str(e)}")
|
||||
|
||||
return st.session_state[cache_key]
|
||||
|
||||
@staticmethod
|
||||
def get_analysis(symbol: str, period: str = "1y") -> Dict[str, Any]:
|
||||
"""Get technical analysis for specific period"""
|
||||
cache_key = f"{symbol}_analysis_{period}"
|
||||
|
||||
if cache_key not in st.session_state:
|
||||
data = DataService.get_price_data(symbol, period)
|
||||
if data is not None and hasattr(data, 'empty') and not data.empty:
|
||||
try:
|
||||
analysis = stock_analyzer.analyze_stock(data)
|
||||
analysis['period'] = period
|
||||
st.session_state[cache_key] = analysis
|
||||
except Exception as e:
|
||||
st.session_state[cache_key] = {'error': f"Analysis failed: {str(e)}"}
|
||||
else:
|
||||
st.session_state[cache_key] = {'error': 'No data available'}
|
||||
|
||||
return st.session_state[cache_key]
|
||||
|
||||
@staticmethod
|
||||
def get_trading_recommendation(symbol: str, country: str) -> Dict[str, Any]:
|
||||
"""Get trading recommendation"""
|
||||
cache_key = f"{symbol}_trading"
|
||||
|
||||
if cache_key not in st.session_state:
|
||||
try:
|
||||
analysis = DataService.get_analysis(symbol)
|
||||
stock_info = DataService.get_basic_info(symbol, country)
|
||||
|
||||
if 'error' not in analysis and 'error' not in stock_info:
|
||||
trading = trading_engine.get_trading_recommendation(symbol, analysis, stock_info)
|
||||
st.session_state[cache_key] = trading
|
||||
else:
|
||||
st.session_state[cache_key] = {'error': 'Cannot generate recommendation'}
|
||||
except Exception as e:
|
||||
st.session_state[cache_key] = {'error': f"Trading analysis failed: {str(e)}"}
|
||||
|
||||
return st.session_state[cache_key]
|
||||
|
||||
@staticmethod
|
||||
def get_sharia_compliance(symbol: str, country: str) -> Dict[str, Any]:
|
||||
"""Get Sharia compliance analysis"""
|
||||
cache_key = f"{symbol}_sharia"
|
||||
|
||||
if cache_key not in st.session_state:
|
||||
try:
|
||||
stock_info = DataService.get_basic_info(symbol, country)
|
||||
analysis = DataService.get_analysis(symbol)
|
||||
|
||||
if 'error' not in stock_info:
|
||||
sharia = sharia_checker.check_sharia_compliance(symbol, stock_info, analysis)
|
||||
st.session_state[cache_key] = sharia
|
||||
else:
|
||||
st.session_state[cache_key] = {'error': 'Cannot check compliance'}
|
||||
except Exception as e:
|
||||
st.session_state[cache_key] = {'error': f"Sharia check failed: {str(e)}"}
|
||||
|
||||
return st.session_state[cache_key]
|
||||
|
||||
@staticmethod
|
||||
def clear_cache(symbol: Optional[str] = None):
|
||||
"""Clear cached data"""
|
||||
if symbol:
|
||||
keys_to_remove = [key for key in st.session_state.keys() if isinstance(key, str) and key.startswith(f"{symbol}_")]
|
||||
for key in keys_to_remove:
|
||||
del st.session_state[key]
|
||||
else:
|
||||
# Clear all cache
|
||||
keys_to_remove = [key for key in st.session_state.keys()
|
||||
if isinstance(key, str) and ('_data_' in key or '_analysis_' in key or '_trading' in key or '_sharia' in key or '_basic' in key)]
|
||||
for key in keys_to_remove:
|
||||
del st.session_state[key]
|
||||
|
||||
|
||||
# Global instance
|
||||
data_service = DataService()
|
||||
332
week4/community-contributions/ai_stock_trading/main_app.py
Normal file
332
week4/community-contributions/ai_stock_trading/main_app.py
Normal file
@@ -0,0 +1,332 @@
|
||||
"""
|
||||
Main Streamlit Application for AI Stock Trading with Sharia Compliance
|
||||
"""
|
||||
|
||||
import streamlit as st
|
||||
import pandas as pd
|
||||
import os
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# Import our custom tools
|
||||
from tools.fetching import stock_fetcher, get_available_stocks
|
||||
from tools.analysis import stock_analyzer
|
||||
from tools.trading_decisions import trading_engine
|
||||
from tools.sharia_compliance import sharia_checker
|
||||
from tools.charting import chart_generator
|
||||
|
||||
# Import new modular components
|
||||
from core.data_service import data_service
|
||||
from core.ai_assistant import ai_assistant
|
||||
from components.chat_interface import ChatInterface
|
||||
|
||||
# Load environment variables
|
||||
load_dotenv()
|
||||
|
||||
# Page configuration
|
||||
st.set_page_config(
|
||||
page_title="AI Stock Trading & Sharia Compliance",
|
||||
page_icon="📈",
|
||||
layout="wide",
|
||||
initial_sidebar_state="expanded"
|
||||
)
|
||||
|
||||
class StockTradingApp:
|
||||
def __init__(self):
|
||||
self.initialize_session_state()
|
||||
self.setup_sidebar()
|
||||
|
||||
def initialize_session_state(self):
|
||||
if 'selected_country' not in st.session_state:
|
||||
st.session_state.selected_country = 'USA'
|
||||
if 'selected_stock' not in st.session_state:
|
||||
st.session_state.selected_stock = None
|
||||
if 'stock_data' not in st.session_state:
|
||||
st.session_state.stock_data = {}
|
||||
if 'chat_history' not in st.session_state:
|
||||
st.session_state.chat_history = []
|
||||
if 'current_page' not in st.session_state:
|
||||
st.session_state.current_page = 'home'
|
||||
|
||||
def setup_sidebar(self):
|
||||
with st.sidebar:
|
||||
st.title("🏛️ Navigation")
|
||||
|
||||
page = st.radio(
|
||||
"Select Page:",
|
||||
["🏠 Home", "💬 Chat Interface", "📊 Dashboard"],
|
||||
key="page_selector"
|
||||
)
|
||||
|
||||
page_mapping = {
|
||||
"🏠 Home": "home",
|
||||
"💬 Chat Interface": "chat",
|
||||
"📊 Dashboard": "dashboard"
|
||||
}
|
||||
st.session_state.current_page = page_mapping[page]
|
||||
|
||||
st.divider()
|
||||
self.render_stock_selector()
|
||||
st.divider()
|
||||
self.show_api_status()
|
||||
|
||||
def render_stock_selector(self):
|
||||
st.subheader("🌍 Stock Selection")
|
||||
|
||||
countries = ['USA', 'Egypt']
|
||||
selected_country = st.selectbox(
|
||||
"Select Country:",
|
||||
countries,
|
||||
index=countries.index(st.session_state.selected_country),
|
||||
key="country_selector"
|
||||
)
|
||||
|
||||
if selected_country != st.session_state.selected_country:
|
||||
st.session_state.selected_country = selected_country
|
||||
st.session_state.selected_stock = None
|
||||
|
||||
available_stocks = get_available_stocks(selected_country)
|
||||
|
||||
if available_stocks:
|
||||
stock_names = list(available_stocks.keys())
|
||||
current_index = 0
|
||||
|
||||
if st.session_state.selected_stock:
|
||||
current_symbol = st.session_state.selected_stock
|
||||
for i, (name, symbol) in enumerate(available_stocks.items()):
|
||||
if symbol == current_symbol:
|
||||
current_index = i
|
||||
break
|
||||
|
||||
selected_stock_name = st.selectbox(
|
||||
"Select Stock:",
|
||||
stock_names,
|
||||
index=current_index,
|
||||
key="stock_selector"
|
||||
)
|
||||
|
||||
selected_symbol = available_stocks[selected_stock_name]
|
||||
|
||||
if selected_symbol != st.session_state.selected_stock:
|
||||
st.session_state.selected_stock = selected_symbol
|
||||
st.session_state.stock_data = {}
|
||||
st.session_state.chat_history = []
|
||||
|
||||
if st.session_state.selected_stock:
|
||||
st.success(f"Selected: {selected_stock_name} ({selected_symbol})")
|
||||
else:
|
||||
st.error(f"No stocks available for {selected_country}")
|
||||
|
||||
def show_api_status(self):
|
||||
st.subheader("API Used")
|
||||
openai_key = os.getenv('OPENAI_API_KEY')
|
||||
if openai_key:
|
||||
st.success("✅ OpenAI Connected")
|
||||
else:
|
||||
st.error("❌ Not Connected")
|
||||
|
||||
def run(self):
|
||||
st.title("🤖 AI Stock Trading")
|
||||
st.markdown("*Intelligent stock analysis with Islamic finance compliance*")
|
||||
|
||||
if st.session_state.current_page == 'home':
|
||||
self.render_home_page()
|
||||
elif st.session_state.current_page == 'chat':
|
||||
self.render_chat_page()
|
||||
elif st.session_state.current_page == 'dashboard':
|
||||
self.render_dashboard_page()
|
||||
|
||||
def render_home_page(self):
|
||||
st.header("🏠 Welcome to AI Stock Trading Platform")
|
||||
|
||||
st.markdown("""
|
||||
Get intelligent stock analysis with Islamic finance compliance checking.
|
||||
Select a country and stock from the sidebar to begin.
|
||||
|
||||
**Key Features:**
|
||||
- 📊 Real-time stock analysis with advanced indicators
|
||||
- 🤖 AI-powered trading recommendations
|
||||
- ☪️ Sharia compliance assessment
|
||||
- 💬 Natural language chat interface
|
||||
|
||||
**Supported Markets:** 🇺🇸 USA | 🇪🇬 Egypt
|
||||
|
||||
*Disclaimer: For educational purposes only. Not financial advice.*
|
||||
""")
|
||||
|
||||
if st.session_state.selected_stock:
|
||||
st.divider()
|
||||
st.subheader(f"📊 Quick Analysis: {st.session_state.selected_stock}")
|
||||
with st.spinner("Loading quick analysis..."):
|
||||
self.show_quick_analysis()
|
||||
|
||||
def show_quick_analysis(self):
|
||||
symbol = st.session_state.selected_stock
|
||||
country = st.session_state.selected_country
|
||||
try:
|
||||
data = stock_fetcher.fetch_stock_data(symbol, period="1mo")
|
||||
stock_info = stock_fetcher.get_stock_info(symbol, country)
|
||||
|
||||
if not data.empty:
|
||||
col1, col2, col3, col4 = st.columns(4)
|
||||
|
||||
current_price = data['Close'].iloc[-1]
|
||||
price_change = data['Close'].iloc[-1] - data['Close'].iloc[-2] if len(data) > 1 else 0
|
||||
price_change_pct = (price_change / data['Close'].iloc[-2] * 100) if len(data) > 1 else 0
|
||||
|
||||
with col1:
|
||||
formatted_price = stock_fetcher.format_price_with_currency(current_price, country)
|
||||
price_change_str = f"{price_change:+.2f} ({price_change_pct:+.1f}%)"
|
||||
st.metric("Current Price", formatted_price, price_change_str)
|
||||
|
||||
with col2:
|
||||
high_52w = stock_info.get('fifty_two_week_high', 0)
|
||||
formatted_high = stock_fetcher.format_price_with_currency(high_52w, country)
|
||||
st.metric("52W High", formatted_high)
|
||||
|
||||
with col3:
|
||||
low_52w = stock_info.get('fifty_two_week_low', 0)
|
||||
formatted_low = stock_fetcher.format_price_with_currency(low_52w, country)
|
||||
st.metric("52W Low", formatted_low)
|
||||
|
||||
with col4:
|
||||
market_cap = stock_info.get('market_cap', 0)
|
||||
currency = stock_fetcher.get_market_currency(country)
|
||||
if market_cap > 1e9:
|
||||
if currency == 'EGP':
|
||||
market_cap_str = f"{market_cap/1e9:.1f}B EGP"
|
||||
else:
|
||||
market_cap_str = f"${market_cap/1e9:.1f}B"
|
||||
elif market_cap > 1e6:
|
||||
if currency == 'EGP':
|
||||
market_cap_str = f"{market_cap/1e6:.1f}M EGP"
|
||||
else:
|
||||
market_cap_str = f"${market_cap/1e6:.1f}M"
|
||||
else:
|
||||
if currency == 'EGP':
|
||||
market_cap_str = f"{market_cap:,.0f} EGP"
|
||||
else:
|
||||
market_cap_str = f"${market_cap:,.0f}"
|
||||
st.metric("Market Cap", market_cap_str)
|
||||
|
||||
st.info(f"**{stock_info.get('company_name', 'N/A')}** | "
|
||||
f"Sector: {stock_info.get('sector', 'N/A')} | "
|
||||
f"Industry: {stock_info.get('industry', 'N/A')}")
|
||||
|
||||
except Exception as e:
|
||||
st.error(f"Error loading quick analysis: {str(e)}")
|
||||
|
||||
def load_stock_analysis(self, symbol: str):
|
||||
"""Load complete analysis using data service"""
|
||||
country = st.session_state.selected_country
|
||||
# Pre-load all analysis components
|
||||
data_service.get_analysis(symbol)
|
||||
data_service.get_trading_recommendation(symbol, country)
|
||||
data_service.get_sharia_compliance(symbol, country)
|
||||
|
||||
def render_chat_page(self):
|
||||
st.header("💬 AI Stock Analysis Chat")
|
||||
|
||||
symbol = st.session_state.selected_stock
|
||||
country = st.session_state.selected_country
|
||||
|
||||
ChatInterface.render(symbol, country)
|
||||
|
||||
def render_dashboard_page(self):
|
||||
st.header("📊 Dashboard")
|
||||
|
||||
if not st.session_state.selected_stock:
|
||||
st.warning("⚠️ Please select a stock from the sidebar.")
|
||||
return
|
||||
|
||||
symbol = st.session_state.selected_stock
|
||||
country = st.session_state.selected_country
|
||||
|
||||
# Load data using new data service
|
||||
with st.spinner("Loading dashboard data..."):
|
||||
basic_info = data_service.get_basic_info(symbol, country)
|
||||
data = data_service.get_price_data(symbol, "1y")
|
||||
analysis = data_service.get_analysis(symbol, "1y")
|
||||
trading_decision = data_service.get_trading_recommendation(symbol, country)
|
||||
sharia_compliance = data_service.get_sharia_compliance(symbol, country)
|
||||
|
||||
# Check if data loaded successfully
|
||||
if data is None or analysis.get('error') or trading_decision.get('error'):
|
||||
st.error("Failed to load dashboard data. Please try again.")
|
||||
return
|
||||
|
||||
# KPIs at the top
|
||||
col1, col2, col3, col4, col5 = st.columns(5)
|
||||
|
||||
with col1:
|
||||
if data is not None and hasattr(data, 'iloc') and len(data) > 0:
|
||||
current_price = data['Close'].iloc[-1]
|
||||
formatted_price = stock_fetcher.format_price_with_currency(current_price, country)
|
||||
st.metric("💰 Current Price", formatted_price)
|
||||
else:
|
||||
st.metric("💰 Current Price", "N/A")
|
||||
|
||||
with col2:
|
||||
total_return = analysis.get('total_return_pct', 0)
|
||||
st.metric("Total Return", f"{total_return:.2f}%")
|
||||
|
||||
with col3:
|
||||
rec = trading_decision.get('recommendation', 'HOLD')
|
||||
conf = trading_decision.get('confidence', 0.5)
|
||||
if conf <= 1.0:
|
||||
conf_pct = conf * 100
|
||||
else:
|
||||
conf_pct = conf
|
||||
st.metric("Recommendation", rec, f"{conf_pct:.0f}% confidence")
|
||||
|
||||
with col4:
|
||||
ruling = sharia_compliance.get('ruling', 'UNCERTAIN')
|
||||
sharia_conf = sharia_compliance.get('confidence', 0.5)
|
||||
if sharia_conf <= 1.0:
|
||||
sharia_conf_pct = sharia_conf * 100
|
||||
else:
|
||||
sharia_conf_pct = sharia_conf
|
||||
st.metric("Sharia Status", ruling, f"{sharia_conf_pct:.0f}% confidence")
|
||||
|
||||
with col5:
|
||||
volatility = analysis.get('volatility_annualized', 0)
|
||||
st.metric("Volatility", f"{volatility:.1f}%")
|
||||
|
||||
|
||||
# Charts section (only if data is available)
|
||||
if data is not None and hasattr(data, 'iloc') and len(data) > 0:
|
||||
st.divider()
|
||||
|
||||
# First row: Risk Analysis and Trading Signals
|
||||
col1, col2 = st.columns(2)
|
||||
|
||||
with col1:
|
||||
try:
|
||||
risk_fig = chart_generator.create_risk_analysis_chart(analysis, symbol)
|
||||
st.plotly_chart(risk_fig, use_container_width=True)
|
||||
except Exception as e:
|
||||
st.error(f"Risk chart error: {str(e)}")
|
||||
|
||||
with col2:
|
||||
try:
|
||||
signals_fig = chart_generator.create_trading_signals_chart(data, analysis, trading_decision, symbol)
|
||||
st.plotly_chart(signals_fig, use_container_width=True)
|
||||
except Exception as e:
|
||||
st.error(f"Signals chart error: {str(e)}")
|
||||
|
||||
# Second row: Price Chart (full width)
|
||||
try:
|
||||
price_fig = chart_generator.create_price_chart(data, symbol, analysis)
|
||||
st.plotly_chart(price_fig, use_container_width=True)
|
||||
except Exception as e:
|
||||
st.error(f"Price chart error: {str(e)}")
|
||||
else:
|
||||
st.warning("📊 Charts unavailable - no price data loaded.")
|
||||
|
||||
|
||||
|
||||
def main():
|
||||
app = StockTradingApp()
|
||||
app.run()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,10 @@
|
||||
yfinance>=0.2.10
|
||||
openai>=1.0.0
|
||||
pandas>=1.5.0
|
||||
matplotlib>=3.5.0
|
||||
streamlit>=1.28.0
|
||||
requests>=2.28.0
|
||||
beautifulsoup4>=4.11.0
|
||||
numpy>=1.21.0
|
||||
python-dotenv>=1.0.0
|
||||
plotly>=5.15.0
|
||||
@@ -0,0 +1,28 @@
|
||||
"""
|
||||
AI Stock Trading Tools
|
||||
|
||||
This package contains all the core tools for the AI Stock Trading platform:
|
||||
- fetching: Stock data fetching and market data
|
||||
- analysis: Technical analysis and stock metrics
|
||||
- trading_decisions: AI-powered trading recommendations
|
||||
- sharia_compliance: Islamic finance compliance checking
|
||||
- charting: Interactive charts and visualizations
|
||||
"""
|
||||
|
||||
__version__ = "1.0.0"
|
||||
__author__ = "AI Stock Trading Platform"
|
||||
|
||||
# Import main classes and functions for easy access
|
||||
from .fetching import StockDataFetcher, stock_fetcher, fetch_stock_data, get_available_stocks
|
||||
from .analysis import StockAnalyzer, stock_analyzer, analyze_stock
|
||||
from .trading_decisions import TradingDecisionEngine, trading_engine, get_trading_recommendation
|
||||
from .sharia_compliance import ShariaComplianceChecker, sharia_checker, check_sharia_compliance
|
||||
from .charting import StockChartGenerator, chart_generator, create_price_chart
|
||||
|
||||
__all__ = [
|
||||
'StockDataFetcher', 'stock_fetcher', 'fetch_stock_data', 'get_available_stocks',
|
||||
'StockAnalyzer', 'stock_analyzer', 'analyze_stock',
|
||||
'TradingDecisionEngine', 'trading_engine', 'get_trading_recommendation',
|
||||
'ShariaComplianceChecker', 'sharia_checker', 'check_sharia_compliance',
|
||||
'StockChartGenerator', 'chart_generator', 'create_price_chart'
|
||||
]
|
||||
316
week4/community-contributions/ai_stock_trading/tools/analysis.py
Normal file
316
week4/community-contributions/ai_stock_trading/tools/analysis.py
Normal file
@@ -0,0 +1,316 @@
|
||||
"""
|
||||
Stock Analysis Module
|
||||
|
||||
This module provides enhanced technical and fundamental analysis capabilities
|
||||
for stock data with advanced metrics and indicators.
|
||||
"""
|
||||
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
from typing import Dict, List, Optional, Tuple, Union, Any
|
||||
import warnings
|
||||
|
||||
warnings.filterwarnings('ignore')
|
||||
|
||||
class StockAnalyzer:
|
||||
"""Enhanced stock analyzer with comprehensive technical indicators"""
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def analyze_stock(self, data: pd.DataFrame) -> Dict:
|
||||
"""
|
||||
Comprehensive stock analysis with enhanced metrics
|
||||
|
||||
Args:
|
||||
data: DataFrame with OHLCV stock data
|
||||
|
||||
Returns:
|
||||
Dictionary with analysis results
|
||||
"""
|
||||
if data.empty:
|
||||
return {'error': 'No data provided for analysis'}
|
||||
|
||||
try:
|
||||
analysis = {}
|
||||
|
||||
# Basic price metrics
|
||||
analysis.update(self._calculate_price_metrics(data))
|
||||
|
||||
# Technical indicators
|
||||
analysis.update(self._calculate_technical_indicators(data))
|
||||
|
||||
# Volatility analysis
|
||||
analysis.update(self._calculate_volatility_metrics(data))
|
||||
|
||||
# Volume analysis
|
||||
analysis.update(self._calculate_volume_metrics(data))
|
||||
|
||||
# Trend analysis
|
||||
analysis.update(self._calculate_trend_metrics(data))
|
||||
|
||||
# Risk metrics
|
||||
analysis.update(self._calculate_risk_metrics(data))
|
||||
|
||||
# Performance metrics
|
||||
analysis.update(self._calculate_performance_metrics(data))
|
||||
|
||||
return analysis
|
||||
|
||||
except Exception as e:
|
||||
return {'error': f'Analysis failed: {str(e)}'}
|
||||
|
||||
def _calculate_price_metrics(self, data: pd.DataFrame) -> Dict:
|
||||
"""Calculate basic price metrics"""
|
||||
close_prices = data['Close']
|
||||
|
||||
return {
|
||||
'current_price': float(close_prices.iloc[-1]),
|
||||
'start_price': float(close_prices.iloc[0]),
|
||||
'max_price': float(close_prices.max()),
|
||||
'min_price': float(close_prices.min()),
|
||||
'price_range_pct': float(((close_prices.max() - close_prices.min()) / close_prices.min()) * 100),
|
||||
'total_return_pct': float(((close_prices.iloc[-1] - close_prices.iloc[0]) / close_prices.iloc[0]) * 100)
|
||||
}
|
||||
|
||||
def _calculate_technical_indicators(self, data: pd.DataFrame) -> Dict:
|
||||
"""Calculate technical indicators"""
|
||||
close_prices = data['Close']
|
||||
high_prices = data['High']
|
||||
low_prices = data['Low']
|
||||
|
||||
indicators = {}
|
||||
|
||||
# Moving averages
|
||||
if len(data) >= 20:
|
||||
sma_20 = close_prices.rolling(window=20).mean()
|
||||
indicators['sma_20'] = float(sma_20.iloc[-1])
|
||||
indicators['price_vs_sma_20'] = float(((close_prices.iloc[-1] - sma_20.iloc[-1]) / sma_20.iloc[-1]) * 100)
|
||||
|
||||
if len(data) >= 50:
|
||||
sma_50 = close_prices.rolling(window=50).mean()
|
||||
indicators['sma_50'] = float(sma_50.iloc[-1])
|
||||
indicators['price_vs_sma_50'] = float(((close_prices.iloc[-1] - sma_50.iloc[-1]) / sma_50.iloc[-1]) * 100)
|
||||
|
||||
# Exponential Moving Average
|
||||
if len(data) >= 12:
|
||||
ema_12 = close_prices.ewm(span=12).mean()
|
||||
indicators['ema_12'] = float(ema_12.iloc[-1])
|
||||
|
||||
# RSI (Relative Strength Index)
|
||||
if len(data) >= 14:
|
||||
rsi = self._calculate_rsi(pd.Series(close_prices), 14)
|
||||
indicators['rsi'] = float(rsi.iloc[-1])
|
||||
indicators['rsi_signal'] = self._interpret_rsi(float(rsi.iloc[-1]))
|
||||
|
||||
# MACD
|
||||
if len(data) >= 26:
|
||||
macd_line, signal_line, histogram = self._calculate_macd(pd.Series(close_prices))
|
||||
indicators['macd'] = float(macd_line.iloc[-1])
|
||||
indicators['macd_signal'] = float(signal_line.iloc[-1])
|
||||
indicators['macd_histogram'] = float(histogram.iloc[-1])
|
||||
indicators['macd_trend'] = 'bullish' if float(histogram.iloc[-1]) > 0 else 'bearish'
|
||||
|
||||
# Bollinger Bands
|
||||
if len(data) >= 20:
|
||||
bb_upper, bb_middle, bb_lower = self._calculate_bollinger_bands(pd.Series(close_prices), 20, 2)
|
||||
indicators['bb_upper'] = float(bb_upper.iloc[-1])
|
||||
indicators['bb_middle'] = float(bb_middle.iloc[-1])
|
||||
indicators['bb_lower'] = float(bb_lower.iloc[-1])
|
||||
indicators['bb_position'] = self._interpret_bollinger_position(float(close_prices.iloc[-1]), float(bb_upper.iloc[-1]), float(bb_lower.iloc[-1]))
|
||||
|
||||
return indicators
|
||||
|
||||
def _calculate_volatility_metrics(self, data: pd.DataFrame) -> Dict:
|
||||
"""Calculate volatility metrics"""
|
||||
close_prices = data['Close']
|
||||
daily_returns = close_prices.pct_change().dropna()
|
||||
|
||||
return {
|
||||
'volatility_daily': float(daily_returns.std() * 100),
|
||||
'volatility_annualized': float(daily_returns.std() * np.sqrt(252) * 100),
|
||||
'avg_daily_return': float(daily_returns.mean() * 100),
|
||||
'max_daily_gain': float(daily_returns.max() * 100),
|
||||
'max_daily_loss': float(daily_returns.min() * 100)
|
||||
}
|
||||
|
||||
def _calculate_volume_metrics(self, data: pd.DataFrame) -> Dict:
|
||||
"""Calculate volume metrics"""
|
||||
volume = data['Volume']
|
||||
|
||||
metrics: Dict[str, Union[float, str]] = {
|
||||
'avg_volume': float(volume.mean()),
|
||||
'current_volume': float(volume.iloc[-1]),
|
||||
'max_volume': float(volume.max()),
|
||||
'min_volume': float(volume.min())
|
||||
}
|
||||
|
||||
# Volume trend
|
||||
if len(volume) >= 10:
|
||||
recent_avg = volume.tail(10).mean()
|
||||
overall_avg = volume.mean()
|
||||
if recent_avg > overall_avg:
|
||||
metrics['volume_trend'] = 'increasing'
|
||||
else:
|
||||
metrics['volume_trend'] = 'decreasing'
|
||||
metrics['volume_vs_avg'] = float(((recent_avg - overall_avg) / overall_avg) * 100)
|
||||
|
||||
return metrics
|
||||
|
||||
def _calculate_trend_metrics(self, data: pd.DataFrame) -> Dict:
|
||||
"""Calculate trend analysis metrics"""
|
||||
close_prices = data['Close']
|
||||
|
||||
# Linear regression for trend
|
||||
x = np.arange(len(close_prices))
|
||||
slope, intercept = np.polyfit(x, close_prices, 1)
|
||||
|
||||
# Trend strength
|
||||
correlation = np.corrcoef(x, close_prices)[0, 1]
|
||||
|
||||
return {
|
||||
'trend_slope': float(slope),
|
||||
'trend_direction': 'upward' if slope > 0 else 'downward',
|
||||
'trend_strength': float(abs(correlation)),
|
||||
'trend_angle': float(np.degrees(np.arctan(slope))),
|
||||
'r_squared': float(correlation ** 2)
|
||||
}
|
||||
|
||||
def _calculate_risk_metrics(self, data: pd.DataFrame) -> Dict:
|
||||
"""Calculate risk metrics"""
|
||||
close_prices = data['Close']
|
||||
daily_returns = close_prices.pct_change().dropna()
|
||||
|
||||
# Value at Risk (VaR)
|
||||
var_95 = np.percentile(daily_returns, 5)
|
||||
var_99 = np.percentile(daily_returns, 1)
|
||||
|
||||
# Maximum Drawdown
|
||||
cumulative_returns = (1 + daily_returns).cumprod()
|
||||
running_max = cumulative_returns.expanding().max()
|
||||
drawdown = (cumulative_returns - running_max) / running_max
|
||||
max_drawdown = drawdown.min()
|
||||
|
||||
# Sharpe Ratio (assuming risk-free rate of 2%)
|
||||
risk_free_rate = 0.02 / 252 # Daily risk-free rate
|
||||
excess_returns = daily_returns - risk_free_rate
|
||||
sharpe_ratio = excess_returns.mean() / daily_returns.std() if daily_returns.std() != 0 else 0
|
||||
|
||||
return {
|
||||
'var_95': float(var_95 * 100),
|
||||
'var_99': float(var_99 * 100),
|
||||
'max_drawdown': float(max_drawdown * 100),
|
||||
'sharpe_ratio': float(sharpe_ratio * np.sqrt(252)), # Annualized
|
||||
'downside_deviation': float(daily_returns[daily_returns < 0].std() * 100)
|
||||
}
|
||||
|
||||
def _calculate_performance_metrics(self, data: pd.DataFrame) -> Dict:
|
||||
"""Calculate performance metrics"""
|
||||
close_prices = data['Close']
|
||||
|
||||
# Different period returns
|
||||
periods = {
|
||||
'1_week': min(5, len(close_prices) - 1),
|
||||
'1_month': min(22, len(close_prices) - 1),
|
||||
'3_months': min(66, len(close_prices) - 1),
|
||||
'6_months': min(132, len(close_prices) - 1)
|
||||
}
|
||||
|
||||
performance = {}
|
||||
current_price = close_prices.iloc[-1]
|
||||
|
||||
for period_name, days_back in periods.items():
|
||||
if days_back > 0:
|
||||
past_price = close_prices.iloc[-(days_back + 1)]
|
||||
return_pct = ((current_price - past_price) / past_price) * 100
|
||||
performance[f'return_{period_name}'] = float(return_pct)
|
||||
|
||||
return performance
|
||||
|
||||
def _calculate_rsi(self, prices: pd.Series, period: int = 14) -> pd.Series:
|
||||
"""Calculate Relative Strength Index"""
|
||||
delta = prices.diff()
|
||||
gain = (delta.where(delta > 0, 0)).rolling(window=period).mean()
|
||||
loss = (-delta.where(delta < 0, 0)).rolling(window=period).mean()
|
||||
rs = gain / loss
|
||||
rsi = 100 - (100 / (1 + rs))
|
||||
return rsi
|
||||
|
||||
def _interpret_rsi(self, rsi_value: float) -> str:
|
||||
"""Interpret RSI value"""
|
||||
if rsi_value >= 70:
|
||||
return 'overbought'
|
||||
elif rsi_value <= 30:
|
||||
return 'oversold'
|
||||
else:
|
||||
return 'neutral'
|
||||
|
||||
def _calculate_macd(self, prices: pd.Series, fast: int = 12, slow: int = 26, signal: int = 9) -> Tuple[pd.Series, pd.Series, pd.Series]:
|
||||
"""Calculate MACD indicator"""
|
||||
ema_fast = prices.ewm(span=fast).mean()
|
||||
ema_slow = prices.ewm(span=slow).mean()
|
||||
macd_line = ema_fast - ema_slow
|
||||
signal_line = macd_line.ewm(span=signal).mean()
|
||||
histogram = macd_line - signal_line
|
||||
return macd_line, signal_line, histogram
|
||||
|
||||
def _calculate_bollinger_bands(self, prices: pd.Series, period: int = 20, std_dev: int = 2) -> Tuple[pd.Series, pd.Series, pd.Series]:
|
||||
"""Calculate Bollinger Bands"""
|
||||
sma = prices.rolling(window=period).mean()
|
||||
std = prices.rolling(window=period).std()
|
||||
upper_band = sma + (std * std_dev)
|
||||
lower_band = sma - (std * std_dev)
|
||||
return upper_band, sma, lower_band
|
||||
|
||||
def _interpret_bollinger_position(self, current_price: float, upper_band: float, lower_band: float) -> str:
|
||||
"""Interpret position relative to Bollinger Bands"""
|
||||
if current_price > upper_band:
|
||||
return 'above_upper_band'
|
||||
elif current_price < lower_band:
|
||||
return 'below_lower_band'
|
||||
else:
|
||||
return 'within_bands'
|
||||
|
||||
def get_analysis_summary(self, analysis: Dict) -> str:
|
||||
"""Generate a human-readable analysis summary"""
|
||||
if 'error' in analysis:
|
||||
return f"Analysis Error: {analysis['error']}"
|
||||
|
||||
summary = []
|
||||
|
||||
# Price summary
|
||||
current_price = analysis.get('current_price', 0)
|
||||
total_return = analysis.get('total_return_pct', 0)
|
||||
summary.append(f"Current Price: ${current_price:.2f}")
|
||||
summary.append(f"Total Return: {total_return:.2f}%")
|
||||
|
||||
# Trend
|
||||
trend_direction = analysis.get('trend_direction', 'unknown')
|
||||
trend_strength = analysis.get('trend_strength', 0)
|
||||
summary.append(f"Trend: {trend_direction.title()} (Strength: {trend_strength:.2f})")
|
||||
|
||||
# Technical indicators
|
||||
if 'rsi' in analysis:
|
||||
rsi = analysis['rsi']
|
||||
rsi_signal = analysis['rsi_signal']
|
||||
summary.append(f"RSI: {rsi:.1f} ({rsi_signal})")
|
||||
|
||||
if 'macd_trend' in analysis:
|
||||
macd_trend = analysis['macd_trend']
|
||||
summary.append(f"MACD: {macd_trend}")
|
||||
|
||||
# Risk
|
||||
volatility = analysis.get('volatility_annualized', 0)
|
||||
max_drawdown = analysis.get('max_drawdown', 0)
|
||||
summary.append(f"Volatility: {volatility:.1f}% (Annual)")
|
||||
summary.append(f"Max Drawdown: {max_drawdown:.1f}%")
|
||||
|
||||
return "\n".join(summary)
|
||||
|
||||
# Global instance for easy import
|
||||
stock_analyzer = StockAnalyzer()
|
||||
|
||||
# Convenience function
|
||||
def analyze_stock(data: pd.DataFrame) -> Dict:
|
||||
"""Convenience function to analyze stock data"""
|
||||
return stock_analyzer.analyze_stock(data)
|
||||
483
week4/community-contributions/ai_stock_trading/tools/charting.py
Normal file
483
week4/community-contributions/ai_stock_trading/tools/charting.py
Normal file
@@ -0,0 +1,483 @@
|
||||
"""
|
||||
Charting Module
|
||||
|
||||
This module provides comprehensive charting and visualization capabilities
|
||||
for stock analysis with interactive dashboards using Plotly.
|
||||
"""
|
||||
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
import plotly.graph_objects as go
|
||||
import plotly.express as px
|
||||
from plotly.subplots import make_subplots
|
||||
import streamlit as st
|
||||
from typing import Dict, List, Optional, Tuple
|
||||
import warnings
|
||||
|
||||
warnings.filterwarnings('ignore')
|
||||
|
||||
class StockChartGenerator:
|
||||
"""Enhanced stock chart generator with interactive dashboards"""
|
||||
|
||||
def __init__(self):
|
||||
self.color_scheme = {
|
||||
'primary': '#1f77b4',
|
||||
'secondary': '#ff7f0e',
|
||||
'success': '#2ca02c',
|
||||
'danger': '#d62728',
|
||||
'warning': '#ff7f0e',
|
||||
'info': '#17a2b8',
|
||||
'background': '#f8f9fa'
|
||||
}
|
||||
|
||||
def create_price_chart(self, data: pd.DataFrame, symbol: str, analysis: Dict = None) -> go.Figure:
|
||||
"""
|
||||
Create comprehensive price chart with technical indicators
|
||||
|
||||
Args:
|
||||
data: Stock price data
|
||||
symbol: Stock symbol
|
||||
analysis: Technical analysis results
|
||||
|
||||
Returns:
|
||||
Plotly figure object
|
||||
"""
|
||||
if data.empty:
|
||||
return self._create_empty_chart("No data available")
|
||||
|
||||
# Create subplots
|
||||
fig = make_subplots(
|
||||
rows=3, cols=1,
|
||||
shared_xaxes=True,
|
||||
vertical_spacing=0.05,
|
||||
subplot_titles=(f'{symbol} Price Chart', 'Volume', 'Technical Indicators'),
|
||||
row_heights=[0.6, 0.2, 0.2]
|
||||
)
|
||||
|
||||
# Main price chart (candlestick)
|
||||
fig.add_trace(
|
||||
go.Candlestick(
|
||||
x=data.index,
|
||||
open=data['Open'],
|
||||
high=data['High'],
|
||||
low=data['Low'],
|
||||
close=data['Close'],
|
||||
name='Price',
|
||||
increasing_line_color=self.color_scheme['success'],
|
||||
decreasing_line_color=self.color_scheme['danger']
|
||||
),
|
||||
row=1, col=1
|
||||
)
|
||||
|
||||
# Add moving averages if available
|
||||
if 'SMA_20' in data.columns:
|
||||
fig.add_trace(
|
||||
go.Scatter(
|
||||
x=data.index,
|
||||
y=data['SMA_20'],
|
||||
mode='lines',
|
||||
name='SMA 20',
|
||||
line=dict(color=self.color_scheme['primary'], width=1)
|
||||
),
|
||||
row=1, col=1
|
||||
)
|
||||
|
||||
if 'SMA_50' in data.columns:
|
||||
fig.add_trace(
|
||||
go.Scatter(
|
||||
x=data.index,
|
||||
y=data['SMA_50'],
|
||||
mode='lines',
|
||||
name='SMA 50',
|
||||
line=dict(color=self.color_scheme['secondary'], width=1)
|
||||
),
|
||||
row=1, col=1
|
||||
)
|
||||
|
||||
# Volume chart
|
||||
colors = ['red' if close < open else 'green' for close, open in zip(data['Close'], data['Open'])]
|
||||
fig.add_trace(
|
||||
go.Bar(
|
||||
x=data.index,
|
||||
y=data['Volume'],
|
||||
name='Volume',
|
||||
marker_color=colors,
|
||||
opacity=0.7
|
||||
),
|
||||
row=2, col=1
|
||||
)
|
||||
|
||||
# Technical indicators (RSI if available in analysis)
|
||||
if analysis and 'rsi' in analysis:
|
||||
# Create RSI line (simplified - would need full RSI calculation for time series)
|
||||
rsi_value = analysis['rsi']
|
||||
rsi_line = [rsi_value] * len(data)
|
||||
|
||||
fig.add_trace(
|
||||
go.Scatter(
|
||||
x=data.index,
|
||||
y=rsi_line,
|
||||
mode='lines',
|
||||
name=f'RSI ({rsi_value:.1f})',
|
||||
line=dict(color=self.color_scheme['info'], width=2)
|
||||
),
|
||||
row=3, col=1
|
||||
)
|
||||
|
||||
# Add RSI reference lines
|
||||
fig.add_hline(y=70, line_dash="dash", line_color="red", opacity=0.5, row=3, col=1)
|
||||
fig.add_hline(y=30, line_dash="dash", line_color="green", opacity=0.5, row=3, col=1)
|
||||
|
||||
# Update layout
|
||||
fig.update_layout(
|
||||
title=f'{symbol} Stock Analysis Dashboard',
|
||||
xaxis_title='Date',
|
||||
yaxis_title='Price ($)',
|
||||
template='plotly_white',
|
||||
height=800,
|
||||
showlegend=True,
|
||||
hovermode='x unified'
|
||||
)
|
||||
|
||||
# Remove rangeslider for cleaner look
|
||||
fig.update_layout(xaxis_rangeslider_visible=False)
|
||||
|
||||
return fig
|
||||
|
||||
def create_performance_chart(self, data: pd.DataFrame, symbol: str, analysis: Dict) -> go.Figure:
|
||||
"""
|
||||
Create performance analysis chart
|
||||
|
||||
Args:
|
||||
data: Stock price data
|
||||
symbol: Stock symbol
|
||||
analysis: Analysis results with performance metrics
|
||||
|
||||
Returns:
|
||||
Plotly figure object
|
||||
"""
|
||||
if data.empty:
|
||||
return self._create_empty_chart("No data available for performance analysis")
|
||||
|
||||
# Calculate cumulative returns
|
||||
daily_returns = data['Close'].pct_change().fillna(0)
|
||||
cumulative_returns = (1 + daily_returns).cumprod() - 1
|
||||
|
||||
fig = go.Figure()
|
||||
|
||||
# Cumulative returns line
|
||||
fig.add_trace(
|
||||
go.Scatter(
|
||||
x=data.index,
|
||||
y=cumulative_returns * 100,
|
||||
mode='lines',
|
||||
name='Cumulative Returns (%)',
|
||||
line=dict(color=self.color_scheme['primary'], width=2),
|
||||
fill='tonexty',
|
||||
fillcolor='rgba(31, 119, 180, 0.1)'
|
||||
)
|
||||
)
|
||||
|
||||
# Add benchmark line (0% return)
|
||||
fig.add_hline(y=0, line_dash="dash", line_color="gray", opacity=0.5)
|
||||
|
||||
# Add performance annotations
|
||||
if analysis:
|
||||
total_return = analysis.get('total_return_pct', 0)
|
||||
fig.add_annotation(
|
||||
x=data.index[-1],
|
||||
y=total_return,
|
||||
text=f"Total Return: {total_return:.1f}%",
|
||||
showarrow=True,
|
||||
arrowhead=2,
|
||||
arrowcolor=self.color_scheme['primary'],
|
||||
bgcolor="white",
|
||||
bordercolor=self.color_scheme['primary']
|
||||
)
|
||||
|
||||
fig.update_layout(
|
||||
title=f'{symbol} Performance Analysis',
|
||||
xaxis_title='Date',
|
||||
yaxis_title='Cumulative Returns (%)',
|
||||
template='plotly_white',
|
||||
height=500,
|
||||
hovermode='x'
|
||||
)
|
||||
|
||||
return fig
|
||||
|
||||
def create_risk_analysis_chart(self, analysis: Dict, symbol: str) -> go.Figure:
|
||||
"""
|
||||
Create risk analysis visualization
|
||||
|
||||
Args:
|
||||
analysis: Analysis results with risk metrics
|
||||
symbol: Stock symbol
|
||||
|
||||
Returns:
|
||||
Plotly figure object
|
||||
"""
|
||||
if not analysis or 'error' in analysis:
|
||||
return self._create_empty_chart("No risk data available")
|
||||
|
||||
# Prepare risk metrics
|
||||
risk_metrics = {
|
||||
'Volatility (Annual)': analysis.get('volatility_annualized', 0),
|
||||
'Max Drawdown': abs(analysis.get('max_drawdown', 0)),
|
||||
'VaR 95%': abs(analysis.get('var_95', 0)),
|
||||
'VaR 99%': abs(analysis.get('var_99', 0))
|
||||
}
|
||||
|
||||
# Create radar chart for risk metrics
|
||||
categories = list(risk_metrics.keys())
|
||||
values = list(risk_metrics.values())
|
||||
|
||||
fig = go.Figure()
|
||||
|
||||
fig.add_trace(go.Scatterpolar(
|
||||
r=values,
|
||||
theta=categories,
|
||||
fill='toself',
|
||||
name=f'{symbol} Risk Profile',
|
||||
line_color=self.color_scheme['danger'],
|
||||
fillcolor='rgba(214, 39, 40, 0.1)'
|
||||
))
|
||||
|
||||
fig.update_layout(
|
||||
polar=dict(
|
||||
radialaxis=dict(
|
||||
visible=True,
|
||||
range=[0, max(values) * 1.2] if values else [0, 100]
|
||||
)
|
||||
),
|
||||
title=f'{symbol} Risk Analysis Chart',
|
||||
template='plotly_white',
|
||||
height=500
|
||||
)
|
||||
|
||||
return fig
|
||||
|
||||
def create_comparison_chart(self, data_dict: Dict[str, pd.DataFrame], symbols: List[str]) -> go.Figure:
|
||||
"""
|
||||
Create comparison chart for multiple stocks
|
||||
|
||||
Args:
|
||||
data_dict: Dictionary of stock data {symbol: dataframe}
|
||||
symbols: List of stock symbols to compare
|
||||
|
||||
Returns:
|
||||
Plotly figure object
|
||||
"""
|
||||
fig = go.Figure()
|
||||
|
||||
colors = [self.color_scheme['primary'], self.color_scheme['secondary'],
|
||||
self.color_scheme['success'], self.color_scheme['danger']]
|
||||
|
||||
for i, symbol in enumerate(symbols):
|
||||
if symbol in data_dict and not data_dict[symbol].empty:
|
||||
data = data_dict[symbol]
|
||||
# Normalize prices to start at 100 for comparison
|
||||
normalized_prices = (data['Close'] / data['Close'].iloc[0]) * 100
|
||||
|
||||
fig.add_trace(
|
||||
go.Scatter(
|
||||
x=data.index,
|
||||
y=normalized_prices,
|
||||
mode='lines',
|
||||
name=symbol,
|
||||
line=dict(color=colors[i % len(colors)], width=2)
|
||||
)
|
||||
)
|
||||
|
||||
fig.update_layout(
|
||||
title='Stock Price Comparison (Normalized to 100)',
|
||||
xaxis_title='Date',
|
||||
yaxis_title='Normalized Price',
|
||||
template='plotly_white',
|
||||
height=600,
|
||||
hovermode='x unified'
|
||||
)
|
||||
|
||||
return fig
|
||||
|
||||
def create_sector_analysis_chart(self, sector_data: Dict) -> go.Figure:
|
||||
"""
|
||||
Create sector analysis visualization
|
||||
|
||||
Args:
|
||||
sector_data: Dictionary with sector analysis data
|
||||
|
||||
Returns:
|
||||
Plotly figure object
|
||||
"""
|
||||
# This would typically show sector performance, P/E ratios, etc.
|
||||
# For now, create a placeholder
|
||||
fig = go.Figure()
|
||||
|
||||
fig.add_annotation(
|
||||
x=0.5, y=0.5,
|
||||
text="Sector Analysis<br>Coming Soon",
|
||||
showarrow=False,
|
||||
font=dict(size=20),
|
||||
xref="paper", yref="paper"
|
||||
)
|
||||
|
||||
fig.update_layout(
|
||||
title='Sector Analysis Dashboard',
|
||||
template='plotly_white',
|
||||
height=400,
|
||||
showticklabels=False
|
||||
)
|
||||
|
||||
return fig
|
||||
|
||||
def create_trading_signals_chart(self, data: pd.DataFrame, analysis: Dict, trading_decision: Dict, symbol: str) -> go.Figure:
|
||||
"""
|
||||
Create trading signals visualization
|
||||
|
||||
Args:
|
||||
data: Stock price data
|
||||
analysis: Technical analysis results
|
||||
trading_decision: Trading recommendation
|
||||
symbol: Stock symbol
|
||||
|
||||
Returns:
|
||||
Plotly figure object
|
||||
"""
|
||||
if data.empty:
|
||||
return self._create_empty_chart("No data available for trading signals")
|
||||
|
||||
fig = go.Figure()
|
||||
|
||||
# Price line
|
||||
fig.add_trace(
|
||||
go.Scatter(
|
||||
x=data.index,
|
||||
y=data['Close'],
|
||||
mode='lines',
|
||||
name='Price',
|
||||
line=dict(color=self.color_scheme['primary'], width=2)
|
||||
)
|
||||
)
|
||||
|
||||
# Add trading signal
|
||||
recommendation = trading_decision.get('recommendation', 'HOLD')
|
||||
current_price = data['Close'].iloc[-1]
|
||||
|
||||
signal_color = {
|
||||
'BUY': self.color_scheme['success'],
|
||||
'SELL': self.color_scheme['danger'],
|
||||
'HOLD': self.color_scheme['warning']
|
||||
}.get(recommendation, self.color_scheme['info'])
|
||||
|
||||
fig.add_trace(
|
||||
go.Scatter(
|
||||
x=[data.index[-1]],
|
||||
y=[current_price],
|
||||
mode='markers',
|
||||
name=f'{recommendation} Signal',
|
||||
marker=dict(
|
||||
color=signal_color,
|
||||
size=15,
|
||||
symbol='triangle-up' if recommendation == 'BUY' else
|
||||
'triangle-down' if recommendation == 'SELL' else 'circle'
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
# Add price target if available
|
||||
price_target = trading_decision.get('price_target')
|
||||
if price_target:
|
||||
fig.add_hline(
|
||||
y=price_target,
|
||||
line_dash="dash",
|
||||
line_color=self.color_scheme['success'],
|
||||
annotation_text=f"Target: ${price_target:.2f}"
|
||||
)
|
||||
|
||||
# Add stop loss if available
|
||||
stop_loss = trading_decision.get('stop_loss')
|
||||
if stop_loss:
|
||||
fig.add_hline(
|
||||
y=stop_loss,
|
||||
line_dash="dash",
|
||||
line_color=self.color_scheme['danger'],
|
||||
annotation_text=f"Stop Loss: ${stop_loss:.2f}"
|
||||
)
|
||||
|
||||
fig.update_layout(
|
||||
title=f'{symbol} Trading Signals',
|
||||
xaxis_title='Date',
|
||||
yaxis_title='Price ($)',
|
||||
template='plotly_white',
|
||||
height=500,
|
||||
hovermode='x'
|
||||
)
|
||||
|
||||
return fig
|
||||
|
||||
def create_dashboard_summary(self, symbol: str, analysis: Dict, trading_decision: Dict, sharia_compliance: Dict) -> Dict:
|
||||
"""
|
||||
Create summary metrics for dashboard display
|
||||
|
||||
Args:
|
||||
symbol: Stock symbol
|
||||
analysis: Technical analysis results
|
||||
trading_decision: Trading recommendation
|
||||
sharia_compliance: Sharia compliance results
|
||||
|
||||
Returns:
|
||||
Dictionary with summary metrics
|
||||
"""
|
||||
summary = {
|
||||
'symbol': symbol,
|
||||
'current_price': analysis.get('current_price', 0),
|
||||
'total_return': analysis.get('total_return_pct', 0),
|
||||
'volatility': analysis.get('volatility_annualized', 0),
|
||||
'trading_recommendation': trading_decision.get('recommendation', 'HOLD'),
|
||||
'trading_confidence': trading_decision.get('confidence', 0) * 100,
|
||||
'sharia_ruling': sharia_compliance.get('ruling', 'UNCERTAIN'),
|
||||
'sharia_confidence': sharia_compliance.get('confidence', 0) * 100,
|
||||
'risk_level': trading_decision.get('risk_level', 'medium'),
|
||||
'trend_direction': analysis.get('trend_direction', 'unknown'),
|
||||
'rsi': analysis.get('rsi', 50),
|
||||
'max_drawdown': analysis.get('max_drawdown', 0)
|
||||
}
|
||||
|
||||
return summary
|
||||
|
||||
def _create_empty_chart(self, message: str) -> go.Figure:
|
||||
"""Create an empty chart with a message"""
|
||||
fig = go.Figure()
|
||||
|
||||
fig.add_annotation(
|
||||
x=0.5, y=0.5,
|
||||
text=message,
|
||||
showarrow=False,
|
||||
font=dict(size=16),
|
||||
xref="paper", yref="paper"
|
||||
)
|
||||
|
||||
fig.update_layout(
|
||||
template='plotly_white',
|
||||
height=400,
|
||||
showticklabels=False
|
||||
)
|
||||
|
||||
return fig
|
||||
|
||||
# Global instance for easy import
|
||||
chart_generator = StockChartGenerator()
|
||||
|
||||
# Convenience functions
|
||||
def create_price_chart(data: pd.DataFrame, symbol: str, analysis: Dict = None) -> go.Figure:
|
||||
"""Convenience function to create price chart"""
|
||||
return chart_generator.create_price_chart(data, symbol, analysis)
|
||||
|
||||
def create_performance_chart(data: pd.DataFrame, symbol: str, analysis: Dict) -> go.Figure:
|
||||
"""Convenience function to create performance chart"""
|
||||
return chart_generator.create_performance_chart(data, symbol, analysis)
|
||||
|
||||
def create_trading_signals_chart(data: pd.DataFrame, analysis: Dict, trading_decision: Dict, symbol: str) -> go.Figure:
|
||||
"""Convenience function to create trading signals chart"""
|
||||
return chart_generator.create_trading_signals_chart(data, analysis, trading_decision, symbol)
|
||||
384
week4/community-contributions/ai_stock_trading/tools/fetching.py
Normal file
384
week4/community-contributions/ai_stock_trading/tools/fetching.py
Normal file
@@ -0,0 +1,384 @@
|
||||
"""
|
||||
Stock Data Fetching Module
|
||||
|
||||
This module handles fetching stock data from various sources including yfinance
|
||||
and provides enhanced data retrieval capabilities for different markets.
|
||||
"""
|
||||
|
||||
import yfinance as yf
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
import requests
|
||||
from typing import Dict, List, Optional, Tuple
|
||||
import warnings
|
||||
|
||||
warnings.filterwarnings('ignore')
|
||||
|
||||
class StockDataFetcher:
|
||||
"""Enhanced stock data fetcher with multi-market support"""
|
||||
|
||||
# Stock symbols for different markets
|
||||
STOCK_SYMBOLS = {
|
||||
'USA': {
|
||||
# Technology
|
||||
'Apple Inc.': 'AAPL',
|
||||
'Microsoft Corporation': 'MSFT',
|
||||
'NVIDIA Corporation': 'NVDA',
|
||||
'Alphabet Inc. (Class A)': 'GOOGL',
|
||||
'Alphabet Inc. (Class C)': 'GOOG',
|
||||
'Meta Platforms Inc.': 'META',
|
||||
'Tesla Inc.': 'TSLA',
|
||||
'Amazon.com Inc.': 'AMZN',
|
||||
'Netflix Inc.': 'NFLX',
|
||||
'Adobe Inc.': 'ADBE',
|
||||
'Salesforce Inc.': 'CRM',
|
||||
'Oracle Corporation': 'ORCL',
|
||||
'Cisco Systems Inc.': 'CSCO',
|
||||
'Intel Corporation': 'INTC',
|
||||
'Advanced Micro Devices': 'AMD',
|
||||
'Qualcomm Inc.': 'QCOM',
|
||||
'Texas Instruments': 'TXN',
|
||||
'Broadcom Inc.': 'AVGO',
|
||||
'ServiceNow Inc.': 'NOW',
|
||||
'Palantir Technologies': 'PLTR',
|
||||
|
||||
# Financial Services
|
||||
'JPMorgan Chase & Co.': 'JPM',
|
||||
'Bank of America Corp': 'BAC',
|
||||
'Wells Fargo & Company': 'WFC',
|
||||
'Goldman Sachs Group': 'GS',
|
||||
'Morgan Stanley': 'MS',
|
||||
'Citigroup Inc.': 'C',
|
||||
'American Express Company': 'AXP',
|
||||
'Berkshire Hathaway Inc.': 'BRK.B',
|
||||
'BlackRock Inc.': 'BLK',
|
||||
'Charles Schwab Corporation': 'SCHW',
|
||||
'Visa Inc.': 'V',
|
||||
'Mastercard Inc.': 'MA',
|
||||
|
||||
# Healthcare & Pharmaceuticals
|
||||
'Johnson & Johnson': 'JNJ',
|
||||
'UnitedHealth Group': 'UNH',
|
||||
'Pfizer Inc.': 'PFE',
|
||||
'AbbVie Inc.': 'ABBV',
|
||||
'Merck & Co Inc.': 'MRK',
|
||||
'Eli Lilly and Company': 'LLY',
|
||||
'Abbott Laboratories': 'ABT',
|
||||
'Thermo Fisher Scientific': 'TMO',
|
||||
'Danaher Corporation': 'DHR',
|
||||
'Gilead Sciences Inc.': 'GILD',
|
||||
|
||||
# Consumer & Retail
|
||||
'Walmart Inc.': 'WMT',
|
||||
'Procter & Gamble Co': 'PG',
|
||||
'Coca-Cola Company': 'KO',
|
||||
'PepsiCo Inc.': 'PEP',
|
||||
'Home Depot Inc.': 'HD',
|
||||
'McDonald\'s Corporation': 'MCD',
|
||||
'Nike Inc.': 'NKE',
|
||||
'Costco Wholesale Corp': 'COST',
|
||||
'TJX Companies Inc.': 'TJX',
|
||||
'Lowe\'s Companies Inc.': 'LOW',
|
||||
|
||||
# Industrial & Energy
|
||||
'Exxon Mobil Corporation': 'XOM',
|
||||
'Chevron Corporation': 'CVX',
|
||||
'ConocoPhillips': 'COP',
|
||||
'Caterpillar Inc.': 'CAT',
|
||||
'Boeing Company': 'BA',
|
||||
'General Electric': 'GE',
|
||||
'Honeywell International': 'HON',
|
||||
'Deere & Company': 'DE',
|
||||
'Union Pacific Corporation': 'UNP',
|
||||
'Lockheed Martin Corp': 'LMT',
|
||||
|
||||
# Communication & Media
|
||||
'AT&T Inc.': 'T',
|
||||
'Verizon Communications': 'VZ',
|
||||
'T-Mobile US Inc.': 'TMUS',
|
||||
'Comcast Corporation': 'CMCSA',
|
||||
'Walt Disney Company': 'DIS'
|
||||
},
|
||||
'Egypt': {
|
||||
# Banking & Financial Services
|
||||
'Commercial International Bank': 'COMI.CA',
|
||||
'QNB Alahli Bank': 'QNBE.CA',
|
||||
'Housing and Development Bank': 'HDBK.CA',
|
||||
'Abu Dhabi Islamic Bank Egypt': 'ADIB.CA',
|
||||
'Egyptian Gulf Bank': 'EGBE.CA',
|
||||
|
||||
# Real Estate & Construction
|
||||
'Talaat Moustafa Group Holding': 'TMGH.CA',
|
||||
'Palm Hills Developments': 'PHDC.CA',
|
||||
'Orascom Construction': 'ORAS.CA',
|
||||
'Orascom Development Holding': 'ORHD.CA',
|
||||
'Six of October Development': 'SCTS.CA',
|
||||
'Heliopolis Housing': 'HELI.CA',
|
||||
'Rooya Group': 'RMDA.CA',
|
||||
|
||||
# Industrial & Manufacturing
|
||||
'Eastern Company': 'EAST.CA',
|
||||
'El Sewedy Electric Company': 'SWDY.CA',
|
||||
'Ezz Steel': 'ESRS.CA',
|
||||
'Iron and Steel Company': 'IRON.CA',
|
||||
'Alexandria Containers': 'ALCN.CA',
|
||||
'Sidi Kerir Petrochemicals': 'SKPC.CA',
|
||||
|
||||
# Chemicals & Fertilizers
|
||||
'Abu Qir Fertilizers and Chemical Industries': 'ABUK.CA',
|
||||
'Egyptian Chemical Industries (Kima)': 'KIMA.CA',
|
||||
'Misr Fertilizers Production': 'MFPC.CA',
|
||||
|
||||
# Telecommunications & Technology
|
||||
'Telecom Egypt': 'ETEL.CA',
|
||||
'Raya Holding': 'RAYA.CA',
|
||||
'E-Finance for Digital Payments': 'EFIH.CA',
|
||||
'Fawry for Banking Technology': 'FWRY.CA',
|
||||
|
||||
# Food & Beverages
|
||||
'Juhayna Food Industries': 'JUFO.CA',
|
||||
'Edita Food Industries': 'EFID.CA',
|
||||
'Cairo Poultry Company': 'POUL.CA',
|
||||
'Upper Egypt Flour Mills': 'UEFM.CA',
|
||||
'Ismailia Misr Poultry': 'ISPH.CA',
|
||||
|
||||
# Healthcare & Pharmaceuticals
|
||||
'Cleopatra Hospital Group': 'CLHO.CA',
|
||||
'Cairo Pharmaceuticals': 'PHAR.CA',
|
||||
|
||||
# Energy & Utilities
|
||||
'Egyptian Natural Gas Company': 'EGAS.CA',
|
||||
'Suez Cement Company': 'SCEM.CA',
|
||||
'Arabian Cement Company': 'ARCC.CA',
|
||||
|
||||
# Investment & Holding Companies
|
||||
'Egyptian Financial Group-Hermes': 'HRHO.CA',
|
||||
'Citadel Capital': 'CCAP.CA',
|
||||
'Beltone Financial Holding': 'BTFH.CA'
|
||||
}
|
||||
}
|
||||
|
||||
# Currency mapping for different markets
|
||||
MARKET_CURRENCIES = {
|
||||
'USA': 'USD',
|
||||
'Egypt': 'EGP'
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
self.cache = {}
|
||||
|
||||
def get_available_stocks(self, country: str) -> Dict[str, str]:
|
||||
"""Get available stocks for a specific country"""
|
||||
return self.STOCK_SYMBOLS.get(country, {})
|
||||
|
||||
def get_market_currency(self, country: str) -> str:
|
||||
"""Get the currency for a specific market"""
|
||||
return self.MARKET_CURRENCIES.get(country, 'USD')
|
||||
|
||||
def format_price_with_currency(self, price: float, country: str) -> str:
|
||||
"""Format price with appropriate currency symbol"""
|
||||
currency = self.get_market_currency(country)
|
||||
if currency == 'EGP':
|
||||
return f"{price:.2f} EGP"
|
||||
elif currency == 'USD':
|
||||
return f"${price:.2f}"
|
||||
else:
|
||||
return f"{price:.2f} {currency}"
|
||||
|
||||
def fetch_stock_data(self, symbol: str, period: str = "1y", interval: str = "1d") -> pd.DataFrame:
|
||||
"""
|
||||
Fetch historical stock data with enhanced error handling
|
||||
|
||||
Args:
|
||||
symbol: Stock symbol (e.g., 'AAPL', 'COMI.CA')
|
||||
period: Time period ('1d', '5d', '1mo', '3mo', '6mo', '1y', '2y', '5y', '10y', 'ytd', 'max')
|
||||
interval: Data interval ('1m', '2m', '5m', '15m', '30m', '60m', '90m', '1h', '1d', '5d', '1wk', '1mo', '3mo')
|
||||
|
||||
Returns:
|
||||
DataFrame with OHLCV data
|
||||
"""
|
||||
cache_key = f"{symbol}_{period}_{interval}"
|
||||
|
||||
# Check cache first
|
||||
if cache_key in self.cache:
|
||||
return self.cache[cache_key]
|
||||
|
||||
try:
|
||||
# Create ticker object
|
||||
ticker = yf.Ticker(symbol)
|
||||
|
||||
# Fetch historical data
|
||||
data = ticker.history(period=period, interval=interval)
|
||||
|
||||
if data.empty:
|
||||
print(f"⚠️ No data found for {symbol}")
|
||||
return pd.DataFrame()
|
||||
|
||||
# Clean and enhance data
|
||||
data = self._clean_data(data)
|
||||
|
||||
# Cache the result
|
||||
self.cache[cache_key] = data
|
||||
|
||||
print(f"✅ Successfully fetched {len(data)} data points for {symbol} ({period})")
|
||||
return data
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Error fetching data for {symbol}: {str(e)}")
|
||||
return pd.DataFrame()
|
||||
|
||||
def get_stock_info(self, symbol: str, country: Optional[str] = None) -> Dict:
|
||||
"""
|
||||
Get comprehensive stock information
|
||||
|
||||
Args:
|
||||
symbol: Stock symbol
|
||||
country: Market country (USA, Egypt) for currency handling
|
||||
|
||||
Returns:
|
||||
Dictionary with stock information
|
||||
"""
|
||||
try:
|
||||
ticker = yf.Ticker(symbol)
|
||||
info = ticker.info
|
||||
|
||||
# Detect country if not provided
|
||||
if country is None:
|
||||
country = self._detect_country_from_symbol(symbol)
|
||||
|
||||
# Get market currency
|
||||
market_currency = self.get_market_currency(country)
|
||||
|
||||
# Extract key information
|
||||
stock_info = {
|
||||
'symbol': symbol,
|
||||
'company_name': info.get('longName', 'N/A'),
|
||||
'sector': info.get('sector', 'N/A'),
|
||||
'industry': info.get('industry', 'N/A'),
|
||||
'market_cap': info.get('marketCap', 0),
|
||||
'pe_ratio': info.get('trailingPE', 0),
|
||||
'dividend_yield': info.get('dividendYield', 0),
|
||||
'beta': info.get('beta', 0),
|
||||
'fifty_two_week_high': info.get('fiftyTwoWeekHigh', 0),
|
||||
'fifty_two_week_low': info.get('fiftyTwoWeekLow', 0),
|
||||
'current_price': info.get('currentPrice', 0),
|
||||
'currency': market_currency, # Use detected market currency
|
||||
'exchange': info.get('exchange', 'N/A'),
|
||||
'country': country,
|
||||
'market_country': country # Add explicit market country
|
||||
}
|
||||
|
||||
return stock_info
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Error fetching info for {symbol}: {str(e)}")
|
||||
return {'symbol': symbol, 'error': str(e)}
|
||||
|
||||
def _detect_country_from_symbol(self, symbol: str) -> str:
|
||||
"""
|
||||
Detect country from stock symbol
|
||||
|
||||
Args:
|
||||
symbol: Stock symbol
|
||||
|
||||
Returns:
|
||||
Country name (USA or Egypt)
|
||||
"""
|
||||
# Check if symbol exists in any country's stock list
|
||||
for country, stocks in self.STOCK_SYMBOLS.items():
|
||||
if symbol in stocks.values():
|
||||
return country
|
||||
|
||||
# Default to USA if not found
|
||||
return 'USA'
|
||||
|
||||
def fetch_multiple_periods(self, symbol: str) -> Dict[str, pd.DataFrame]:
|
||||
"""
|
||||
Fetch data for multiple time periods
|
||||
|
||||
Args:
|
||||
symbol: Stock symbol
|
||||
|
||||
Returns:
|
||||
Dictionary with DataFrames for different periods
|
||||
"""
|
||||
periods = ['1mo', '1y', '5y']
|
||||
data = {}
|
||||
|
||||
for period in periods:
|
||||
df = self.fetch_stock_data(symbol, period)
|
||||
if not df.empty:
|
||||
data[period] = df
|
||||
|
||||
return data
|
||||
|
||||
def _clean_data(self, data: pd.DataFrame) -> pd.DataFrame:
|
||||
"""
|
||||
Clean and enhance the stock data
|
||||
|
||||
Args:
|
||||
data: Raw stock data DataFrame
|
||||
|
||||
Returns:
|
||||
Cleaned DataFrame
|
||||
"""
|
||||
# Remove rows with all NaN values
|
||||
data = data.dropna(how='all')
|
||||
|
||||
# Forward fill missing values
|
||||
data = data.fillna(method='ffill')
|
||||
|
||||
# Add technical indicators
|
||||
if len(data) > 0:
|
||||
# Simple moving averages
|
||||
if len(data) >= 20:
|
||||
data['SMA_20'] = data['Close'].rolling(window=20).mean()
|
||||
if len(data) >= 50:
|
||||
data['SMA_50'] = data['Close'].rolling(window=50).mean()
|
||||
|
||||
# Daily returns
|
||||
data['Daily_Return'] = data['Close'].pct_change()
|
||||
|
||||
# Price change from previous day
|
||||
data['Price_Change'] = data['Close'].diff()
|
||||
data['Price_Change_Pct'] = (data['Price_Change'] / data['Close'].shift(1)) * 100
|
||||
|
||||
return data
|
||||
|
||||
def get_real_time_price(self, symbol: str) -> Optional[float]:
|
||||
"""
|
||||
Get real-time stock price
|
||||
|
||||
Args:
|
||||
symbol: Stock symbol
|
||||
|
||||
Returns:
|
||||
Current stock price or None if error
|
||||
"""
|
||||
try:
|
||||
ticker = yf.Ticker(symbol)
|
||||
data = ticker.history(period="1d", interval="1m")
|
||||
|
||||
if not data.empty:
|
||||
return float(data['Close'].iloc[-1])
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Error fetching real-time price for {symbol}: {str(e)}")
|
||||
return None
|
||||
|
||||
# Global instance for easy import
|
||||
stock_fetcher = StockDataFetcher()
|
||||
|
||||
# Convenience functions
|
||||
def fetch_stock_data(symbol: str, period: str = "1y", interval: str = "1d") -> pd.DataFrame:
|
||||
"""Convenience function to fetch stock data"""
|
||||
return stock_fetcher.fetch_stock_data(symbol, period, interval)
|
||||
|
||||
def get_available_stocks(country: str) -> Dict[str, str]:
|
||||
"""Convenience function to get available stocks"""
|
||||
return stock_fetcher.get_available_stocks(country)
|
||||
|
||||
def get_stock_info(symbol: str) -> Dict:
|
||||
"""Convenience function to get stock info"""
|
||||
return stock_fetcher.get_stock_info(symbol)
|
||||
@@ -0,0 +1,591 @@
|
||||
"""
|
||||
Sharia Compliance Module
|
||||
|
||||
This module provides comprehensive Islamic finance compliance checking
|
||||
for stocks and investments according to Islamic principles.
|
||||
"""
|
||||
|
||||
import os
|
||||
import json
|
||||
import requests
|
||||
from typing import Dict, List, Optional, Tuple
|
||||
import pandas as pd
|
||||
from openai import OpenAI
|
||||
from dotenv import load_dotenv
|
||||
from bs4 import BeautifulSoup
|
||||
import time
|
||||
import re
|
||||
|
||||
# Load environment variables
|
||||
load_dotenv()
|
||||
|
||||
class ShariaComplianceChecker:
|
||||
"""Enhanced Sharia compliance checker for Islamic investing"""
|
||||
|
||||
def __init__(self):
|
||||
self.client = OpenAI(api_key=os.getenv('OPENAI_API_KEY'))
|
||||
|
||||
# Sharia compliance criteria weights
|
||||
self.criteria_weights = {
|
||||
'business_activity': 0.40, # Most important
|
||||
'financial_ratios': 0.30,
|
||||
'debt_levels': 0.20,
|
||||
'revenue_sources': 0.10
|
||||
}
|
||||
|
||||
# Prohibited business activities (comprehensive list)
|
||||
self.prohibited_activities = {
|
||||
# Core prohibitions
|
||||
'alcohol', 'alcoholic_beverages', 'wine', 'beer', 'spirits', 'liquor',
|
||||
'gambling', 'casino', 'lottery', 'betting', 'gaming', 'poker',
|
||||
'tobacco', 'cigarettes', 'smoking', 'nicotine',
|
||||
'pork', 'pig_farming', 'swine', 'ham', 'bacon',
|
||||
'adult_entertainment', 'pornography', 'strip_clubs', 'escort_services',
|
||||
|
||||
# Financial prohibitions
|
||||
'conventional_banking', 'interest_based_finance', 'usury', 'riba',
|
||||
'conventional_insurance', 'life_insurance', 'derivatives_trading',
|
||||
'forex_trading', 'currency_speculation', 'margin_trading',
|
||||
'short_selling', 'day_trading', 'high_frequency_trading',
|
||||
|
||||
# Weapons and defense
|
||||
'weapons', 'arms_manufacturing', 'defense_contractors', 'military_equipment',
|
||||
'ammunition', 'explosives', 'nuclear_weapons',
|
||||
|
||||
# Other prohibitions
|
||||
'nightclubs', 'bars', 'entertainment_venues', 'music_industry',
|
||||
'film_industry', 'media_entertainment', 'advertising_haram_products'
|
||||
}
|
||||
|
||||
# Sharia-compliant sectors (generally accepted)
|
||||
self.compliant_sectors = {
|
||||
'technology', 'healthcare', 'pharmaceuticals', 'telecommunications',
|
||||
'utilities', 'real_estate', 'construction', 'manufacturing',
|
||||
'retail', 'food_beverages', 'transportation', 'energy_renewable'
|
||||
}
|
||||
|
||||
# Questionable sectors (need detailed analysis)
|
||||
self.questionable_sectors = {
|
||||
'financial_services', 'media', 'hotels', 'airlines',
|
||||
'oil_gas', 'mining', 'chemicals', 'entertainment',
|
||||
'restaurants', 'hospitality', 'advertising'
|
||||
}
|
||||
|
||||
# AAOIFI and DSN Sharia standards
|
||||
self.sharia_standards = {
|
||||
'max_debt_to_assets': 0.33, # 33% maximum debt-to-assets ratio
|
||||
'max_interest_income': 0.05, # 5% maximum interest income
|
||||
'max_non_compliant_income': 0.05, # 5% maximum non-compliant income
|
||||
'min_tangible_assets': 0.20 # 20% minimum tangible assets
|
||||
}
|
||||
|
||||
def _search_company_business_activities(self, company_name: str, symbol: str) -> Dict:
|
||||
"""
|
||||
Search web for company's business activities to verify Sharia compliance
|
||||
|
||||
Args:
|
||||
company_name: Company name
|
||||
symbol: Stock symbol
|
||||
|
||||
Returns:
|
||||
Dictionary with business activity information
|
||||
"""
|
||||
try:
|
||||
# Search query for company business activities
|
||||
search_queries = [
|
||||
f"{company_name} business activities products services",
|
||||
f"{company_name} {symbol} what does company do",
|
||||
f"{company_name} revenue sources business model"
|
||||
]
|
||||
|
||||
business_info = {
|
||||
'activities': [],
|
||||
'products': [],
|
||||
'services': [],
|
||||
'revenue_sources': [],
|
||||
'prohibited_found': [],
|
||||
'confidence': 0.5
|
||||
}
|
||||
|
||||
for query in search_queries[:1]: # Limit to 1 search to avoid rate limits
|
||||
try:
|
||||
# Simple web search simulation (in production, use proper search API)
|
||||
search_url = f"https://www.google.com/search?q={query.replace(' ', '+')}"
|
||||
headers = {
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
|
||||
}
|
||||
|
||||
# For now, return basic analysis based on company name and sector
|
||||
# In production, implement actual web scraping with proper rate limiting
|
||||
business_info['confidence'] = 0.6
|
||||
break
|
||||
|
||||
except Exception as e:
|
||||
print(f"Web search error: {e}")
|
||||
continue
|
||||
|
||||
return business_info
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error in business activity search: {e}")
|
||||
return {'activities': [], 'prohibited_found': [], 'confidence': 0.3}
|
||||
|
||||
def _estimate_debt_ratio(self, stock_info: Dict) -> float:
|
||||
"""
|
||||
Estimate debt-to-assets ratio based on available information
|
||||
|
||||
Args:
|
||||
stock_info: Stock information dictionary
|
||||
|
||||
Returns:
|
||||
Estimated debt-to-assets ratio
|
||||
"""
|
||||
try:
|
||||
# In production, this would fetch actual balance sheet data
|
||||
# For now, estimate based on sector and other indicators
|
||||
sector = stock_info.get('sector', '').lower()
|
||||
industry = stock_info.get('industry', '').lower()
|
||||
|
||||
# High debt sectors
|
||||
if any(x in sector or x in industry for x in ['utility', 'telecom', 'airline', 'real estate']):
|
||||
return 0.45 # Typically higher debt
|
||||
|
||||
# Medium debt sectors
|
||||
elif any(x in sector or x in industry for x in ['manufacturing', 'retail', 'energy']):
|
||||
return 0.25
|
||||
|
||||
# Low debt sectors
|
||||
elif any(x in sector or x in industry for x in ['technology', 'healthcare', 'software']):
|
||||
return 0.15
|
||||
|
||||
# Financial sector (different calculation)
|
||||
elif 'financial' in sector or 'bank' in sector:
|
||||
return 0.8 # Banks have high leverage by nature
|
||||
|
||||
# Default estimate
|
||||
return 0.3
|
||||
|
||||
except Exception:
|
||||
return 0.3 # Conservative default
|
||||
|
||||
def _check_business_activity(self, stock_info: Dict) -> float:
|
||||
"""
|
||||
Check if the company's primary business activity is Sharia-compliant
|
||||
|
||||
Returns:
|
||||
Score from 0.0 (non-compliant) to 1.0 (fully compliant)
|
||||
"""
|
||||
sector = stock_info.get('sector', '').lower()
|
||||
industry = stock_info.get('industry', '').lower()
|
||||
company_name = stock_info.get('company_name', '').lower()
|
||||
|
||||
# Check for explicitly prohibited activities
|
||||
for prohibited in self.prohibited_activities:
|
||||
if (prohibited.replace('_', ' ') in sector or
|
||||
prohibited.replace('_', ' ') in industry or
|
||||
prohibited.replace('_', ' ') in company_name):
|
||||
return 0.0
|
||||
|
||||
# Check for compliant sectors
|
||||
for compliant in self.compliant_sectors:
|
||||
if (compliant.replace('_', ' ') in sector or
|
||||
compliant.replace('_', ' ') in industry):
|
||||
return 1.0
|
||||
|
||||
# Check for questionable sectors
|
||||
for questionable in self.questionable_sectors:
|
||||
if (questionable.replace('_', ' ') in sector or
|
||||
questionable.replace('_', ' ') in industry):
|
||||
return 0.5
|
||||
|
||||
# Default for unknown sectors
|
||||
return 0.7
|
||||
|
||||
def check_sharia_compliance(self, symbol: str, stock_info: Dict, analysis: Dict) -> Dict:
|
||||
"""
|
||||
Comprehensive Sharia compliance check
|
||||
|
||||
Args:
|
||||
symbol: Stock symbol
|
||||
stock_info: Stock information
|
||||
analysis: Technical analysis results
|
||||
|
||||
Returns:
|
||||
Dictionary with compliance assessment
|
||||
"""
|
||||
try:
|
||||
# Business activity screening
|
||||
business_score = self._check_business_activity(stock_info)
|
||||
|
||||
# Financial ratios screening
|
||||
financial_score = self._check_financial_ratios(stock_info, analysis)
|
||||
|
||||
# Debt levels screening
|
||||
debt_score = self._check_debt_levels(stock_info)
|
||||
|
||||
# Revenue sources screening
|
||||
revenue_score = self._check_revenue_sources(stock_info)
|
||||
|
||||
# Calculate weighted compliance score
|
||||
total_score = (
|
||||
business_score * self.criteria_weights['business_activity'] +
|
||||
financial_score * self.criteria_weights['financial_ratios'] +
|
||||
debt_score * self.criteria_weights['debt_levels'] +
|
||||
revenue_score * self.criteria_weights['revenue_sources']
|
||||
)
|
||||
|
||||
# Get AI-powered detailed analysis
|
||||
ai_analysis = self._get_ai_sharia_analysis(symbol, stock_info)
|
||||
|
||||
# Determine final ruling
|
||||
ruling = self._determine_ruling(total_score, ai_analysis)
|
||||
|
||||
return {
|
||||
'symbol': symbol,
|
||||
'ruling': ruling['status'],
|
||||
'confidence': ruling['confidence'],
|
||||
'compliance_score': total_score,
|
||||
'detailed_scores': {
|
||||
'business_activity': business_score,
|
||||
'financial_ratios': financial_score,
|
||||
'debt_levels': debt_score,
|
||||
'revenue_sources': revenue_score
|
||||
},
|
||||
'reasoning': ruling['reasoning'],
|
||||
'key_concerns': ruling.get('concerns', []),
|
||||
'recommendations': ruling.get('recommendations', []),
|
||||
'ai_analysis': ai_analysis.get('analysis', ''),
|
||||
'scholar_consultation_advised': ruling.get('scholar_consultation', False),
|
||||
'alternative_suggestions': ruling.get('alternatives', [])
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
return {
|
||||
'symbol': symbol,
|
||||
'ruling': 'UNCERTAIN',
|
||||
'confidence': 0.0,
|
||||
'reasoning': f'Error in Sharia compliance analysis: {str(e)}',
|
||||
'error': str(e)
|
||||
}
|
||||
|
||||
def _check_business_activity(self, stock_info: Dict) -> float:
|
||||
"""
|
||||
Check if the company's primary business activity is Sharia-compliant
|
||||
|
||||
Returns:
|
||||
Score from 0.0 (non-compliant) to 1.0 (fully compliant)
|
||||
"""
|
||||
sector = stock_info.get('sector', '').lower()
|
||||
industry = stock_info.get('industry', '').lower()
|
||||
company_name = stock_info.get('company_name', '').lower()
|
||||
|
||||
# Check for explicitly prohibited activities
|
||||
for prohibited in self.prohibited_activities:
|
||||
if (prohibited.replace('_', ' ') in sector or
|
||||
prohibited.replace('_', ' ') in industry or
|
||||
prohibited.replace('_', ' ') in company_name):
|
||||
return 0.0
|
||||
|
||||
# Check for compliant sectors
|
||||
for compliant in self.compliant_sectors:
|
||||
if (compliant.replace('_', ' ') in sector or
|
||||
compliant.replace('_', ' ') in industry):
|
||||
return 1.0
|
||||
|
||||
# Check for questionable sectors
|
||||
for questionable in self.questionable_sectors:
|
||||
if (questionable.replace('_', ' ') in sector or
|
||||
questionable.replace('_', ' ') in industry):
|
||||
return 0.5
|
||||
|
||||
# Default for unknown sectors
|
||||
return 0.7
|
||||
|
||||
def _check_financial_ratios(self, stock_info: Dict, analysis: Dict) -> float:
|
||||
"""
|
||||
Check financial ratios according to AAOIFI and DSN Sharia standards
|
||||
|
||||
AAOIFI/DSN Sharia screening ratios:
|
||||
- Debt/Total Assets < 33%
|
||||
- Interest Income/Total Revenue < 5%
|
||||
- Non-compliant Income/Total Revenue < 5%
|
||||
- Tangible Assets/Total Assets > 20%
|
||||
|
||||
Returns:
|
||||
Score from 0.0 to 1.0
|
||||
"""
|
||||
score = 1.0
|
||||
penalties = []
|
||||
|
||||
try:
|
||||
# Get financial metrics (these would come from detailed financial data)
|
||||
market_cap = stock_info.get('market_cap', 0)
|
||||
pe_ratio = stock_info.get('pe_ratio', 0)
|
||||
|
||||
# Debt-to-Assets ratio check
|
||||
# Note: In production, fetch actual balance sheet data
|
||||
debt_to_assets = self._estimate_debt_ratio(stock_info)
|
||||
if debt_to_assets > self.sharia_standards['max_debt_to_assets']:
|
||||
penalty = min(0.5, (debt_to_assets - self.sharia_standards['max_debt_to_assets']) * 2)
|
||||
score -= penalty
|
||||
penalties.append(f"High debt ratio: {debt_to_assets:.1%} > {self.sharia_standards['max_debt_to_assets']:.1%}")
|
||||
|
||||
# Interest income check (for financial companies)
|
||||
sector = stock_info.get('sector', '').lower()
|
||||
if 'financial' in sector or 'bank' in sector:
|
||||
# Financial companies likely have significant interest income
|
||||
score -= 0.3
|
||||
penalties.append("Financial sector - likely high interest income")
|
||||
|
||||
# Industry-specific checks
|
||||
industry = stock_info.get('industry', '').lower()
|
||||
if any(prohibited in industry for prohibited in ['insurance', 'casino', 'alcohol', 'tobacco']):
|
||||
score = 0.0
|
||||
penalties.append(f"Prohibited industry: {industry}")
|
||||
|
||||
return max(0.0, score)
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error in financial ratio analysis: {e}")
|
||||
return 0.5 # Default moderate score if analysis fails
|
||||
# For now, we'll use available basic metrics and estimate
|
||||
|
||||
# PE ratio check (very high PE might indicate speculation)
|
||||
pe_ratio = stock_info.get('pe_ratio', 0)
|
||||
if pe_ratio > 50:
|
||||
score -= 0.2
|
||||
elif pe_ratio > 30:
|
||||
score -= 0.1
|
||||
|
||||
# Beta check (high beta indicates high speculation/volatility)
|
||||
beta = stock_info.get('beta', 1.0)
|
||||
if beta > 2.0:
|
||||
score -= 0.3
|
||||
elif beta > 1.5:
|
||||
score -= 0.1
|
||||
|
||||
# Volatility check from analysis
|
||||
volatility = analysis.get('volatility_annualized', 0)
|
||||
if volatility > 60:
|
||||
score -= 0.2
|
||||
elif volatility > 40:
|
||||
score -= 0.1
|
||||
|
||||
return max(0.0, score)
|
||||
|
||||
def _check_debt_levels(self, stock_info: Dict) -> float:
|
||||
"""
|
||||
Check debt levels according to Sharia standards
|
||||
|
||||
Returns:
|
||||
Score from 0.0 to 1.0
|
||||
"""
|
||||
# Note: In a real implementation, you would fetch debt-to-assets ratio
|
||||
# For now, we'll use sector-based estimation
|
||||
|
||||
sector = stock_info.get('sector', '').lower()
|
||||
|
||||
# Sectors typically with high debt
|
||||
high_debt_sectors = ['utilities', 'real estate', 'telecommunications']
|
||||
medium_debt_sectors = ['manufacturing', 'transportation', 'energy']
|
||||
low_debt_sectors = ['technology', 'healthcare', 'retail']
|
||||
|
||||
if any(s in sector for s in high_debt_sectors):
|
||||
return 0.6 # Assume higher debt but may still be acceptable
|
||||
elif any(s in sector for s in medium_debt_sectors):
|
||||
return 0.8
|
||||
elif any(s in sector for s in low_debt_sectors):
|
||||
return 1.0
|
||||
else:
|
||||
return 0.7 # Default assumption
|
||||
|
||||
def _check_revenue_sources(self, stock_info: Dict) -> float:
|
||||
"""
|
||||
Check revenue sources for non-compliant income
|
||||
|
||||
Returns:
|
||||
Score from 0.0 to 1.0
|
||||
"""
|
||||
sector = stock_info.get('sector', '').lower()
|
||||
industry = stock_info.get('industry', '').lower()
|
||||
|
||||
# Industries with potential non-compliant revenue
|
||||
if 'financial' in sector or 'bank' in industry:
|
||||
return 0.3 # Banks typically have significant interest income
|
||||
elif 'insurance' in industry:
|
||||
return 0.2
|
||||
elif 'hotel' in industry or 'entertainment' in industry:
|
||||
return 0.6 # May have some non-compliant revenue sources
|
||||
else:
|
||||
return 0.9 # Assume mostly compliant revenue
|
||||
|
||||
def _get_ai_sharia_analysis(self, symbol: str, stock_info: Dict) -> Dict:
|
||||
"""
|
||||
Get AI-powered detailed Sharia compliance analysis
|
||||
|
||||
Args:
|
||||
symbol: Stock symbol
|
||||
stock_info: Stock information
|
||||
|
||||
Returns:
|
||||
Dictionary with AI analysis
|
||||
"""
|
||||
try:
|
||||
prompt = f"""
|
||||
As an Islamic finance expert, analyze the Sharia compliance of {symbol}.
|
||||
|
||||
Company Information:
|
||||
- Name: {stock_info.get('company_name', 'N/A')}
|
||||
- Sector: {stock_info.get('sector', 'N/A')}
|
||||
- Industry: {stock_info.get('industry', 'N/A')}
|
||||
- Country: {stock_info.get('country', 'N/A')}
|
||||
|
||||
Please analyze according to Islamic finance principles and provide:
|
||||
1. Primary business activity assessment
|
||||
2. Potential Sharia compliance concerns
|
||||
3. Revenue source analysis
|
||||
4. Debt and interest exposure concerns
|
||||
5. Overall compliance recommendation
|
||||
6. Specific areas requiring scholar consultation
|
||||
7. Alternative Sharia-compliant investment suggestions
|
||||
|
||||
Format your response as JSON:
|
||||
{{
|
||||
"compliance_status": "HALAL/HARAM/DOUBTFUL",
|
||||
"confidence": 85,
|
||||
"analysis": "Detailed analysis...",
|
||||
"concerns": ["concern1", "concern2"],
|
||||
"recommendations": ["rec1", "rec2"],
|
||||
"scholar_consultation": true/false,
|
||||
"alternatives": ["alt1", "alt2"]
|
||||
}}
|
||||
"""
|
||||
|
||||
response = self.client.chat.completions.create(
|
||||
model="gpt-4o-mini",
|
||||
messages=[
|
||||
{"role": "system", "content": "You are an expert in Islamic finance and Sharia compliance for investments."},
|
||||
{"role": "user", "content": prompt}
|
||||
],
|
||||
temperature=0.2,
|
||||
max_tokens=1000
|
||||
)
|
||||
|
||||
ai_response = response.choices[0].message.content
|
||||
|
||||
try:
|
||||
if ai_response:
|
||||
return json.loads(ai_response)
|
||||
else:
|
||||
return {'analysis': 'No AI response received', 'error': 'Empty response'}
|
||||
except json.JSONDecodeError:
|
||||
return {'analysis': ai_response, 'parsed_fallback': True}
|
||||
|
||||
except Exception as e:
|
||||
return {'analysis': f'AI analysis unavailable: {str(e)}', 'error': str(e)}
|
||||
|
||||
def _determine_ruling(self, compliance_score: float, ai_analysis: Dict) -> Dict:
|
||||
"""
|
||||
Determine final Sharia compliance ruling
|
||||
|
||||
Args:
|
||||
compliance_score: Calculated compliance score
|
||||
ai_analysis: AI analysis results
|
||||
|
||||
Returns:
|
||||
Dictionary with final ruling
|
||||
"""
|
||||
# Get AI recommendation if available
|
||||
ai_status = ai_analysis.get('compliance_status', 'DOUBTFUL')
|
||||
ai_confidence = ai_analysis.get('confidence', 50) / 100
|
||||
|
||||
# Combine algorithmic score with AI analysis
|
||||
if compliance_score >= 0.8 and ai_status == 'HALAL':
|
||||
status = 'HALAL'
|
||||
confidence = min(0.9, (compliance_score + ai_confidence) / 2)
|
||||
reasoning = "Company appears to be Sharia-compliant based on business activities and financial structure."
|
||||
elif compliance_score <= 0.3 or ai_status == 'HARAM':
|
||||
status = 'HARAM'
|
||||
confidence = max(0.7, (1 - compliance_score + ai_confidence) / 2)
|
||||
reasoning = "Company has significant Sharia compliance issues and should be avoided."
|
||||
else:
|
||||
status = 'DOUBTFUL'
|
||||
confidence = 0.6
|
||||
reasoning = "Company has mixed compliance indicators. Consultation with Islamic scholars recommended."
|
||||
|
||||
return {
|
||||
'status': status,
|
||||
'confidence': confidence,
|
||||
'reasoning': reasoning,
|
||||
'concerns': ai_analysis.get('concerns', []),
|
||||
'recommendations': ai_analysis.get('recommendations', []),
|
||||
'scholar_consultation': ai_analysis.get('scholar_consultation', status == 'DOUBTFUL'),
|
||||
'alternatives': ai_analysis.get('alternatives', [])
|
||||
}
|
||||
|
||||
def get_compliance_summary(self, compliance_result: Dict) -> str:
|
||||
"""Generate a human-readable compliance summary"""
|
||||
if 'error' in compliance_result:
|
||||
return f"Compliance Analysis Error: {compliance_result['error']}"
|
||||
|
||||
symbol = compliance_result.get('symbol', 'Unknown')
|
||||
ruling = compliance_result.get('ruling', 'UNCERTAIN')
|
||||
confidence = compliance_result.get('confidence', 0) * 100
|
||||
|
||||
summary = [f"Sharia Compliance Analysis for {symbol}"]
|
||||
summary.append(f"Ruling: {ruling} (Confidence: {confidence:.0f}%)")
|
||||
|
||||
if ruling == 'HALAL':
|
||||
summary.append("✅ This investment appears to be permissible under Islamic law.")
|
||||
elif ruling == 'HARAM':
|
||||
summary.append("❌ This investment should be avoided due to Sharia non-compliance.")
|
||||
else:
|
||||
summary.append("⚠️ This investment requires further investigation and scholar consultation.")
|
||||
|
||||
# Add key concerns if any
|
||||
concerns = compliance_result.get('key_concerns', [])
|
||||
if concerns:
|
||||
summary.append(f"Key Concerns: {', '.join(concerns)}")
|
||||
|
||||
# Add recommendations
|
||||
recommendations = compliance_result.get('recommendations', [])
|
||||
if recommendations:
|
||||
summary.append(f"Recommendations: {', '.join(recommendations[:2])}")
|
||||
|
||||
return "\n".join(summary)
|
||||
|
||||
def get_sharia_alternatives(self, sector: str, country: str = 'USA') -> List[str]:
|
||||
"""
|
||||
Get Sharia-compliant alternatives in the same sector
|
||||
|
||||
Args:
|
||||
sector: Company sector
|
||||
country: Market country
|
||||
|
||||
Returns:
|
||||
List of alternative stock symbols
|
||||
"""
|
||||
# This would typically connect to a Sharia-compliant stock database
|
||||
# For now, return some common Sharia-compliant stocks by sector
|
||||
|
||||
alternatives = {
|
||||
'technology': ['AAPL', 'MSFT', 'GOOGL', 'META'],
|
||||
'healthcare': ['JNJ', 'PFE', 'UNH', 'ABBV'],
|
||||
'consumer': ['PG', 'KO', 'PEP', 'WMT'],
|
||||
'industrial': ['BA', 'CAT', 'GE', 'MMM']
|
||||
}
|
||||
|
||||
sector_lower = sector.lower()
|
||||
for key, stocks in alternatives.items():
|
||||
if key in sector_lower:
|
||||
return stocks[:3] # Return top 3 alternatives
|
||||
|
||||
return []
|
||||
|
||||
# Global instance for easy import
|
||||
sharia_checker = ShariaComplianceChecker()
|
||||
|
||||
# Convenience function
|
||||
def check_sharia_compliance(symbol: str, stock_info: Dict, analysis: Dict) -> Dict:
|
||||
"""Convenience function to check Sharia compliance"""
|
||||
return sharia_checker.check_sharia_compliance(symbol, stock_info, analysis)
|
||||
@@ -0,0 +1,491 @@
|
||||
"""
|
||||
Trading Decisions Module
|
||||
|
||||
This module provides AI-powered trading recommendations using OpenAI
|
||||
and advanced algorithmic decision-making based on technical analysis.
|
||||
"""
|
||||
|
||||
import os
|
||||
import json
|
||||
from typing import Dict, List, Optional, Tuple
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
from openai import OpenAI
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# Load environment variables
|
||||
load_dotenv()
|
||||
|
||||
class TradingDecisionEngine:
|
||||
"""Enhanced trading decision engine with AI and algorithmic analysis"""
|
||||
|
||||
def __init__(self):
|
||||
self.client = OpenAI(api_key=os.getenv('OPENAI_API_KEY'))
|
||||
|
||||
# Trading signal weights
|
||||
self.signal_weights = {
|
||||
'trend': 0.25,
|
||||
'momentum': 0.20,
|
||||
'volume': 0.15,
|
||||
'volatility': 0.15,
|
||||
'technical': 0.25
|
||||
}
|
||||
|
||||
def get_trading_recommendation(self, symbol: str, analysis: Dict, stock_info: Dict) -> Dict:
|
||||
"""
|
||||
Get comprehensive trading recommendation
|
||||
|
||||
Args:
|
||||
symbol: Stock symbol
|
||||
analysis: Technical analysis results
|
||||
stock_info: Stock information
|
||||
|
||||
Returns:
|
||||
Dictionary with trading recommendation
|
||||
"""
|
||||
try:
|
||||
# Get algorithmic score
|
||||
algo_decision = self._get_algorithmic_decision(analysis)
|
||||
|
||||
# Get AI-powered recommendation
|
||||
ai_decision = self._get_ai_recommendation(symbol, analysis, stock_info)
|
||||
|
||||
# Combine decisions
|
||||
final_decision = self._combine_decisions(algo_decision, ai_decision)
|
||||
|
||||
return {
|
||||
'symbol': symbol,
|
||||
'recommendation': final_decision['action'],
|
||||
'confidence': final_decision['confidence'],
|
||||
'price_target': final_decision.get('price_target'),
|
||||
'stop_loss': final_decision.get('stop_loss'),
|
||||
'reasoning': final_decision['reasoning'],
|
||||
'algorithmic_score': algo_decision['score'],
|
||||
'ai_recommendation': ai_decision['recommendation'],
|
||||
'risk_level': self._assess_risk_level(analysis),
|
||||
'time_horizon': final_decision.get('time_horizon', 'medium'),
|
||||
'key_factors': final_decision.get('key_factors', [])
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
return {
|
||||
'symbol': symbol,
|
||||
'recommendation': 'HOLD',
|
||||
'confidence': 0.5,
|
||||
'reasoning': f'Error in analysis: {str(e)}',
|
||||
'error': str(e)
|
||||
}
|
||||
|
||||
def _get_algorithmic_decision(self, analysis: Dict) -> Dict:
|
||||
"""
|
||||
Generate algorithmic trading decision based on technical indicators
|
||||
|
||||
Args:
|
||||
analysis: Technical analysis results
|
||||
|
||||
Returns:
|
||||
Dictionary with algorithmic decision
|
||||
"""
|
||||
signals = {}
|
||||
|
||||
# Trend signals
|
||||
trend_score = self._calculate_trend_signal(analysis)
|
||||
signals['trend'] = trend_score
|
||||
|
||||
# Momentum signals
|
||||
momentum_score = self._calculate_momentum_signal(analysis)
|
||||
signals['momentum'] = momentum_score
|
||||
|
||||
# Volume signals
|
||||
volume_score = self._calculate_volume_signal(analysis)
|
||||
signals['volume'] = volume_score
|
||||
|
||||
# Volatility signals
|
||||
volatility_score = self._calculate_volatility_signal(analysis)
|
||||
signals['volatility'] = volatility_score
|
||||
|
||||
# Technical indicator signals
|
||||
technical_score = self._calculate_technical_signal(analysis)
|
||||
signals['technical'] = technical_score
|
||||
|
||||
# Calculate weighted score
|
||||
total_score = sum(signals[key] * self.signal_weights[key] for key in signals)
|
||||
|
||||
# Determine action
|
||||
if total_score >= 0.6:
|
||||
action = 'BUY'
|
||||
elif total_score <= -0.6:
|
||||
action = 'SELL'
|
||||
else:
|
||||
action = 'HOLD'
|
||||
|
||||
return {
|
||||
'action': action,
|
||||
'score': total_score,
|
||||
'signals': signals,
|
||||
'confidence': min(abs(total_score), 1.0)
|
||||
}
|
||||
|
||||
def _calculate_trend_signal(self, analysis: Dict) -> float:
|
||||
"""Calculate trend-based signal (-1 to 1)"""
|
||||
score = 0.0
|
||||
|
||||
# Trend direction and strength
|
||||
if analysis.get('trend_direction') == 'upward':
|
||||
score += 0.5
|
||||
elif analysis.get('trend_direction') == 'downward':
|
||||
score -= 0.5
|
||||
|
||||
# Trend strength
|
||||
trend_strength = analysis.get('trend_strength', 0)
|
||||
score *= trend_strength
|
||||
|
||||
# Moving average signals
|
||||
if 'price_vs_sma_20' in analysis:
|
||||
sma_20_signal = analysis['price_vs_sma_20']
|
||||
if sma_20_signal > 2:
|
||||
score += 0.2
|
||||
elif sma_20_signal < -2:
|
||||
score -= 0.2
|
||||
|
||||
if 'price_vs_sma_50' in analysis:
|
||||
sma_50_signal = analysis['price_vs_sma_50']
|
||||
if sma_50_signal > 2:
|
||||
score += 0.3
|
||||
elif sma_50_signal < -2:
|
||||
score -= 0.3
|
||||
|
||||
return max(-1.0, min(1.0, score))
|
||||
|
||||
def _calculate_momentum_signal(self, analysis: Dict) -> float:
|
||||
"""Calculate momentum-based signal (-1 to 1)"""
|
||||
score = 0.0
|
||||
|
||||
# RSI signal
|
||||
if 'rsi' in analysis:
|
||||
rsi = analysis['rsi']
|
||||
if rsi < 30:
|
||||
score += 0.4 # Oversold - potential buy
|
||||
elif rsi > 70:
|
||||
score -= 0.4 # Overbought - potential sell
|
||||
|
||||
# MACD signal
|
||||
if 'macd_trend' in analysis:
|
||||
if analysis['macd_trend'] == 'bullish':
|
||||
score += 0.3
|
||||
else:
|
||||
score -= 0.3
|
||||
|
||||
# Recent performance
|
||||
if 'return_1_week' in analysis:
|
||||
weekly_return = analysis['return_1_week']
|
||||
if weekly_return > 5:
|
||||
score += 0.2
|
||||
elif weekly_return < -5:
|
||||
score -= 0.2
|
||||
|
||||
return max(-1.0, min(1.0, score))
|
||||
|
||||
def _calculate_volume_signal(self, analysis: Dict) -> float:
|
||||
"""Calculate volume-based signal (-1 to 1)"""
|
||||
score = 0.0
|
||||
|
||||
# Volume trend
|
||||
if analysis.get('volume_trend') == 'increasing':
|
||||
score += 0.3
|
||||
elif analysis.get('volume_trend') == 'decreasing':
|
||||
score -= 0.2
|
||||
|
||||
# Volume vs average
|
||||
if 'volume_vs_avg' in analysis:
|
||||
vol_vs_avg = analysis['volume_vs_avg']
|
||||
if vol_vs_avg > 20:
|
||||
score += 0.2
|
||||
elif vol_vs_avg < -20:
|
||||
score -= 0.1
|
||||
|
||||
return max(-1.0, min(1.0, score))
|
||||
|
||||
def _calculate_volatility_signal(self, analysis: Dict) -> float:
|
||||
"""Calculate volatility-based signal (-1 to 1)"""
|
||||
score = 0.0
|
||||
|
||||
# High volatility can be both opportunity and risk
|
||||
volatility = analysis.get('volatility_annualized', 0)
|
||||
|
||||
if volatility > 50:
|
||||
score -= 0.3 # High risk
|
||||
elif volatility < 15:
|
||||
score += 0.2 # Low risk
|
||||
|
||||
# Max drawdown consideration
|
||||
max_drawdown = analysis.get('max_drawdown', 0)
|
||||
if abs(max_drawdown) > 20:
|
||||
score -= 0.2
|
||||
|
||||
return max(-1.0, min(1.0, score))
|
||||
|
||||
def _calculate_technical_signal(self, analysis: Dict) -> float:
|
||||
"""Calculate technical indicator signal (-1 to 1)"""
|
||||
score = 0.0
|
||||
|
||||
# Bollinger Bands
|
||||
if 'bb_position' in analysis:
|
||||
bb_pos = analysis['bb_position']
|
||||
if bb_pos == 'below_lower_band':
|
||||
score += 0.3 # Potential buy
|
||||
elif bb_pos == 'above_upper_band':
|
||||
score -= 0.3 # Potential sell
|
||||
|
||||
# Sharpe ratio
|
||||
sharpe = analysis.get('sharpe_ratio', 0)
|
||||
if sharpe > 1:
|
||||
score += 0.2
|
||||
elif sharpe < 0:
|
||||
score -= 0.2
|
||||
|
||||
return max(-1.0, min(1.0, score))
|
||||
|
||||
def _get_ai_recommendation(self, symbol: str, analysis: Dict, stock_info: Dict) -> Dict:
|
||||
"""
|
||||
Get AI-powered trading recommendation using OpenAI
|
||||
|
||||
Args:
|
||||
symbol: Stock symbol
|
||||
analysis: Technical analysis results
|
||||
stock_info: Stock information
|
||||
|
||||
Returns:
|
||||
Dictionary with AI recommendation
|
||||
"""
|
||||
try:
|
||||
# Prepare analysis data for AI
|
||||
analysis_summary = self._prepare_analysis_for_ai(analysis, stock_info)
|
||||
|
||||
prompt = f"""
|
||||
You are a senior financial analyst with 15+ years of experience providing institutional-grade trading recommendations.
|
||||
Analyze {symbol} and provide a professional trading recommendation.
|
||||
|
||||
Company Information:
|
||||
- Name: {stock_info.get('company_name', 'N/A')}
|
||||
- Sector: {stock_info.get('sector', 'N/A')}
|
||||
- Market Cap: ${stock_info.get('market_cap', 0):,}
|
||||
|
||||
Technical Analysis Data:
|
||||
{analysis_summary}
|
||||
|
||||
REQUIREMENTS:
|
||||
1. Provide BUY/HOLD/SELL recommendation based on technical analysis
|
||||
2. Set realistic confidence level (60-95% range)
|
||||
3. Calculate logical price targets using support/resistance levels
|
||||
4. Set appropriate stop-loss levels (5-15% below entry for long positions)
|
||||
5. Consider risk-reward ratios (minimum 1:2 ratio preferred)
|
||||
6. Provide clear, actionable reasoning without jargon
|
||||
7. Consider market conditions and sector trends
|
||||
|
||||
TRADING STANDARDS:
|
||||
- BUY: Strong upward momentum, good risk/reward, clear catalysts
|
||||
- HOLD: Consolidation phase, mixed signals, or fair value
|
||||
- SELL: Downward trend, poor fundamentals, or overvalued
|
||||
|
||||
Return ONLY valid JSON:
|
||||
{{
|
||||
"recommendation": "BUY/HOLD/SELL",
|
||||
"confidence": 85,
|
||||
"price_target": 150.00,
|
||||
"stop_loss": 120.00,
|
||||
"time_horizon": "short/medium/long",
|
||||
"reasoning": "Professional analysis explaining the recommendation with specific technical factors",
|
||||
"key_factors": ["specific technical indicator", "market condition", "risk factor"],
|
||||
"risk_assessment": "low/medium/high"
|
||||
}}
|
||||
"""
|
||||
|
||||
response = self.client.chat.completions.create(
|
||||
model="gpt-4o-mini",
|
||||
messages=[
|
||||
{"role": "system", "content": "You are an expert financial analyst providing professional trading recommendations."},
|
||||
{"role": "user", "content": prompt}
|
||||
],
|
||||
temperature=0.3,
|
||||
max_tokens=1000
|
||||
)
|
||||
|
||||
# Parse AI response
|
||||
ai_response = response.choices[0].message.content
|
||||
|
||||
if ai_response:
|
||||
try:
|
||||
# Clean the response - extract JSON
|
||||
json_start = ai_response.find('{')
|
||||
json_end = ai_response.rfind('}') + 1
|
||||
|
||||
if json_start != -1 and json_end != -1:
|
||||
json_str = ai_response[json_start:json_end]
|
||||
ai_recommendation = json.loads(json_str)
|
||||
|
||||
# Validate and clean the response
|
||||
return {
|
||||
'recommendation': ai_recommendation.get('recommendation', 'HOLD'),
|
||||
'confidence': ai_recommendation.get('confidence', 50),
|
||||
'price_target': ai_recommendation.get('price_target'),
|
||||
'stop_loss': ai_recommendation.get('stop_loss'),
|
||||
'time_horizon': ai_recommendation.get('time_horizon', 'medium'),
|
||||
'reasoning': ai_recommendation.get('reasoning', 'AI analysis completed'),
|
||||
'key_factors': ai_recommendation.get('key_factors', []),
|
||||
'risk_assessment': ai_recommendation.get('risk_assessment', 'medium')
|
||||
}
|
||||
else:
|
||||
# No JSON found, use fallback
|
||||
return self._parse_ai_response_fallback(ai_response)
|
||||
|
||||
except json.JSONDecodeError:
|
||||
# Fallback parsing
|
||||
return self._parse_ai_response_fallback(ai_response)
|
||||
else:
|
||||
return self._parse_ai_response_fallback('No response received')
|
||||
|
||||
except Exception as e:
|
||||
return {
|
||||
'recommendation': 'HOLD',
|
||||
'confidence': 50,
|
||||
'reasoning': f'AI analysis unavailable: {str(e)}',
|
||||
'error': str(e)
|
||||
}
|
||||
|
||||
def _prepare_analysis_for_ai(self, analysis: Dict, stock_info: Dict) -> str:
|
||||
"""Prepare analysis summary for AI consumption"""
|
||||
summary_parts = []
|
||||
|
||||
# Price metrics
|
||||
current_price = analysis.get('current_price', 0)
|
||||
total_return = analysis.get('total_return_pct', 0)
|
||||
summary_parts.append(f"Current Price: ${current_price:.2f}")
|
||||
summary_parts.append(f"Total Return: {total_return:.2f}%")
|
||||
|
||||
# Trend analysis
|
||||
trend_dir = analysis.get('trend_direction', 'unknown')
|
||||
trend_strength = analysis.get('trend_strength', 0)
|
||||
summary_parts.append(f"Trend: {trend_dir} (strength: {trend_strength:.2f})")
|
||||
|
||||
# Technical indicators
|
||||
if 'rsi' in analysis:
|
||||
rsi = analysis['rsi']
|
||||
rsi_signal = analysis.get('rsi_signal', 'neutral')
|
||||
summary_parts.append(f"RSI: {rsi:.1f} ({rsi_signal})")
|
||||
|
||||
if 'macd_trend' in analysis:
|
||||
summary_parts.append(f"MACD: {analysis['macd_trend']}")
|
||||
|
||||
# Risk metrics
|
||||
volatility = analysis.get('volatility_annualized', 0)
|
||||
max_drawdown = analysis.get('max_drawdown', 0)
|
||||
summary_parts.append(f"Volatility: {volatility:.1f}% (annual)")
|
||||
summary_parts.append(f"Max Drawdown: {max_drawdown:.1f}%")
|
||||
|
||||
# Performance
|
||||
if 'return_1_month' in analysis:
|
||||
monthly_return = analysis['return_1_month']
|
||||
summary_parts.append(f"1-Month Return: {monthly_return:.2f}%")
|
||||
|
||||
return "\n".join(summary_parts)
|
||||
|
||||
def _parse_ai_response_fallback(self, response: str) -> Dict:
|
||||
"""Fallback parser for AI response if JSON parsing fails"""
|
||||
# Simple keyword-based parsing
|
||||
recommendation = 'HOLD'
|
||||
confidence = 50
|
||||
|
||||
response_lower = response.lower()
|
||||
|
||||
if 'buy' in response_lower and 'sell' not in response_lower:
|
||||
recommendation = 'BUY'
|
||||
confidence = 70
|
||||
elif 'sell' in response_lower:
|
||||
recommendation = 'SELL'
|
||||
confidence = 70
|
||||
|
||||
return {
|
||||
'recommendation': recommendation,
|
||||
'confidence': confidence,
|
||||
'reasoning': response,
|
||||
'parsed_fallback': True
|
||||
}
|
||||
|
||||
def _combine_decisions(self, algo_decision: Dict, ai_decision: Dict) -> Dict:
|
||||
"""Combine algorithmic and AI decisions"""
|
||||
# Weight the decisions (60% algorithmic, 40% AI)
|
||||
algo_weight = 0.6
|
||||
ai_weight = 0.4
|
||||
|
||||
# Map recommendations to scores
|
||||
rec_scores = {'BUY': 1, 'HOLD': 0, 'SELL': -1}
|
||||
|
||||
algo_score = rec_scores.get(algo_decision['action'], 0)
|
||||
ai_score = rec_scores.get(ai_decision.get('recommendation', 'HOLD'), 0)
|
||||
|
||||
# Calculate combined score
|
||||
combined_score = (algo_score * algo_weight) + (ai_score * ai_weight)
|
||||
|
||||
# Determine final recommendation
|
||||
if combined_score >= 0.3:
|
||||
final_action = 'BUY'
|
||||
elif combined_score <= -0.3:
|
||||
final_action = 'SELL'
|
||||
else:
|
||||
final_action = 'HOLD'
|
||||
|
||||
# Calculate confidence
|
||||
algo_confidence = algo_decision.get('confidence', 0.5)
|
||||
ai_confidence = ai_decision.get('confidence', 50) / 100
|
||||
combined_confidence = (algo_confidence * algo_weight) + (ai_confidence * ai_weight)
|
||||
|
||||
return {
|
||||
'action': final_action,
|
||||
'confidence': combined_confidence,
|
||||
'combined_score': combined_score,
|
||||
'reasoning': ai_decision.get('reasoning', 'Combined algorithmic and AI analysis'),
|
||||
'price_target': ai_decision.get('price_target'),
|
||||
'stop_loss': ai_decision.get('stop_loss'),
|
||||
'time_horizon': ai_decision.get('time_horizon', 'medium'),
|
||||
'key_factors': ai_decision.get('key_factors', [])
|
||||
}
|
||||
|
||||
def _assess_risk_level(self, analysis: Dict) -> str:
|
||||
"""Assess overall risk level"""
|
||||
risk_score = 0
|
||||
|
||||
# Volatility risk
|
||||
volatility = analysis.get('volatility_annualized', 0)
|
||||
if volatility > 40:
|
||||
risk_score += 2
|
||||
elif volatility > 25:
|
||||
risk_score += 1
|
||||
|
||||
# Drawdown risk
|
||||
max_drawdown = abs(analysis.get('max_drawdown', 0))
|
||||
if max_drawdown > 30:
|
||||
risk_score += 2
|
||||
elif max_drawdown > 15:
|
||||
risk_score += 1
|
||||
|
||||
# Sharpe ratio
|
||||
sharpe = analysis.get('sharpe_ratio', 0)
|
||||
if sharpe < 0:
|
||||
risk_score += 1
|
||||
|
||||
# Determine risk level
|
||||
if risk_score >= 4:
|
||||
return 'high'
|
||||
elif risk_score >= 2:
|
||||
return 'medium'
|
||||
else:
|
||||
return 'low'
|
||||
|
||||
# Global instance for easy import
|
||||
trading_engine = TradingDecisionEngine()
|
||||
|
||||
# Convenience function
|
||||
def get_trading_recommendation(symbol: str, analysis: Dict, stock_info: Dict) -> Dict:
|
||||
"""Convenience function to get trading recommendation"""
|
||||
return trading_engine.get_trading_recommendation(symbol, analysis, stock_info)
|
||||
Reference in New Issue
Block a user