week4: ai stock trading
This commit is contained in:
@@ -1,41 +1,63 @@
|
||||
# 📈 Stock Analysis & Sharia Compliance Tool
|
||||
# 📈 AI Stock Trading & Sharia Compliance Platform
|
||||
|
||||
A comprehensive Gradio-based web application that provides AI-powered stock analysis with Islamic Sharia compliance assessment. This tool combines real-time financial data, technical analysis, and AI-driven insights to help users make informed investment decisions while adhering to Islamic finance principles.
|
||||
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
|
||||
|
||||
## 🌟 Features
|
||||
### Home View
|
||||

|
||||
*Main application interface with market selection and stock input*
|
||||
|
||||
### 📊 **Multi-Period Stock Analysis**
|
||||
- Fetches historical data for 1 month, 1 year, and 5 years
|
||||
- Calculates key financial metrics: returns, volatility, volume, price ranges
|
||||
- Comprehensive technical analysis with statistical insights
|
||||
### Chat Interface
|
||||

|
||||
*Interactive chat for trading advice, Sharia compliance, and stock analysis*
|
||||
|
||||
### 🤖 **AI-Powered Trade Recommendations**
|
||||
- Uses OpenAI GPT-4o-mini for intelligent analysis
|
||||
- Provides clear BUY/HOLD/SELL recommendations
|
||||
- Numerical justification based on multi-timeframe data
|
||||
- Considers risk factors and market trends
|
||||
### Dashboard View
|
||||

|
||||
*Comprehensive dashboard with KPIs, charts, and real-time metrics*
|
||||
|
||||
### ☪️ **Sharia Compliance Assessment**
|
||||
- Analyzes company business activities for Islamic compliance
|
||||
- Provides HALAL/HARAM/DOUBTFUL rulings
|
||||
- Confidence scores (0-100) for each assessment
|
||||
- Detailed justification based on Islamic finance principles
|
||||
## 🎯 Key Features
|
||||
|
||||
### 📈 **Interactive Visualizations**
|
||||
### 📊 **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
|
||||
|
||||
### 🖥️ **User-Friendly Interface**
|
||||
- Clean, modern Gradio web interface
|
||||
- Two-column layout for optimal user experience
|
||||
- Example stock buttons for quick testing
|
||||
- Real-time analysis progress tracking
|
||||
### 🖥️ **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
|
||||
|
||||
@@ -56,83 +78,116 @@ cd ai_stock_trading
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
3. **Set up OpenAI API Key**
|
||||
3. **Set up environment variables**
|
||||
Create a `.env` file in the project root:
|
||||
```bash
|
||||
export OPENAI_API_KEY="your-api-key-here"
|
||||
```
|
||||
|
||||
Or set it in the notebook:
|
||||
```python
|
||||
import os
|
||||
os.environ["OPENAI_API_KEY"] = "your-api-key-here"
|
||||
OPENAI_API_KEY=your-api-key-here
|
||||
```
|
||||
|
||||
### Running the Application
|
||||
|
||||
1. **Open the Jupyter notebook**
|
||||
1. **Launch the Streamlit app**
|
||||
```bash
|
||||
jupyter notebook stock_analysis_sharia_compliance.ipynb
|
||||
streamlit run main_app.py
|
||||
```
|
||||
|
||||
2. **Run all cells** to initialize the functions
|
||||
2. **Access the web interface** at `http://localhost:8501`
|
||||
|
||||
3. **Launch the interface** by running the final cell
|
||||
3. **Select your market** (USA or Egypt) from the sidebar
|
||||
|
||||
4. **Access the web interface** at `http://localhost:7860`
|
||||
4. **Enter a stock symbol** and start analyzing!
|
||||
|
||||
## 📖 How to Use
|
||||
|
||||
1. **Enter a stock ticker** (e.g., AAPL, MSFT, GOOGL) in the input field
|
||||
2. **Click "Analyze Stock"** to start the analysis
|
||||
3. **Review the results**:
|
||||
- **Trade Advice**: AI-generated BUY/HOLD/SELL recommendation
|
||||
- **Sharia Assessment**: Islamic compliance ruling with confidence score
|
||||
- **Price Chart**: 1-month interactive price and volume chart
|
||||
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
|
||||
|
||||
| Ticker | Company | Expected Sharia Status |
|
||||
|--------|---------|----------------------|
|
||||
| **AAPL** | Apple Inc. | ✅ Likely Halal (Technology) |
|
||||
| **MSFT** | Microsoft Corp. | ✅ Likely Halal (Technology) |
|
||||
| **JNJ** | Johnson & Johnson | ✅ Likely Halal (Healthcare) |
|
||||
| **BAC** | Bank of America | ❌ Likely Haram (Banking/Interest) |
|
||||
| **KO** | Coca-Cola | ⚠️ May be Doubtful |
|
||||
#### 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 |
|
||||
|
||||
## 🛠️ Technical Implementation
|
||||
#### 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 |
|
||||
|
||||
### Core Components
|
||||
## 🔧 Technical Implementation
|
||||
|
||||
1. **Data Fetching Tool** (`fetch_history`)
|
||||
- Uses yfinance API for real-time stock data
|
||||
- Supports multiple time periods and intervals
|
||||
- Error handling for invalid tickers
|
||||
### Modular Architecture
|
||||
|
||||
2. **Analysis Tool** (`summarize`)
|
||||
- Calculates financial metrics
|
||||
- Annualized volatility calculation
|
||||
- Price performance analysis
|
||||
The platform is built with a clean, modular architecture using separate tool modules:
|
||||
|
||||
3. **Trade Decision Tool** (`get_trade_advice`)
|
||||
- OpenAI GPT-4o-mini integration
|
||||
- Multi-period analysis prompts
|
||||
- Structured recommendation format
|
||||
#### 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
|
||||
|
||||
4. **Sharia Compliance Tool** (`assess_sharia`)
|
||||
- Company profile extraction
|
||||
- Islamic finance criteria evaluation
|
||||
- Confidence scoring system
|
||||
#### 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
|
||||
|
||||
5. **Charting Tool** (`plot_price`)
|
||||
- Matplotlib-based visualizations
|
||||
- Price and volume charts
|
||||
- Professional styling
|
||||
#### 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
|
||||
|
||||
### AI Prompts
|
||||
#### 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
|
||||
|
||||
The application uses carefully crafted prompts for:
|
||||
- **Financial Analysis**: Multi-timeframe technical analysis with numerical justification
|
||||
- **Sharia Assessment**: Islamic finance principles evaluation with scholarly approach
|
||||
#### 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
|
||||
|
||||
@@ -227,6 +282,27 @@ Contributions are welcome! Please feel free to submit issues, feature requests,
|
||||
- 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:
|
||||
|
||||
549
week4/community-contributions/ai_stock_trading/main_app.py
Normal file
549
week4/community-contributions/ai_stock_trading/main_app.py
Normal file
@@ -0,0 +1,549 @@
|
||||
"""
|
||||
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
|
||||
|
||||
# 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):
|
||||
try:
|
||||
country = st.session_state.selected_country
|
||||
data = stock_fetcher.fetch_stock_data(symbol, period="1y")
|
||||
stock_info = stock_fetcher.get_stock_info(symbol, country)
|
||||
analysis = stock_analyzer.analyze_stock(data)
|
||||
trading_decision = trading_engine.get_trading_recommendation(symbol, analysis, stock_info)
|
||||
sharia_compliance = sharia_checker.check_sharia_compliance(symbol, stock_info, analysis)
|
||||
|
||||
st.session_state.stock_data[symbol] = {
|
||||
'data': data,
|
||||
'stock_info': stock_info,
|
||||
'analysis': analysis,
|
||||
'trading_decision': trading_decision,
|
||||
'sharia_compliance': sharia_compliance
|
||||
}
|
||||
except Exception as e:
|
||||
st.error(f"Error loading analysis for {symbol}: {str(e)}")
|
||||
|
||||
def render_chat_page(self):
|
||||
st.header("💬 AI Stock Analysis Chat")
|
||||
|
||||
if not st.session_state.selected_stock:
|
||||
st.warning("⚠️ Please select a stock from the sidebar to start chatting.")
|
||||
return
|
||||
|
||||
symbol = st.session_state.selected_stock
|
||||
st.info(f"💬 Chatting about: **{symbol}**")
|
||||
|
||||
if symbol not in st.session_state.stock_data:
|
||||
with st.spinner("Loading stock data and analysis..."):
|
||||
self.load_stock_analysis(symbol)
|
||||
|
||||
self.render_chat_interface()
|
||||
|
||||
def render_chat_interface(self):
|
||||
symbol = st.session_state.selected_stock
|
||||
|
||||
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:
|
||||
welcome_msg = f"""
|
||||
👋 Hello! I'm your AI stock analysis assistant. I can help you with:
|
||||
|
||||
• **Technical Analysis** of {symbol}
|
||||
• **Trading Recommendations** (Buy/Hold/Sell)
|
||||
• **Sharia Compliance** assessment
|
||||
• **Risk Analysis** and market insights
|
||||
|
||||
What would you like to know about {symbol}?
|
||||
"""
|
||||
st.chat_message("assistant").write(welcome_msg)
|
||||
|
||||
user_input = st.chat_input("Ask me anything about this stock...")
|
||||
|
||||
if user_input:
|
||||
st.session_state.chat_history.append({'role': 'user', 'content': user_input})
|
||||
|
||||
with st.spinner("Analyzing..."):
|
||||
ai_response = self.generate_ai_response(user_input, symbol)
|
||||
|
||||
st.session_state.chat_history.append({'role': 'assistant', 'content': ai_response})
|
||||
st.rerun()
|
||||
|
||||
st.subheader("🚀 Quick Actions")
|
||||
col1, col2, col3, col4 = st.columns(4)
|
||||
|
||||
with col1:
|
||||
if st.button("📊 Get Analysis"):
|
||||
self.add_analysis_to_chat(symbol)
|
||||
st.rerun()
|
||||
|
||||
with col2:
|
||||
if st.button("💰 Trading Rec"):
|
||||
self.add_trading_to_chat(symbol)
|
||||
st.rerun()
|
||||
|
||||
with col3:
|
||||
if st.button("☪️ Sharia Check"):
|
||||
self.add_sharia_to_chat(symbol)
|
||||
st.rerun()
|
||||
|
||||
with col4:
|
||||
if st.button("🎯 Price Target"):
|
||||
self.add_target_to_chat(symbol)
|
||||
st.rerun()
|
||||
|
||||
def generate_ai_response(self, user_input: str, symbol: str) -> str:
|
||||
try:
|
||||
from openai import OpenAI
|
||||
client = OpenAI(api_key=os.getenv('OPENAI_API_KEY'))
|
||||
|
||||
# Check if user is asking about Sharia compliance
|
||||
sharia_keywords = ['sharia', 'halal', 'haram', 'islamic', 'muslim', 'compliant', 'permissible', 'forbidden']
|
||||
is_sharia_query = any(keyword in user_input.lower() for keyword in sharia_keywords)
|
||||
|
||||
stock_data = st.session_state.stock_data.get(symbol, {})
|
||||
analysis = stock_data.get('analysis', {})
|
||||
trading_decision = stock_data.get('trading_decision', {})
|
||||
stock_info = stock_data.get('stock_info', {})
|
||||
country = st.session_state.selected_country
|
||||
|
||||
# Format price with proper currency
|
||||
current_price = analysis.get('current_price', 0)
|
||||
formatted_price = stock_fetcher.format_price_with_currency(current_price, country)
|
||||
|
||||
# Base context without Sharia info
|
||||
context = f"""
|
||||
You are analyzing {symbol} ({stock_info.get('company_name', 'N/A')}).
|
||||
|
||||
Current Price: {formatted_price}
|
||||
Return: {analysis.get('total_return_pct', 0):.2f}%
|
||||
Recommendation: {trading_decision.get('recommendation', 'N/A')}
|
||||
Sector: {stock_info.get('sector', 'N/A')}
|
||||
|
||||
User Question: {user_input}
|
||||
|
||||
Provide helpful analysis based on the available data.
|
||||
"""
|
||||
|
||||
# Add Sharia context only if user asks about it
|
||||
if is_sharia_query:
|
||||
# Load Sharia compliance if not already loaded
|
||||
if symbol not in st.session_state.stock_data or 'sharia_compliance' not in st.session_state.stock_data[symbol]:
|
||||
with st.spinner("Loading Sharia compliance analysis..."):
|
||||
self.load_stock_analysis(symbol)
|
||||
|
||||
sharia_compliance = st.session_state.stock_data.get(symbol, {}).get('sharia_compliance', {})
|
||||
context += f"""
|
||||
|
||||
SHARIA COMPLIANCE ANALYSIS:
|
||||
Ruling: {sharia_compliance.get('ruling', 'N/A')}
|
||||
Confidence: {sharia_compliance.get('confidence', 0)*100:.0f}%
|
||||
Reasoning: {sharia_compliance.get('reasoning', 'N/A')}
|
||||
|
||||
Focus your response on Islamic finance principles and Sharia compliance.
|
||||
"""
|
||||
|
||||
response = client.chat.completions.create(
|
||||
model="gpt-4o-mini",
|
||||
messages=[
|
||||
{"role": "system", "content": "You are a financial advisor and Islamic finance expert."},
|
||||
{"role": "user", "content": context}
|
||||
],
|
||||
temperature=0.7,
|
||||
max_tokens=400
|
||||
)
|
||||
|
||||
return response.choices[0].message.content
|
||||
|
||||
except Exception as e:
|
||||
return f"Sorry, I'm having trouble right now. Error: {str(e)}"
|
||||
|
||||
def add_analysis_to_chat(self, symbol: str):
|
||||
stock_data = st.session_state.stock_data.get(symbol, {})
|
||||
analysis = stock_data.get('analysis', {})
|
||||
|
||||
if analysis:
|
||||
summary = stock_analyzer.get_analysis_summary(analysis)
|
||||
st.session_state.chat_history.append({
|
||||
'role': 'assistant',
|
||||
'content': f"📊 **Analysis Summary for {symbol}:**\n\n{summary}"
|
||||
})
|
||||
|
||||
def add_trading_to_chat(self, symbol: str):
|
||||
stock_data = st.session_state.stock_data.get(symbol, {})
|
||||
trading_decision = stock_data.get('trading_decision', {})
|
||||
stock_info = stock_data.get('stock_info', {})
|
||||
country = st.session_state.selected_country
|
||||
|
||||
if trading_decision:
|
||||
rec = trading_decision.get('recommendation', 'HOLD')
|
||||
conf = trading_decision.get('confidence', 0)
|
||||
|
||||
# Handle confidence as percentage if it's already 0-100, or as decimal if 0-1
|
||||
if conf <= 1.0:
|
||||
conf_pct = conf * 100
|
||||
else:
|
||||
conf_pct = conf
|
||||
|
||||
reason = trading_decision.get('reasoning', 'No reasoning available')
|
||||
price_target = trading_decision.get('price_target')
|
||||
stop_loss = trading_decision.get('stop_loss')
|
||||
time_horizon = trading_decision.get('time_horizon', 'medium')
|
||||
risk_level = trading_decision.get('risk_level', 'medium')
|
||||
|
||||
# Clean reasoning - remove JSON artifacts
|
||||
if reason.startswith('```json') or reason.startswith('{'):
|
||||
# Extract readable content from malformed JSON
|
||||
if 'reasoning' in reason:
|
||||
try:
|
||||
import re
|
||||
reasoning_match = re.search(r'"reasoning"\s*:\s*"([^"]+)"', reason)
|
||||
if reasoning_match:
|
||||
reason = reasoning_match.group(1)
|
||||
else:
|
||||
reason = "Technical analysis suggests this recommendation based on current market conditions."
|
||||
except:
|
||||
reason = "Technical analysis suggests this recommendation based on current market conditions."
|
||||
|
||||
# Format the message professionally
|
||||
message_parts = [
|
||||
f"💰 **Trading Recommendation: {rec}**",
|
||||
f"📊 **Confidence Level:** {conf_pct:.0f}%",
|
||||
f"⏱️ **Time Horizon:** {time_horizon.title()}-term",
|
||||
f"⚠️ **Risk Level:** {risk_level.title()}",
|
||||
"",
|
||||
f"**Analysis:**",
|
||||
reason
|
||||
]
|
||||
|
||||
# Add price targets if available
|
||||
if price_target:
|
||||
formatted_target = stock_fetcher.format_price_with_currency(price_target, country)
|
||||
message_parts.append(f"🎯 **Price Target:** {formatted_target}")
|
||||
|
||||
if stop_loss:
|
||||
formatted_stop = stock_fetcher.format_price_with_currency(stop_loss, country)
|
||||
message_parts.append(f"🛡️ **Stop Loss:** {formatted_stop}")
|
||||
|
||||
message_parts.append("")
|
||||
message_parts.append("*This is not financial advice. Please do your own research and consult with a financial advisor.*")
|
||||
|
||||
message = "\n".join(message_parts)
|
||||
st.session_state.chat_history.append({'role': 'assistant', 'content': message})
|
||||
|
||||
def add_sharia_to_chat(self, symbol: str):
|
||||
stock_data = st.session_state.stock_data.get(symbol, {})
|
||||
sharia_compliance = stock_data.get('sharia_compliance', {})
|
||||
|
||||
if sharia_compliance:
|
||||
summary = sharia_checker.get_compliance_summary(sharia_compliance)
|
||||
st.session_state.chat_history.append({
|
||||
'role': 'assistant',
|
||||
'content': f"☪️ **Sharia Compliance:**\n\n{summary}"
|
||||
})
|
||||
|
||||
def add_target_to_chat(self, symbol: str):
|
||||
stock_data = st.session_state.stock_data.get(symbol, {})
|
||||
trading_decision = stock_data.get('trading_decision', {})
|
||||
analysis = stock_data.get('analysis', {})
|
||||
|
||||
current = analysis.get('current_price', 0)
|
||||
target = trading_decision.get('price_target')
|
||||
stop = trading_decision.get('stop_loss')
|
||||
|
||||
message = f"🎯 **Current Price:** ${current:.2f}\n"
|
||||
if target:
|
||||
upside = ((target - current) / current) * 100
|
||||
message += f"**Target:** ${target:.2f} ({upside:+.1f}%)\n"
|
||||
if stop:
|
||||
downside = ((stop - current) / current) * 100
|
||||
message += f"**Stop Loss:** ${stop:.2f} ({downside:+.1f}%)"
|
||||
|
||||
st.session_state.chat_history.append({'role': 'assistant', 'content': message})
|
||||
|
||||
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
|
||||
|
||||
if symbol not in st.session_state.stock_data:
|
||||
with st.spinner("Loading analysis..."):
|
||||
self.load_stock_analysis(symbol)
|
||||
|
||||
stock_data = st.session_state.stock_data.get(symbol, {})
|
||||
if not stock_data:
|
||||
st.error("Failed to load data.")
|
||||
return
|
||||
|
||||
analysis = stock_data.get('analysis', {})
|
||||
trading_decision = stock_data.get('trading_decision', {})
|
||||
sharia_compliance = stock_data.get('sharia_compliance', {})
|
||||
data = stock_data.get('data')
|
||||
|
||||
# KPIs at the top
|
||||
col1, col2, col3, col4, col5 = st.columns(5)
|
||||
|
||||
with col1:
|
||||
current_price = data['Close'].iloc[-1]
|
||||
formatted_price = stock_fetcher.format_price_with_currency(current_price, country)
|
||||
st.metric("💰 Current Price", formatted_price)
|
||||
|
||||
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) * 100
|
||||
st.metric("Recommendation", rec, f"{conf:.0f}% confidence")
|
||||
|
||||
with col4:
|
||||
ruling = sharia_compliance.get('ruling', 'UNCERTAIN')
|
||||
sharia_conf = sharia_compliance.get('confidence', 0) * 100
|
||||
st.metric("Sharia Status", ruling, f"{sharia_conf:.0f}% confidence")
|
||||
|
||||
with col5:
|
||||
volatility = analysis.get('volatility_annualized', 0)
|
||||
st.metric("Volatility", f"{volatility:.1f}%")
|
||||
|
||||
st.divider()
|
||||
|
||||
# Charts section
|
||||
|
||||
# First row: Risk Analysis and Trading Signals
|
||||
col1, col2 = st.columns(2)
|
||||
|
||||
with col1:
|
||||
risk_fig = chart_generator.create_risk_analysis_chart(analysis, symbol)
|
||||
st.plotly_chart(risk_fig, use_container_width=True)
|
||||
|
||||
with col2:
|
||||
signals_fig = chart_generator.create_trading_signals_chart(data, analysis, trading_decision, symbol)
|
||||
st.plotly_chart(signals_fig, use_container_width=True)
|
||||
|
||||
# Second row: Price Chart (full width)
|
||||
price_fig = chart_generator.create_price_chart(data, symbol, analysis)
|
||||
st.plotly_chart(price_fig, use_container_width=True)
|
||||
|
||||
|
||||
|
||||
def main():
|
||||
app = StockTradingApp()
|
||||
app.run()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -2,7 +2,9 @@ yfinance>=0.2.10
|
||||
openai>=1.0.0
|
||||
pandas>=1.5.0
|
||||
matplotlib>=3.5.0
|
||||
gradio>=4.0.0
|
||||
streamlit>=1.28.0
|
||||
requests>=2.28.0
|
||||
beautifulsoup4>=4.11.0
|
||||
numpy>=1.21.0
|
||||
numpy>=1.21.0
|
||||
python-dotenv>=1.0.0
|
||||
plotly>=5.15.0
|
||||
File diff suppressed because one or more lines are too long
@@ -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