feat: Add autonomous trading system with IBKR integration
- Implement IBKR connector for live portfolio monitoring - Add multi-source data aggregator (congressional trades, news, insider trading) - Create AI-powered signal processor with TradingAgents integration - Build multi-channel alert system (Discord, Telegram, Email) - Set up automated scheduler for 24/7 monitoring - Add comprehensive configuration and safety controls - Include portfolio analysis tools for IBKR positions This system monitors markets continuously, tracks congressional trades, and provides actionable trading signals with specific entry/exit prices.
This commit is contained in:
parent
32be17c606
commit
22ff8d8a4f
|
|
@ -0,0 +1,282 @@
|
|||
# 🤖 Autonomous Trading System
|
||||
|
||||
A 24/7 intelligent trading system that monitors your IBKR portfolio, analyzes multiple data sources, and provides actionable trading signals.
|
||||
|
||||
## 🚀 Features
|
||||
|
||||
### Real-Time Monitoring
|
||||
- **IBKR Portfolio Sync** - Live connection to your Interactive Brokers account
|
||||
- **Position Tracking** - Monitor P&L, cost basis, and performance
|
||||
- **Risk Management** - Automatic alerts for position limits and losses
|
||||
|
||||
### Multi-Source Intelligence
|
||||
- **Congressional Trades** - Track politician stock trades via QuiverQuant
|
||||
- **Insider Trading** - Monitor SEC filings and insider activity
|
||||
- **Market Sentiment** - News analysis and social media sentiment
|
||||
- **Technical Analysis** - Support/resistance, RSI, moving averages
|
||||
- **AI Analysis** - TradingAgents multi-agent evaluation
|
||||
|
||||
### Smart Alerts
|
||||
- **Trading Signals** - Specific entry/exit prices with confidence scores
|
||||
- **Risk Warnings** - Position concentration and loss alerts
|
||||
- **Opportunity Detection** - Congressional trades matching your portfolio
|
||||
- **Multi-Channel** - Discord, Telegram, Email notifications
|
||||
|
||||
## 📋 Prerequisites
|
||||
|
||||
1. **Interactive Brokers Account** with TWS or IB Gateway
|
||||
2. **API Keys**:
|
||||
- OpenAI API key (required)
|
||||
- Alpha Vantage API key (required)
|
||||
- QuiverQuant API key (optional, for congressional trades)
|
||||
- Discord Webhook URL (optional, for alerts)
|
||||
|
||||
## 🛠️ Installation
|
||||
|
||||
### 1. Install Dependencies
|
||||
|
||||
```bash
|
||||
# Install autonomous system requirements
|
||||
pip install -r requirements_autonomous.txt
|
||||
|
||||
# Install base TradingAgents requirements
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
### 2. Configure IBKR
|
||||
|
||||
1. Open TWS or IB Gateway
|
||||
2. Enable API connections:
|
||||
- File → Global Configuration → API → Settings
|
||||
- Enable "Enable ActiveX and Socket Clients"
|
||||
- Add "127.0.0.1" to trusted IPs
|
||||
3. Note the port:
|
||||
- TWS Paper: 7497
|
||||
- TWS Live: 7496
|
||||
- IB Gateway Paper: 4002
|
||||
- IB Gateway Live: 4001
|
||||
|
||||
### 3. Set Environment Variables
|
||||
|
||||
Create or update `.env` file:
|
||||
|
||||
```bash
|
||||
# === REQUIRED ===
|
||||
OPENAI_API_KEY=your-openai-key
|
||||
ALPHA_VANTAGE_API_KEY=your-alpha-vantage-key
|
||||
|
||||
# === IBKR Settings ===
|
||||
IBKR_HOST=127.0.0.1
|
||||
IBKR_PORT=7497 # Paper trading port
|
||||
IBKR_CLIENT_ID=1
|
||||
|
||||
# === Optional Data Sources ===
|
||||
QUIVER_API_KEY=your-quiver-key # For congressional trades
|
||||
POLYGON_API_KEY=your-polygon-key # For real-time data
|
||||
NEWS_API_KEY=your-news-api-key # For news aggregation
|
||||
|
||||
# === Notifications (at least one recommended) ===
|
||||
DISCORD_WEBHOOK_URL=https://discord.com/api/webhooks/...
|
||||
TELEGRAM_BOT_TOKEN=your-telegram-bot-token
|
||||
TELEGRAM_CHAT_ID=your-chat-id
|
||||
|
||||
# === Trading Settings ===
|
||||
TRADING_ENABLED=false # Set to true to enable trading
|
||||
PAPER_TRADING=true # Use paper account
|
||||
MAX_POSITION_SIZE=0.20 # Max 20% per position
|
||||
CONFIDENCE_THRESHOLD=70 # Min confidence for trades
|
||||
```
|
||||
|
||||
## 🎯 Quick Start
|
||||
|
||||
### 1. Test Connection
|
||||
|
||||
```bash
|
||||
# Test IBKR connection
|
||||
python -c "from autonomous.ibkr_connector import IBKRConnector; import asyncio; asyncio.run(IBKRConnector().connect())"
|
||||
```
|
||||
|
||||
### 2. Start Monitoring (Safe Mode)
|
||||
|
||||
```bash
|
||||
# Start with monitoring only (no trading)
|
||||
TRADING_ENABLED=false python autonomous_trader.py
|
||||
```
|
||||
|
||||
### 3. Paper Trading
|
||||
|
||||
```bash
|
||||
# Test with paper account
|
||||
PAPER_TRADING=true TRADING_ENABLED=true python autonomous_trader.py
|
||||
```
|
||||
|
||||
### 4. Live Trading (⚠️ Use with caution!)
|
||||
|
||||
```bash
|
||||
# Live trading - BE VERY CAREFUL
|
||||
PAPER_TRADING=false TRADING_ENABLED=true python autonomous_trader.py
|
||||
```
|
||||
|
||||
## 📊 System Architecture
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────┐
|
||||
│ AUTONOMOUS TRADER │
|
||||
├─────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
|
||||
│ │ IBKR │ │ Data │ │ AI │ │
|
||||
│ │ Connector│ │Aggregator│ │ Analysis │ │
|
||||
│ └─────┬────┘ └─────┬────┘ └─────┬────┘ │
|
||||
│ └─────────────┼─────────────┘ │
|
||||
│ │ │
|
||||
│ ┌───────▼────────┐ │
|
||||
│ │Signal Processor│ │
|
||||
│ └───────┬────────┘ │
|
||||
│ │ │
|
||||
│ ┌────────────┼────────────┐ │
|
||||
│ │ │ │ │
|
||||
│ ┌────▼───┐ ┌────▼───┐ ┌────▼───┐ │
|
||||
│ │ Alerts │ │ Risk │ │ Orders │ │
|
||||
│ │ Engine │ │ Manager│ │(Future)│ │
|
||||
│ └────────┘ └────────┘ └────────┘ │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## 📱 Alert Examples
|
||||
|
||||
### Trading Signal Alert
|
||||
```
|
||||
🎯 ACTION: BUY
|
||||
📈 TICKER: NVDA
|
||||
💰 ENTRY: $132.50 - $133.00
|
||||
🎯 TARGET 1: $138.00 (+4.1%)
|
||||
🛑 STOP LOSS: $129.00 (-2.6%)
|
||||
📊 CONFIDENCE: 85%
|
||||
📝 REASONING:
|
||||
• Congressional buying detected
|
||||
• Strong technical breakout
|
||||
• Positive earnings momentum
|
||||
```
|
||||
|
||||
### Risk Warning Alert
|
||||
```
|
||||
⚠️ Risk Alert: AVGO
|
||||
AVGO is down 5.2%
|
||||
Current: $912.45
|
||||
P&L: -$2,145.00
|
||||
Consider stop loss or hedging
|
||||
```
|
||||
|
||||
## 🕐 Schedule
|
||||
|
||||
The system runs these tasks automatically:
|
||||
|
||||
| Task | Frequency | Description |
|
||||
|------|-----------|-------------|
|
||||
| Portfolio Sync | 5 min | Update positions and P&L |
|
||||
| Market Scan | 15 min | Look for opportunities |
|
||||
| Congressional Check | 1 hour | New politician trades |
|
||||
| News Monitor | 30 min | Sentiment analysis |
|
||||
| Risk Check | 30 min | Portfolio risk assessment |
|
||||
| Daily Summary | 4:30 PM | End of day report |
|
||||
|
||||
## 🛡️ Safety Features
|
||||
|
||||
1. **Position Limits** - Max 20% in any single position
|
||||
2. **Daily Loss Limit** - Stops at 5% daily loss
|
||||
3. **Paper Trading Mode** - Test without real money
|
||||
4. **Manual Confirmation** - Required for large trades
|
||||
5. **Stop Loss** - Automatic stop loss recommendations
|
||||
6. **Circuit Breakers** - Halts during extreme volatility
|
||||
|
||||
## 🔧 Customization
|
||||
|
||||
### Add Custom Indicators
|
||||
|
||||
Edit `autonomous/signal_processor.py`:
|
||||
|
||||
```python
|
||||
async def calculate_custom_indicator(self, ticker: str):
|
||||
# Your custom logic here
|
||||
pass
|
||||
```
|
||||
|
||||
### Modify Alert Channels
|
||||
|
||||
Edit `autonomous/alert_engine.py`:
|
||||
|
||||
```python
|
||||
async def _send_custom_channel(self, title, message):
|
||||
# Your custom notification method
|
||||
pass
|
||||
```
|
||||
|
||||
### Change Trading Rules
|
||||
|
||||
Edit `autonomous/config/settings.py`:
|
||||
|
||||
```python
|
||||
MAX_POSITION_SIZE = 0.15 # 15% instead of 20%
|
||||
CONFIDENCE_THRESHOLD = 80 # Require 80% confidence
|
||||
```
|
||||
|
||||
## 📈 Performance Monitoring
|
||||
|
||||
View logs and metrics:
|
||||
|
||||
```bash
|
||||
# View real-time logs
|
||||
tail -f autonomous_trader.log
|
||||
|
||||
# Check alert history
|
||||
python -c "from autonomous.alert_engine import AlertEngine; print(AlertEngine().alert_history)"
|
||||
```
|
||||
|
||||
## 🐛 Troubleshooting
|
||||
|
||||
### IBKR Connection Issues
|
||||
- Ensure TWS/Gateway is running
|
||||
- Check API settings are enabled
|
||||
- Verify port number matches config
|
||||
- Check firewall isn't blocking connection
|
||||
|
||||
### No Alerts Received
|
||||
- Verify at least one notification channel is configured
|
||||
- Check webhook URLs are correct
|
||||
- Test with console output first
|
||||
|
||||
### High API Usage
|
||||
- Reduce `MARKET_SCAN_INTERVAL` in config
|
||||
- Use fewer models or smaller LLMs
|
||||
- Implement caching for repeated queries
|
||||
|
||||
## ⚠️ Disclaimer
|
||||
|
||||
**This system is for educational and research purposes only.**
|
||||
|
||||
- Trading involves substantial risk of loss
|
||||
- Past performance doesn't guarantee future results
|
||||
- Always do your own research
|
||||
- Start with paper trading
|
||||
- Never risk more than you can afford to lose
|
||||
|
||||
## 📚 Resources
|
||||
|
||||
- [IBKR API Documentation](https://interactivebrokers.github.io/)
|
||||
- [QuiverQuant API](https://www.quiverquant.com/api)
|
||||
- [TradingAgents Documentation](../README.md)
|
||||
- [Discord Webhooks Guide](https://discord.com/developers/docs/resources/webhook)
|
||||
|
||||
## 🤝 Support
|
||||
|
||||
For issues or questions:
|
||||
1. Check the logs: `autonomous_trader.log`
|
||||
2. Review configuration in `.env`
|
||||
3. Test components individually
|
||||
4. Open an issue with error details
|
||||
|
||||
---
|
||||
|
||||
**Remember**: Start small, test thoroughly, and never trade with money you can't afford to lose! 🎯
|
||||
|
|
@ -0,0 +1,272 @@
|
|||
# 🤖 Autonomous Trading Intelligence System
|
||||
|
||||
## System Architecture Overview
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ AUTONOMOUS TRADING BRAIN │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
|
||||
│ │ IBKR Live │ │ Market Data │ │ Alternative │ │
|
||||
│ │ Integration │ │ Aggregator │ │ Data Sources │ │
|
||||
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
|
||||
│ │ │ │ │
|
||||
│ └──────────────────┼──────────────────┘ │
|
||||
│ │ │
|
||||
│ ┌──────▼───────┐ │
|
||||
│ │ AI BRAIN │ │
|
||||
│ │ (TradingAgents│ │
|
||||
│ │ + Custom) │ │
|
||||
│ └──────┬───────┘ │
|
||||
│ │ │
|
||||
│ ┌─────────────────┼─────────────────┐ │
|
||||
│ │ │ │ │
|
||||
│ ┌────▼────┐ ┌─────▼─────┐ ┌─────▼─────┐ │
|
||||
│ │Position │ │ Risk Mgmt │ │ Alert │ │
|
||||
│ │Manager │ │ Engine │ │ System │ │
|
||||
│ └─────────┘ └───────────┘ └───────────┘ │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## 1. Core Components
|
||||
|
||||
### A. IBKR Live Integration Module
|
||||
```python
|
||||
# Key Features:
|
||||
- Real-time portfolio sync via IB Gateway/TWS API
|
||||
- Position tracking (shares, P&L, cost basis)
|
||||
- Order execution capability (with safety controls)
|
||||
- Account balance and margin monitoring
|
||||
- Historical trade analysis
|
||||
```
|
||||
|
||||
### B. Data Aggregation Pipeline
|
||||
```python
|
||||
DATA_SOURCES = {
|
||||
"market_data": {
|
||||
"real_time": ["IEX Cloud", "Polygon.io", "AlphaVantage Premium"],
|
||||
"historical": ["yfinance", "IBKR API"]
|
||||
},
|
||||
"alternative_data": {
|
||||
"congressional_trades": ["CapitolTrades API", "QuiverQuant"],
|
||||
"insider_trading": ["SEC EDGAR", "OpenInsider API"],
|
||||
"social_sentiment": ["Reddit API", "Twitter/X API", "StockTwits"],
|
||||
"news": ["NewsAPI", "Benzinga", "Bloomberg Terminal"],
|
||||
"earnings": ["AlphaVantage", "Yahoo Finance", "Earnings Whispers"],
|
||||
"options_flow": ["FlowAlgo", "Unusual Whales API"],
|
||||
"institutional": ["13F filings", "WhaleWisdom API"]
|
||||
},
|
||||
"economic_data": {
|
||||
"fed": ["FRED API"],
|
||||
"macro": ["TradingEconomics", "World Bank API"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### C. Autonomous Monitoring System
|
||||
```python
|
||||
MONITORING_INTERVALS = {
|
||||
"portfolio_health": "5 minutes",
|
||||
"market_movers": "15 minutes",
|
||||
"news_scan": "30 minutes",
|
||||
"congressional_trades": "1 hour",
|
||||
"earnings_calendar": "daily",
|
||||
"technical_analysis": "1 hour",
|
||||
"risk_assessment": "30 minutes"
|
||||
}
|
||||
```
|
||||
|
||||
## 2. Implementation Plan
|
||||
|
||||
### Phase 1: Foundation (Week 1-2)
|
||||
- [ ] Set up IBKR API connection using ib_insync
|
||||
- [ ] Create database (PostgreSQL/TimescaleDB) for historical data
|
||||
- [ ] Build basic portfolio monitoring dashboard
|
||||
- [ ] Implement core data fetching modules
|
||||
|
||||
### Phase 2: Intelligence Layer (Week 3-4)
|
||||
- [ ] Integrate TradingAgents with continuous monitoring
|
||||
- [ ] Add custom AI agents for specific strategies
|
||||
- [ ] Implement pattern recognition system
|
||||
- [ ] Create backtesting framework
|
||||
|
||||
### Phase 3: Alerting & Automation (Week 5-6)
|
||||
- [ ] Build multi-channel alert system (Discord/Telegram/Email)
|
||||
- [ ] Create trading signal generator
|
||||
- [ ] Implement paper trading mode
|
||||
- [ ] Add risk management rules
|
||||
|
||||
### Phase 4: Advanced Features (Week 7-8)
|
||||
- [ ] Congressional trade mirroring alerts
|
||||
- [ ] Earnings play recommendations
|
||||
- [ ] Options strategy suggestions
|
||||
- [ ] Portfolio rebalancing recommendations
|
||||
|
||||
## 3. Key Modules to Build
|
||||
|
||||
### A. Portfolio Monitor (`portfolio_monitor.py`)
|
||||
```python
|
||||
class PortfolioMonitor:
|
||||
def __init__(self):
|
||||
self.ibkr_client = IBKRClient()
|
||||
self.positions = {}
|
||||
self.alerts = []
|
||||
|
||||
async def sync_portfolio(self):
|
||||
"""Sync with IBKR every 5 minutes"""
|
||||
|
||||
async def calculate_metrics(self):
|
||||
"""Calculate P&L, exposure, risk metrics"""
|
||||
|
||||
async def generate_recommendations(self):
|
||||
"""AI-powered buy/sell recommendations"""
|
||||
```
|
||||
|
||||
### B. Market Scanner (`market_scanner.py`)
|
||||
```python
|
||||
class MarketScanner:
|
||||
def __init__(self):
|
||||
self.scanners = {
|
||||
"momentum": MomentumScanner(),
|
||||
"value": ValueScanner(),
|
||||
"breakout": BreakoutScanner(),
|
||||
"insider": InsiderScanner(),
|
||||
"congressional": CongressionalScanner()
|
||||
}
|
||||
|
||||
async def scan_opportunities(self):
|
||||
"""Continuous market scanning"""
|
||||
|
||||
async def rank_opportunities(self):
|
||||
"""AI-powered opportunity ranking"""
|
||||
```
|
||||
|
||||
### C. Alert Engine (`alert_engine.py`)
|
||||
```python
|
||||
class AlertEngine:
|
||||
def __init__(self):
|
||||
self.channels = {
|
||||
"discord": DiscordBot(),
|
||||
"telegram": TelegramBot(),
|
||||
"email": EmailNotifier(),
|
||||
"sms": TwilioSMS()
|
||||
}
|
||||
|
||||
async def send_alert(self, alert_type, message, priority):
|
||||
"""Multi-channel alert distribution"""
|
||||
```
|
||||
|
||||
## 4. Alert Types & Actions
|
||||
|
||||
### 🚨 CRITICAL ALERTS (Immediate Action)
|
||||
- Stop loss triggers
|
||||
- Margin calls
|
||||
- Extreme volatility in holdings
|
||||
- Major news affecting positions
|
||||
|
||||
### 📊 TRADING SIGNALS
|
||||
```
|
||||
FORMAT:
|
||||
━━━━━━━━━━━━━━━━━━━━━━━
|
||||
🎯 ACTION: BUY/SELL
|
||||
📈 TICKER: NVDA
|
||||
💰 PRICE: $450.25
|
||||
🎯 TARGET: $465.00
|
||||
🛑 STOP: $445.00
|
||||
📊 CONFIDENCE: 85%
|
||||
📝 REASON: Congressional buying + Earnings beat
|
||||
━━━━━━━━━━━━━━━━━━━━━━━
|
||||
```
|
||||
|
||||
### 🔍 OPPORTUNITY ALERTS
|
||||
- Congressional trades matching your watchlist
|
||||
- Unusual options activity
|
||||
- Insider buying in your sectors
|
||||
- Earnings surprises
|
||||
- Technical breakouts
|
||||
|
||||
## 5. Database Schema
|
||||
|
||||
```sql
|
||||
-- Portfolio tracking
|
||||
CREATE TABLE positions (
|
||||
id SERIAL PRIMARY KEY,
|
||||
ticker VARCHAR(10),
|
||||
shares INTEGER,
|
||||
avg_cost DECIMAL,
|
||||
current_price DECIMAL,
|
||||
last_updated TIMESTAMP
|
||||
);
|
||||
|
||||
-- Trade recommendations
|
||||
CREATE TABLE recommendations (
|
||||
id SERIAL PRIMARY KEY,
|
||||
ticker VARCHAR(10),
|
||||
action VARCHAR(10),
|
||||
price_target DECIMAL,
|
||||
stop_loss DECIMAL,
|
||||
confidence DECIMAL,
|
||||
reasoning TEXT,
|
||||
created_at TIMESTAMP
|
||||
);
|
||||
|
||||
-- Congressional trades
|
||||
CREATE TABLE congressional_trades (
|
||||
id SERIAL PRIMARY KEY,
|
||||
politician VARCHAR(100),
|
||||
ticker VARCHAR(10),
|
||||
action VARCHAR(10),
|
||||
amount_range VARCHAR(50),
|
||||
filed_date DATE
|
||||
);
|
||||
```
|
||||
|
||||
## 6. Deployment Strategy
|
||||
|
||||
### Local Server Setup
|
||||
```bash
|
||||
# Docker Compose for all services
|
||||
docker-compose up -d postgres redis rabbitmq
|
||||
|
||||
# Main application
|
||||
python autonomous_trader.py --mode=production
|
||||
|
||||
# Background workers
|
||||
celery -A tasks worker --loglevel=info
|
||||
celery -A tasks beat --loglevel=info
|
||||
```
|
||||
|
||||
### Cloud Deployment (AWS/GCP)
|
||||
```yaml
|
||||
services:
|
||||
- trading_brain: EC2/Compute Engine
|
||||
- database: RDS/Cloud SQL
|
||||
- message_queue: SQS/Pub-Sub
|
||||
- monitoring: CloudWatch/Stackdriver
|
||||
- alerts: Lambda/Cloud Functions
|
||||
```
|
||||
|
||||
## 7. Safety Features
|
||||
|
||||
### Risk Controls
|
||||
```python
|
||||
RISK_LIMITS = {
|
||||
"max_position_size": 0.20, # 20% of portfolio
|
||||
"max_daily_loss": 0.05, # 5% daily loss limit
|
||||
"max_trades_per_day": 10,
|
||||
"require_confirmation": True, # For trades > $10k
|
||||
"paper_trade_first": True # Test mode
|
||||
}
|
||||
```
|
||||
|
||||
### Fail-Safes
|
||||
- Circuit breakers for extreme market conditions
|
||||
- Automatic position hedging
|
||||
- Emergency liquidation protocols
|
||||
- Manual override capabilities
|
||||
|
||||
## 8. Quick Start Implementation
|
||||
|
||||
Let me create the initial autonomous monitoring script:
|
||||
|
|
@ -0,0 +1,105 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Analyze your IBKR portfolio positions"""
|
||||
|
||||
from tradingagents.graph.trading_graph import TradingAgentsGraph
|
||||
from tradingagents.default_config import DEFAULT_CONFIG
|
||||
from dotenv import load_dotenv
|
||||
from datetime import datetime, timedelta
|
||||
import time
|
||||
|
||||
# Load environment variables
|
||||
load_dotenv()
|
||||
|
||||
# Your IBKR positions
|
||||
PORTFOLIO = [
|
||||
{"ticker": "AVGO", "name": "Broadcom Inc", "shares": 43},
|
||||
{"ticker": "MSFT", "name": "Microsoft Corp", "shares": 12},
|
||||
{"ticker": "MU", "name": "Micron Technology Inc", "shares": 13},
|
||||
{"ticker": "NVDA", "name": "Nvidia Corp", "shares": 30},
|
||||
{"ticker": "SXRV", "name": "iShares NASDAQ 100 USD ACC", "shares": 9},
|
||||
{"ticker": "TSM", "name": "Taiwan Semiconductor SP ADR", "shares": 15},
|
||||
]
|
||||
|
||||
print("=" * 70)
|
||||
print("🏦 IBKR Portfolio Analysis - TradingAgents")
|
||||
print("=" * 70)
|
||||
print("\nYour positions:")
|
||||
for pos in PORTFOLIO:
|
||||
print(f" • {pos['ticker']:6s} - {pos['shares']:3d} shares - {pos['name']}")
|
||||
|
||||
# Configure for efficient analysis
|
||||
config = DEFAULT_CONFIG.copy()
|
||||
config["deep_think_llm"] = "gpt-4o-mini" # Use faster model for bulk analysis
|
||||
config["quick_think_llm"] = "gpt-4o-mini"
|
||||
config["max_debate_rounds"] = 1 # Keep it fast
|
||||
|
||||
# Configure data sources
|
||||
config["data_vendors"] = {
|
||||
"core_stock_apis": "yfinance",
|
||||
"technical_indicators": "yfinance",
|
||||
"fundamental_data": "alpha_vantage",
|
||||
"news_data": "alpha_vantage",
|
||||
}
|
||||
|
||||
# Use recent date for analysis
|
||||
analysis_date = (datetime.now() - timedelta(days=5)).strftime("%Y-%m-%d")
|
||||
|
||||
print(f"\n📅 Analysis date: {analysis_date}")
|
||||
print("🤖 Using: gpt-4o-mini (fast mode)")
|
||||
print("📊 Data sources: yfinance + Alpha Vantage")
|
||||
print("\n" + "=" * 70)
|
||||
|
||||
# Initialize the trading graph
|
||||
ta = TradingAgentsGraph(debug=False, config=config)
|
||||
|
||||
# Store decisions
|
||||
decisions = {}
|
||||
|
||||
# Analyze each position
|
||||
for i, position in enumerate(PORTFOLIO, 1):
|
||||
ticker = position["ticker"]
|
||||
|
||||
# Skip ETF for now (SXRV might not have all data available)
|
||||
if ticker == "SXRV":
|
||||
print(f"\n[{i}/6] Skipping {ticker} (ETF - limited data)")
|
||||
decisions[ticker] = "ETF - Manual review recommended"
|
||||
continue
|
||||
|
||||
print(f"\n[{i}/6] Analyzing {ticker} ({position['name']})...")
|
||||
print(" 🔄 Agents working...")
|
||||
|
||||
try:
|
||||
start_time = time.time()
|
||||
_, decision = ta.propagate(ticker, analysis_date)
|
||||
elapsed = time.time() - start_time
|
||||
|
||||
decisions[ticker] = decision
|
||||
print(f" ✅ Complete ({elapsed:.1f}s)")
|
||||
|
||||
# Brief pause to avoid rate limits
|
||||
if i < len(PORTFOLIO):
|
||||
time.sleep(2)
|
||||
|
||||
except Exception as e:
|
||||
print(f" ❌ Error: {str(e)[:100]}")
|
||||
decisions[ticker] = f"Error during analysis: {str(e)[:100]}"
|
||||
|
||||
# Summary Report
|
||||
print("\n" + "=" * 70)
|
||||
print("📈 PORTFOLIO ANALYSIS SUMMARY")
|
||||
print("=" * 70)
|
||||
|
||||
for position in PORTFOLIO:
|
||||
ticker = position["ticker"]
|
||||
print(f"\n{'='*70}")
|
||||
print(f"📊 {ticker} - {position['name']} ({position['shares']} shares)")
|
||||
print(f"{'='*70}")
|
||||
|
||||
if ticker in decisions:
|
||||
print(decisions[ticker])
|
||||
|
||||
print("\n" + "=" * 70)
|
||||
print("✅ Portfolio analysis complete!")
|
||||
print("\nNote: This is AI analysis for research purposes only.")
|
||||
print("Always do your own due diligence before making trading decisions.")
|
||||
print("=" * 70)
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
"""
|
||||
Autonomous Trading System
|
||||
========================
|
||||
|
||||
A 24/7 monitoring system that integrates with IBKR and multiple data sources
|
||||
to provide real-time trading recommendations.
|
||||
"""
|
||||
|
||||
__version__ = "0.1.0"
|
||||
|
||||
from .ibkr_connector import IBKRConnector
|
||||
from .data_aggregator import DataAggregator
|
||||
from .signal_processor import SignalProcessor
|
||||
from .alert_engine import AlertEngine
|
||||
|
||||
__all__ = [
|
||||
"IBKRConnector",
|
||||
"DataAggregator",
|
||||
"SignalProcessor",
|
||||
"AlertEngine",
|
||||
]
|
||||
|
|
@ -0,0 +1,468 @@
|
|||
"""
|
||||
Alert Engine
|
||||
===========
|
||||
|
||||
Multi-channel notification system for trading alerts.
|
||||
Supports Discord, Telegram, Email, and console output.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
import json
|
||||
from typing import Dict, List, Optional, Any
|
||||
from datetime import datetime
|
||||
from enum import Enum
|
||||
import os
|
||||
|
||||
# Optional imports for different notification channels
|
||||
try:
|
||||
import discord
|
||||
from discord import Webhook
|
||||
import aiohttp
|
||||
DISCORD_AVAILABLE = True
|
||||
except ImportError:
|
||||
DISCORD_AVAILABLE = False
|
||||
print("Discord not installed. Install with: pip install discord.py")
|
||||
|
||||
try:
|
||||
import smtplib
|
||||
from email.mime.text import MIMEText
|
||||
from email.mime.multipart import MIMEMultipart
|
||||
EMAIL_AVAILABLE = True
|
||||
except ImportError:
|
||||
EMAIL_AVAILABLE = False
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AlertPriority(Enum):
|
||||
"""Alert priority levels"""
|
||||
CRITICAL = "critical" # Immediate action required
|
||||
HIGH = "high" # Important, time-sensitive
|
||||
MEDIUM = "medium" # Standard alerts
|
||||
LOW = "low" # Informational
|
||||
INFO = "info" # Non-actionable info
|
||||
|
||||
|
||||
class AlertType(Enum):
|
||||
"""Types of alerts"""
|
||||
TRADING_SIGNAL = "trading_signal"
|
||||
PORTFOLIO_UPDATE = "portfolio_update"
|
||||
RISK_WARNING = "risk_warning"
|
||||
CONGRESSIONAL_TRADE = "congressional_trade"
|
||||
INSIDER_TRADE = "insider_trade"
|
||||
EARNINGS = "earnings"
|
||||
MARKET_MOVE = "market_move"
|
||||
SYSTEM = "system"
|
||||
|
||||
|
||||
class AlertEngine:
|
||||
"""
|
||||
Manages multi-channel alert distribution
|
||||
"""
|
||||
|
||||
def __init__(self, config: Optional[Dict] = None):
|
||||
"""
|
||||
Initialize alert engine
|
||||
|
||||
Args:
|
||||
config: Configuration with API keys and settings
|
||||
"""
|
||||
self.config = config or {}
|
||||
self.discord_webhook_url = self.config.get('discord_webhook_url')
|
||||
self.telegram_bot_token = self.config.get('telegram_bot_token')
|
||||
self.telegram_chat_id = self.config.get('telegram_chat_id')
|
||||
self.email_config = self.config.get('email', {})
|
||||
|
||||
# Track sent alerts to avoid duplicates
|
||||
self.sent_alerts: List[Dict] = []
|
||||
self.alert_history: List[Dict] = []
|
||||
|
||||
async def send_alert(self,
|
||||
title: str,
|
||||
message: str,
|
||||
alert_type: AlertType,
|
||||
priority: AlertPriority,
|
||||
data: Optional[Dict] = None,
|
||||
channels: Optional[List[str]] = None) -> bool:
|
||||
"""
|
||||
Send alert through specified channels
|
||||
|
||||
Args:
|
||||
title: Alert title
|
||||
message: Alert message
|
||||
alert_type: Type of alert
|
||||
priority: Alert priority
|
||||
data: Additional data
|
||||
channels: List of channels to use (discord, telegram, email, console)
|
||||
|
||||
Returns:
|
||||
True if alert sent successfully
|
||||
"""
|
||||
# Default channels based on priority
|
||||
if channels is None:
|
||||
if priority == AlertPriority.CRITICAL:
|
||||
channels = ['discord', 'telegram', 'email', 'console']
|
||||
elif priority == AlertPriority.HIGH:
|
||||
channels = ['discord', 'telegram', 'console']
|
||||
elif priority == AlertPriority.MEDIUM:
|
||||
channels = ['discord', 'console']
|
||||
else:
|
||||
channels = ['console']
|
||||
|
||||
# Check for duplicate alerts
|
||||
alert_hash = f"{title}_{message}_{datetime.now().strftime('%Y%m%d%H')}"
|
||||
if alert_hash in [a.get('hash') for a in self.sent_alerts[-100:]]:
|
||||
logger.info("Skipping duplicate alert")
|
||||
return False
|
||||
|
||||
# Create alert record
|
||||
alert_record = {
|
||||
'hash': alert_hash,
|
||||
'title': title,
|
||||
'message': message,
|
||||
'type': alert_type.value,
|
||||
'priority': priority.value,
|
||||
'timestamp': datetime.now().isoformat(),
|
||||
'data': data
|
||||
}
|
||||
|
||||
success = False
|
||||
|
||||
# Send to each channel
|
||||
tasks = []
|
||||
if 'discord' in channels and self.discord_webhook_url:
|
||||
tasks.append(self._send_discord(title, message, alert_type, priority, data))
|
||||
|
||||
if 'telegram' in channels and self.telegram_bot_token:
|
||||
tasks.append(self._send_telegram(title, message, alert_type, priority, data))
|
||||
|
||||
if 'email' in channels and self.email_config:
|
||||
tasks.append(self._send_email(title, message, alert_type, priority, data))
|
||||
|
||||
if 'console' in channels:
|
||||
self._send_console(title, message, alert_type, priority, data)
|
||||
success = True
|
||||
|
||||
# Execute all sends in parallel
|
||||
if tasks:
|
||||
results = await asyncio.gather(*tasks, return_exceptions=True)
|
||||
success = any(r is True for r in results if not isinstance(r, Exception))
|
||||
|
||||
# Record alert
|
||||
if success:
|
||||
self.sent_alerts.append(alert_record)
|
||||
self.alert_history.append(alert_record)
|
||||
|
||||
return success
|
||||
|
||||
async def _send_discord(self,
|
||||
title: str,
|
||||
message: str,
|
||||
alert_type: AlertType,
|
||||
priority: AlertPriority,
|
||||
data: Optional[Dict]) -> bool:
|
||||
"""Send alert to Discord via webhook"""
|
||||
if not DISCORD_AVAILABLE or not self.discord_webhook_url:
|
||||
return False
|
||||
|
||||
try:
|
||||
# Color based on priority
|
||||
colors = {
|
||||
AlertPriority.CRITICAL: 0xFF0000, # Red
|
||||
AlertPriority.HIGH: 0FFA500, # Orange
|
||||
AlertPriority.MEDIUM: 0x00FF00, # Green
|
||||
AlertPriority.LOW: 0x0000FF, # Blue
|
||||
AlertPriority.INFO: 0x808080 # Gray
|
||||
}
|
||||
|
||||
# Icons for different alert types
|
||||
icons = {
|
||||
AlertType.TRADING_SIGNAL: "🎯",
|
||||
AlertType.PORTFOLIO_UPDATE: "📊",
|
||||
AlertType.RISK_WARNING: "⚠️",
|
||||
AlertType.CONGRESSIONAL_TRADE: "🏛️",
|
||||
AlertType.INSIDER_TRADE: "👔",
|
||||
AlertType.EARNINGS: "📈",
|
||||
AlertType.MARKET_MOVE: "📉",
|
||||
AlertType.SYSTEM: "🔧"
|
||||
}
|
||||
|
||||
icon = icons.get(alert_type, "📢")
|
||||
|
||||
async with aiohttp.ClientSession() as session:
|
||||
webhook = Webhook.from_url(self.discord_webhook_url, session=session)
|
||||
|
||||
# Create embed
|
||||
embed = discord.Embed(
|
||||
title=f"{icon} {title}",
|
||||
description=message,
|
||||
color=colors.get(priority, 0x000000),
|
||||
timestamp=datetime.now()
|
||||
)
|
||||
|
||||
# Add fields from data
|
||||
if data:
|
||||
for key, value in list(data.items())[:5]: # Limit to 5 fields
|
||||
if isinstance(value, (str, int, float)):
|
||||
embed.add_field(name=key.replace('_', ' ').title(),
|
||||
value=str(value),
|
||||
inline=True)
|
||||
|
||||
embed.set_footer(text=f"Alert Type: {alert_type.value} | Priority: {priority.value}")
|
||||
|
||||
await webhook.send(embed=embed)
|
||||
logger.info(f"Discord alert sent: {title}")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to send Discord alert: {e}")
|
||||
return False
|
||||
|
||||
async def _send_telegram(self,
|
||||
title: str,
|
||||
message: str,
|
||||
alert_type: AlertType,
|
||||
priority: AlertPriority,
|
||||
data: Optional[Dict]) -> bool:
|
||||
"""Send alert to Telegram"""
|
||||
if not self.telegram_bot_token or not self.telegram_chat_id:
|
||||
return False
|
||||
|
||||
try:
|
||||
import aiohttp
|
||||
|
||||
# Format message for Telegram
|
||||
icons = {
|
||||
AlertPriority.CRITICAL: "🚨",
|
||||
AlertPriority.HIGH: "⚠️",
|
||||
AlertPriority.MEDIUM: "📊",
|
||||
AlertPriority.LOW: "ℹ️",
|
||||
AlertPriority.INFO: "💡"
|
||||
}
|
||||
|
||||
icon = icons.get(priority, "📢")
|
||||
telegram_message = f"{icon} *{title}*\n\n{message}"
|
||||
|
||||
if data:
|
||||
telegram_message += "\n\n*Details:*\n"
|
||||
for key, value in list(data.items())[:5]:
|
||||
if isinstance(value, (str, int, float)):
|
||||
telegram_message += f"• {key}: {value}\n"
|
||||
|
||||
# Send via Telegram Bot API
|
||||
url = f"https://api.telegram.org/bot{self.telegram_bot_token}/sendMessage"
|
||||
payload = {
|
||||
'chat_id': self.telegram_chat_id,
|
||||
'text': telegram_message,
|
||||
'parse_mode': 'Markdown'
|
||||
}
|
||||
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.post(url, json=payload) as response:
|
||||
if response.status == 200:
|
||||
logger.info(f"Telegram alert sent: {title}")
|
||||
return True
|
||||
else:
|
||||
logger.error(f"Telegram API error: {response.status}")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to send Telegram alert: {e}")
|
||||
return False
|
||||
|
||||
async def _send_email(self,
|
||||
title: str,
|
||||
message: str,
|
||||
alert_type: AlertType,
|
||||
priority: AlertPriority,
|
||||
data: Optional[Dict]) -> bool:
|
||||
"""Send alert via email"""
|
||||
if not EMAIL_AVAILABLE or not self.email_config:
|
||||
return False
|
||||
|
||||
try:
|
||||
# Email configuration
|
||||
smtp_server = self.email_config.get('smtp_server', 'smtp.gmail.com')
|
||||
smtp_port = self.email_config.get('smtp_port', 587)
|
||||
sender_email = self.email_config.get('sender_email')
|
||||
sender_password = self.email_config.get('sender_password')
|
||||
recipient_email = self.email_config.get('recipient_email')
|
||||
|
||||
if not all([sender_email, sender_password, recipient_email]):
|
||||
return False
|
||||
|
||||
# Create message
|
||||
msg = MIMEMultipart('alternative')
|
||||
msg['Subject'] = f"[{priority.value.upper()}] {title}"
|
||||
msg['From'] = sender_email
|
||||
msg['To'] = recipient_email
|
||||
|
||||
# Create HTML content
|
||||
html_content = f"""
|
||||
<html>
|
||||
<body>
|
||||
<h2>{title}</h2>
|
||||
<p>{message.replace(chr(10), '<br>')}</p>
|
||||
<hr>
|
||||
<p><small>Alert Type: {alert_type.value} | Priority: {priority.value}</small></p>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
# Attach HTML content
|
||||
html_part = MIMEText(html_content, 'html')
|
||||
msg.attach(html_part)
|
||||
|
||||
# Send email
|
||||
with smtplib.SMTP(smtp_server, smtp_port) as server:
|
||||
server.starttls()
|
||||
server.login(sender_email, sender_password)
|
||||
server.send_message(msg)
|
||||
|
||||
logger.info(f"Email alert sent: {title}")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to send email alert: {e}")
|
||||
return False
|
||||
|
||||
def _send_console(self,
|
||||
title: str,
|
||||
message: str,
|
||||
alert_type: AlertType,
|
||||
priority: AlertPriority,
|
||||
data: Optional[Dict]):
|
||||
"""Display alert in console"""
|
||||
# Color codes for terminal
|
||||
colors = {
|
||||
AlertPriority.CRITICAL: "\033[91m", # Red
|
||||
AlertPriority.HIGH: "\033[93m", # Yellow
|
||||
AlertPriority.MEDIUM: "\033[92m", # Green
|
||||
AlertPriority.LOW: "\033[94m", # Blue
|
||||
AlertPriority.INFO: "\033[95m" # Magenta
|
||||
}
|
||||
reset_color = "\033[0m"
|
||||
|
||||
color = colors.get(priority, "")
|
||||
|
||||
print(f"\n{color}{'='*60}")
|
||||
print(f"🔔 ALERT: {title}")
|
||||
print(f"{'='*60}{reset_color}")
|
||||
print(f"Type: {alert_type.value} | Priority: {priority.value}")
|
||||
print(f"Time: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
|
||||
print(f"\n{message}")
|
||||
|
||||
if data:
|
||||
print("\nAdditional Data:")
|
||||
for key, value in data.items():
|
||||
print(f" {key}: {value}")
|
||||
|
||||
print(f"{color}{'='*60}{reset_color}\n")
|
||||
|
||||
async def send_trading_signal(self, recommendation):
|
||||
"""
|
||||
Send a trading signal alert
|
||||
|
||||
Args:
|
||||
recommendation: TradingRecommendation object
|
||||
"""
|
||||
from .signal_processor import TradingRecommendation
|
||||
|
||||
if isinstance(recommendation, TradingRecommendation):
|
||||
# Format title based on action
|
||||
emoji = "🟢" if recommendation.action == "BUY" else "🔴"
|
||||
title = f"{emoji} {recommendation.action} Signal: {recommendation.ticker}"
|
||||
|
||||
# Calculate potential gains
|
||||
gain_1 = ((recommendation.target_price_1 - recommendation.current_price) /
|
||||
recommendation.current_price) * 100
|
||||
stop_loss_pct = ((recommendation.stop_loss - recommendation.current_price) /
|
||||
recommendation.current_price) * 100
|
||||
|
||||
message = f"""
|
||||
Action: {recommendation.action}
|
||||
Current Price: ${recommendation.current_price:.2f}
|
||||
Entry Range: ${recommendation.entry_price_min:.2f} - ${recommendation.entry_price_max:.2f}
|
||||
Target 1: ${recommendation.target_price_1:.2f} ({gain_1:+.1f}%)
|
||||
Stop Loss: ${recommendation.stop_loss:.2f} ({stop_loss_pct:.1f}%)
|
||||
Confidence: {recommendation.confidence:.0f}%
|
||||
Position Size: {recommendation.position_size:.1%}
|
||||
Risk Level: {recommendation.risk_level}
|
||||
|
||||
Reasoning: {recommendation.reasoning[:200]}
|
||||
"""
|
||||
|
||||
# Determine priority based on confidence
|
||||
if recommendation.confidence >= 85:
|
||||
priority = AlertPriority.HIGH
|
||||
elif recommendation.confidence >= 70:
|
||||
priority = AlertPriority.MEDIUM
|
||||
else:
|
||||
priority = AlertPriority.LOW
|
||||
|
||||
await self.send_alert(
|
||||
title=title,
|
||||
message=message,
|
||||
alert_type=AlertType.TRADING_SIGNAL,
|
||||
priority=priority,
|
||||
data={
|
||||
'ticker': recommendation.ticker,
|
||||
'action': recommendation.action,
|
||||
'confidence': recommendation.confidence
|
||||
}
|
||||
)
|
||||
|
||||
async def send_portfolio_summary(self, portfolio_data: Dict):
|
||||
"""Send daily portfolio summary"""
|
||||
title = "📊 Daily Portfolio Summary"
|
||||
|
||||
total_value = portfolio_data.get('total_value', 0)
|
||||
total_pnl = portfolio_data.get('total_unrealized_pnl', 0)
|
||||
pnl_percent = portfolio_data.get('total_percent_change', 0)
|
||||
|
||||
message = f"""
|
||||
Total Value: ${total_value:,.2f}
|
||||
Unrealized P&L: ${total_pnl:+,.2f} ({pnl_percent:+.2f}%)
|
||||
Positions: {portfolio_data.get('position_count', 0)}
|
||||
|
||||
Top Performers:
|
||||
"""
|
||||
# Add top positions
|
||||
positions = portfolio_data.get('positions', [])
|
||||
sorted_positions = sorted(positions, key=lambda x: x['percent'], reverse=True)
|
||||
|
||||
for pos in sorted_positions[:3]:
|
||||
message += f" • {pos['ticker']}: ${pos['value']:,.2f} ({pos['percent']:+.2f}%)\n"
|
||||
|
||||
await self.send_alert(
|
||||
title=title,
|
||||
message=message,
|
||||
alert_type=AlertType.PORTFOLIO_UPDATE,
|
||||
priority=AlertPriority.INFO
|
||||
)
|
||||
|
||||
|
||||
# Example usage
|
||||
async def main():
|
||||
"""Example of using the alert engine"""
|
||||
config = {
|
||||
'discord_webhook_url': os.getenv('DISCORD_WEBHOOK_URL'),
|
||||
'telegram_bot_token': os.getenv('TELEGRAM_BOT_TOKEN'),
|
||||
'telegram_chat_id': os.getenv('TELEGRAM_CHAT_ID'),
|
||||
}
|
||||
|
||||
alert_engine = AlertEngine(config)
|
||||
|
||||
# Send test alert
|
||||
await alert_engine.send_alert(
|
||||
title="Test Trading Alert",
|
||||
message="NVDA showing strong buy signals based on congressional activity",
|
||||
alert_type=AlertType.TRADING_SIGNAL,
|
||||
priority=AlertPriority.HIGH,
|
||||
data={'ticker': 'NVDA', 'confidence': 85}
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
|
|
@ -0,0 +1,180 @@
|
|||
"""
|
||||
Configuration Settings
|
||||
=====================
|
||||
|
||||
Central configuration for the autonomous trading system.
|
||||
"""
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# Load environment variables
|
||||
load_dotenv()
|
||||
|
||||
|
||||
class Config:
|
||||
"""Configuration class for autonomous trading system"""
|
||||
|
||||
# === IBKR Settings ===
|
||||
IBKR_HOST = os.getenv('IBKR_HOST', '127.0.0.1')
|
||||
IBKR_PORT = int(os.getenv('IBKR_PORT', 7497)) # 7497=paper, 7496=live
|
||||
IBKR_CLIENT_ID = int(os.getenv('IBKR_CLIENT_ID', 1))
|
||||
|
||||
# === API Keys ===
|
||||
OPENAI_API_KEY = os.getenv('OPENAI_API_KEY')
|
||||
ALPHA_VANTAGE_API_KEY = os.getenv('ALPHA_VANTAGE_API_KEY')
|
||||
QUIVER_API_KEY = os.getenv('QUIVER_API_KEY')
|
||||
POLYGON_API_KEY = os.getenv('POLYGON_API_KEY')
|
||||
NEWS_API_KEY = os.getenv('NEWS_API_KEY')
|
||||
|
||||
# === Notification Settings ===
|
||||
# Discord
|
||||
DISCORD_WEBHOOK_URL = os.getenv('DISCORD_WEBHOOK_URL')
|
||||
|
||||
# Telegram
|
||||
TELEGRAM_BOT_TOKEN = os.getenv('TELEGRAM_BOT_TOKEN')
|
||||
TELEGRAM_CHAT_ID = os.getenv('TELEGRAM_CHAT_ID')
|
||||
|
||||
# Email
|
||||
EMAIL_SMTP_SERVER = os.getenv('EMAIL_SMTP_SERVER', 'smtp.gmail.com')
|
||||
EMAIL_SMTP_PORT = int(os.getenv('EMAIL_SMTP_PORT', 587))
|
||||
EMAIL_SENDER = os.getenv('EMAIL_SENDER')
|
||||
EMAIL_PASSWORD = os.getenv('EMAIL_PASSWORD')
|
||||
EMAIL_RECIPIENT = os.getenv('EMAIL_RECIPIENT')
|
||||
|
||||
# === Portfolio Settings ===
|
||||
# Your IBKR portfolio tickers
|
||||
PORTFOLIO_TICKERS = [
|
||||
"AVGO", # Broadcom - 43 shares
|
||||
"MSFT", # Microsoft - 12 shares
|
||||
"MU", # Micron - 13 shares
|
||||
"NVDA", # Nvidia - 30 shares
|
||||
"TSM", # Taiwan Semi - 15 shares
|
||||
]
|
||||
|
||||
# Additional tickers to monitor
|
||||
WATCHLIST = os.getenv('WATCHLIST', 'AAPL,TSLA,META,GOOGL,AMZN').split(',')
|
||||
|
||||
# === Trading Settings ===
|
||||
TRADING_ENABLED = os.getenv('TRADING_ENABLED', 'false').lower() == 'true'
|
||||
PAPER_TRADING = os.getenv('PAPER_TRADING', 'true').lower() == 'true'
|
||||
|
||||
# Risk Management
|
||||
MAX_POSITION_SIZE = float(os.getenv('MAX_POSITION_SIZE', 0.20)) # 20% max per position
|
||||
MAX_DAILY_LOSS = float(os.getenv('MAX_DAILY_LOSS', 0.05)) # 5% daily loss limit
|
||||
MAX_TRADES_PER_DAY = int(os.getenv('MAX_TRADES_PER_DAY', 10))
|
||||
STOP_LOSS_PERCENT = float(os.getenv('STOP_LOSS_PERCENT', 0.03)) # 3% stop loss
|
||||
|
||||
# Position Sizing
|
||||
BASE_POSITION_SIZE = float(os.getenv('BASE_POSITION_SIZE', 0.10)) # 10% base
|
||||
MIN_POSITION_SIZE = float(os.getenv('MIN_POSITION_SIZE', 0.05)) # 5% minimum
|
||||
CONFIDENCE_THRESHOLD = float(os.getenv('CONFIDENCE_THRESHOLD', 70)) # Min 70% confidence
|
||||
|
||||
# === AI Model Settings ===
|
||||
DEEP_THINK_MODEL = os.getenv('DEEP_THINK_MODEL', 'gpt-4o-mini')
|
||||
QUICK_THINK_MODEL = os.getenv('QUICK_THINK_MODEL', 'gpt-4o-mini')
|
||||
MAX_DEBATE_ROUNDS = int(os.getenv('MAX_DEBATE_ROUNDS', 1))
|
||||
|
||||
# === Schedule Settings (in minutes) ===
|
||||
PORTFOLIO_CHECK_INTERVAL = int(os.getenv('PORTFOLIO_CHECK_INTERVAL', 5))
|
||||
MARKET_SCAN_INTERVAL = int(os.getenv('MARKET_SCAN_INTERVAL', 15))
|
||||
NEWS_CHECK_INTERVAL = int(os.getenv('NEWS_CHECK_INTERVAL', 30))
|
||||
CONGRESS_CHECK_INTERVAL = int(os.getenv('CONGRESS_CHECK_INTERVAL', 60))
|
||||
RISK_CHECK_INTERVAL = int(os.getenv('RISK_CHECK_INTERVAL', 30))
|
||||
|
||||
# === Database Settings ===
|
||||
DB_HOST = os.getenv('DB_HOST', 'localhost')
|
||||
DB_PORT = int(os.getenv('DB_PORT', 5432))
|
||||
DB_NAME = os.getenv('DB_NAME', 'trading_autonomous')
|
||||
DB_USER = os.getenv('DB_USER', 'trader')
|
||||
DB_PASSWORD = os.getenv('DB_PASSWORD', 'password')
|
||||
|
||||
@classmethod
|
||||
def get_db_url(cls):
|
||||
"""Get database connection URL"""
|
||||
return f"postgresql://{cls.DB_USER}:{cls.DB_PASSWORD}@{cls.DB_HOST}:{cls.DB_PORT}/{cls.DB_NAME}"
|
||||
|
||||
@classmethod
|
||||
def to_dict(cls):
|
||||
"""Convert config to dictionary"""
|
||||
return {
|
||||
# IBKR
|
||||
'ibkr_host': cls.IBKR_HOST,
|
||||
'ibkr_port': cls.IBKR_PORT,
|
||||
'ibkr_client_id': cls.IBKR_CLIENT_ID,
|
||||
|
||||
# API Keys
|
||||
'openai_api_key': cls.OPENAI_API_KEY,
|
||||
'alpha_vantage_api_key': cls.ALPHA_VANTAGE_API_KEY,
|
||||
'quiver_api_key': cls.QUIVER_API_KEY,
|
||||
|
||||
# Notifications
|
||||
'discord_webhook_url': cls.DISCORD_WEBHOOK_URL,
|
||||
'telegram_bot_token': cls.TELEGRAM_BOT_TOKEN,
|
||||
'telegram_chat_id': cls.TELEGRAM_CHAT_ID,
|
||||
'email': {
|
||||
'smtp_server': cls.EMAIL_SMTP_SERVER,
|
||||
'smtp_port': cls.EMAIL_SMTP_PORT,
|
||||
'sender_email': cls.EMAIL_SENDER,
|
||||
'sender_password': cls.EMAIL_PASSWORD,
|
||||
'recipient_email': cls.EMAIL_RECIPIENT
|
||||
},
|
||||
|
||||
# Portfolio
|
||||
'portfolio_tickers': cls.PORTFOLIO_TICKERS,
|
||||
'watchlist': cls.WATCHLIST,
|
||||
|
||||
# Trading
|
||||
'trading_enabled': cls.TRADING_ENABLED,
|
||||
'paper_trading': cls.PAPER_TRADING,
|
||||
'max_position_size': cls.MAX_POSITION_SIZE,
|
||||
'confidence_threshold': cls.CONFIDENCE_THRESHOLD,
|
||||
|
||||
# AI Models
|
||||
'deep_think_llm': cls.DEEP_THINK_MODEL,
|
||||
'quick_think_llm': cls.QUICK_THINK_MODEL,
|
||||
'max_debate_rounds': cls.MAX_DEBATE_ROUNDS
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def validate(cls):
|
||||
"""Validate configuration"""
|
||||
errors = []
|
||||
|
||||
# Check required API keys
|
||||
if not cls.OPENAI_API_KEY:
|
||||
errors.append("OPENAI_API_KEY is required")
|
||||
|
||||
if not cls.ALPHA_VANTAGE_API_KEY:
|
||||
errors.append("ALPHA_VANTAGE_API_KEY is required")
|
||||
|
||||
# Check notification settings
|
||||
if not any([cls.DISCORD_WEBHOOK_URL, cls.TELEGRAM_BOT_TOKEN, cls.EMAIL_SENDER]):
|
||||
errors.append("At least one notification method must be configured")
|
||||
|
||||
# Validate risk settings
|
||||
if cls.MAX_POSITION_SIZE > 0.5:
|
||||
errors.append("MAX_POSITION_SIZE should not exceed 50%")
|
||||
|
||||
if cls.MAX_DAILY_LOSS > 0.1:
|
||||
errors.append("MAX_DAILY_LOSS should not exceed 10%")
|
||||
|
||||
if errors:
|
||||
print("Configuration errors:")
|
||||
for error in errors:
|
||||
print(f" - {error}")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
# Example config validation
|
||||
if __name__ == "__main__":
|
||||
if Config.validate():
|
||||
print("✅ Configuration valid")
|
||||
print(f"Portfolio: {Config.PORTFOLIO_TICKERS}")
|
||||
print(f"Trading: {'ENABLED' if Config.TRADING_ENABLED else 'DISABLED'}")
|
||||
print(f"Mode: {'PAPER' if Config.PAPER_TRADING else 'LIVE'}")
|
||||
else:
|
||||
print("❌ Configuration invalid")
|
||||
|
|
@ -0,0 +1,495 @@
|
|||
"""
|
||||
Data Aggregator
|
||||
===============
|
||||
|
||||
Aggregates data from multiple sources including:
|
||||
- Congressional trades (QuiverQuant)
|
||||
- Market data (yfinance, Alpha Vantage)
|
||||
- News sentiment
|
||||
- Insider trading
|
||||
- Earnings calendars
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
from typing import Dict, List, Optional, Any
|
||||
from datetime import datetime, timedelta
|
||||
from dataclasses import dataclass
|
||||
import os
|
||||
import json
|
||||
import aiohttp
|
||||
|
||||
# Import existing TradingAgents data tools
|
||||
import sys
|
||||
sys.path.append('..')
|
||||
from tradingagents.dataflows.y_finance import YfinanceInterface
|
||||
from tradingagents.dataflows.alpha_vantage_news import AlphaVantageNewsInterface
|
||||
|
||||
# Optional imports
|
||||
try:
|
||||
import quiverquant
|
||||
QUIVER_AVAILABLE = True
|
||||
except ImportError:
|
||||
QUIVER_AVAILABLE = False
|
||||
print("QuiverQuant not installed. Install with: pip install quiverquant")
|
||||
|
||||
try:
|
||||
import yfinance as yf
|
||||
YFINANCE_AVAILABLE = True
|
||||
except ImportError:
|
||||
YFINANCE_AVAILABLE = False
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@dataclass
|
||||
class CongressionalTrade:
|
||||
"""Represents a congressional trade"""
|
||||
politician: str
|
||||
ticker: str
|
||||
action: str # 'purchase' or 'sale'
|
||||
amount_range: str
|
||||
transaction_date: datetime
|
||||
filing_date: datetime
|
||||
party: str
|
||||
state: str
|
||||
chamber: str # 'house' or 'senate'
|
||||
|
||||
|
||||
@dataclass
|
||||
class InsiderTrade:
|
||||
"""Represents an insider trade"""
|
||||
insider_name: str
|
||||
ticker: str
|
||||
action: str # 'Buy' or 'Sell'
|
||||
shares: int
|
||||
value: float
|
||||
transaction_date: datetime
|
||||
position: str # CEO, CFO, Director, etc.
|
||||
|
||||
|
||||
@dataclass
|
||||
class EarningsEvent:
|
||||
"""Represents an earnings event"""
|
||||
ticker: str
|
||||
earnings_date: datetime
|
||||
eps_estimate: float
|
||||
eps_actual: Optional[float]
|
||||
revenue_estimate: float
|
||||
revenue_actual: Optional[float]
|
||||
surprise_percent: Optional[float]
|
||||
|
||||
|
||||
@dataclass
|
||||
class MarketSignal:
|
||||
"""Aggregated market signal"""
|
||||
ticker: str
|
||||
signal_type: str # 'congressional', 'insider', 'earnings', 'technical'
|
||||
action: str # 'BUY', 'SELL', 'HOLD'
|
||||
confidence: float # 0-100
|
||||
data: Dict[str, Any]
|
||||
timestamp: datetime
|
||||
|
||||
|
||||
class DataAggregator:
|
||||
"""
|
||||
Aggregates data from multiple sources for trading decisions
|
||||
"""
|
||||
|
||||
def __init__(self, config: Optional[Dict] = None):
|
||||
"""
|
||||
Initialize data aggregator
|
||||
|
||||
Args:
|
||||
config: Configuration dictionary with API keys
|
||||
"""
|
||||
self.config = config or {}
|
||||
self.quiver_client = None
|
||||
self.congressional_trades: List[CongressionalTrade] = []
|
||||
self.insider_trades: List[InsiderTrade] = []
|
||||
self.earnings_calendar: List[EarningsEvent] = []
|
||||
self.market_signals: List[MarketSignal] = []
|
||||
|
||||
# Initialize QuiverQuant if available
|
||||
if QUIVER_AVAILABLE and self.config.get('quiver_api_key'):
|
||||
self.quiver_client = quiverquant.quiver(self.config['quiver_api_key'])
|
||||
|
||||
async def fetch_congressional_trades(self,
|
||||
tickers: Optional[List[str]] = None,
|
||||
days_back: int = 30) -> List[CongressionalTrade]:
|
||||
"""
|
||||
Fetch recent congressional trades
|
||||
|
||||
Args:
|
||||
tickers: List of tickers to filter (None for all)
|
||||
days_back: Number of days to look back
|
||||
|
||||
Returns:
|
||||
List of congressional trades
|
||||
"""
|
||||
trades = []
|
||||
|
||||
if not self.quiver_client:
|
||||
logger.warning("QuiverQuant not configured, using mock data")
|
||||
# Return mock data for demonstration
|
||||
return self._get_mock_congressional_trades(tickers)
|
||||
|
||||
try:
|
||||
# Fetch congressional trading data
|
||||
df = self.quiver_client.congress_trading()
|
||||
|
||||
if df is not None and not df.empty:
|
||||
# Filter by date
|
||||
cutoff_date = datetime.now() - timedelta(days=days_back)
|
||||
df['TransactionDate'] = pd.to_datetime(df['TransactionDate'])
|
||||
df = df[df['TransactionDate'] >= cutoff_date]
|
||||
|
||||
# Filter by tickers if provided
|
||||
if tickers:
|
||||
df = df[df['Ticker'].isin(tickers)]
|
||||
|
||||
# Convert to CongressionalTrade objects
|
||||
for _, row in df.iterrows():
|
||||
trade = CongressionalTrade(
|
||||
politician=row.get('Representative', 'Unknown'),
|
||||
ticker=row.get('Ticker', ''),
|
||||
action=row.get('Transaction', 'Unknown').lower(),
|
||||
amount_range=row.get('Range', 'Unknown'),
|
||||
transaction_date=row.get('TransactionDate'),
|
||||
filing_date=row.get('FilingDate', row.get('TransactionDate')),
|
||||
party=row.get('Party', 'Unknown'),
|
||||
state=row.get('State', 'Unknown'),
|
||||
chamber=row.get('Chamber', 'house')
|
||||
)
|
||||
trades.append(trade)
|
||||
|
||||
self.congressional_trades = trades
|
||||
logger.info(f"Fetched {len(trades)} congressional trades")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error fetching congressional trades: {e}")
|
||||
trades = self._get_mock_congressional_trades(tickers)
|
||||
|
||||
return trades
|
||||
|
||||
def _get_mock_congressional_trades(self, tickers: Optional[List[str]] = None) -> List[CongressionalTrade]:
|
||||
"""Get mock congressional trades for testing"""
|
||||
mock_trades = [
|
||||
CongressionalTrade(
|
||||
politician="Nancy Pelosi",
|
||||
ticker="NVDA",
|
||||
action="purchase",
|
||||
amount_range="$1,000,001 - $5,000,000",
|
||||
transaction_date=datetime.now() - timedelta(days=5),
|
||||
filing_date=datetime.now() - timedelta(days=2),
|
||||
party="D",
|
||||
state="CA",
|
||||
chamber="house"
|
||||
),
|
||||
CongressionalTrade(
|
||||
politician="Dan Crenshaw",
|
||||
ticker="MSFT",
|
||||
action="purchase",
|
||||
amount_range="$15,001 - $50,000",
|
||||
transaction_date=datetime.now() - timedelta(days=10),
|
||||
filing_date=datetime.now() - timedelta(days=7),
|
||||
party="R",
|
||||
state="TX",
|
||||
chamber="house"
|
||||
),
|
||||
CongressionalTrade(
|
||||
politician="Josh Gottheimer",
|
||||
ticker="AVGO",
|
||||
action="purchase",
|
||||
amount_range="$50,001 - $100,000",
|
||||
transaction_date=datetime.now() - timedelta(days=3),
|
||||
filing_date=datetime.now() - timedelta(days=1),
|
||||
party="D",
|
||||
state="NJ",
|
||||
chamber="house"
|
||||
)
|
||||
]
|
||||
|
||||
if tickers:
|
||||
mock_trades = [t for t in mock_trades if t.ticker in tickers]
|
||||
|
||||
return mock_trades
|
||||
|
||||
async def fetch_insider_trades(self,
|
||||
ticker: str,
|
||||
days_back: int = 90) -> List[InsiderTrade]:
|
||||
"""
|
||||
Fetch insider trading data
|
||||
|
||||
Args:
|
||||
ticker: Stock ticker
|
||||
days_back: Number of days to look back
|
||||
|
||||
Returns:
|
||||
List of insider trades
|
||||
"""
|
||||
trades = []
|
||||
|
||||
if not YFINANCE_AVAILABLE:
|
||||
logger.warning("yfinance not available for insider data")
|
||||
return trades
|
||||
|
||||
try:
|
||||
stock = yf.Ticker(ticker)
|
||||
insider_df = stock.insider_transactions
|
||||
|
||||
if insider_df is not None and not insider_df.empty:
|
||||
cutoff_date = datetime.now() - timedelta(days=days_back)
|
||||
|
||||
for _, row in insider_df.iterrows():
|
||||
# Parse date
|
||||
date_str = row.get('Date', '')
|
||||
try:
|
||||
trade_date = datetime.strptime(date_str, '%Y-%m-%d %H:%M:%S')
|
||||
except:
|
||||
continue
|
||||
|
||||
if trade_date >= cutoff_date:
|
||||
trade = InsiderTrade(
|
||||
insider_name=row.get('Insider', 'Unknown'),
|
||||
ticker=ticker,
|
||||
action='Buy' if row.get('Transaction', '').lower() in ['buy', 'purchase'] else 'Sell',
|
||||
shares=int(row.get('Shares', 0)),
|
||||
value=float(row.get('Value', 0)),
|
||||
transaction_date=trade_date,
|
||||
position=row.get('Position', 'Unknown')
|
||||
)
|
||||
trades.append(trade)
|
||||
|
||||
logger.info(f"Fetched {len(trades)} insider trades for {ticker}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error fetching insider trades: {e}")
|
||||
|
||||
return trades
|
||||
|
||||
async def fetch_earnings_calendar(self,
|
||||
tickers: List[str],
|
||||
days_ahead: int = 30) -> List[EarningsEvent]:
|
||||
"""
|
||||
Fetch upcoming earnings events
|
||||
|
||||
Args:
|
||||
tickers: List of tickers to check
|
||||
days_ahead: Number of days to look ahead
|
||||
|
||||
Returns:
|
||||
List of earnings events
|
||||
"""
|
||||
events = []
|
||||
|
||||
if not YFINANCE_AVAILABLE:
|
||||
return events
|
||||
|
||||
try:
|
||||
for ticker in tickers:
|
||||
stock = yf.Ticker(ticker)
|
||||
calendar = stock.calendar
|
||||
|
||||
if calendar is not None and not calendar.empty:
|
||||
# Get earnings date
|
||||
earnings_date = calendar.get('Earnings Date')
|
||||
if earnings_date and len(earnings_date) > 0:
|
||||
event = EarningsEvent(
|
||||
ticker=ticker,
|
||||
earnings_date=earnings_date[0] if isinstance(earnings_date, list) else earnings_date,
|
||||
eps_estimate=calendar.get('EPS Estimate', 0),
|
||||
eps_actual=None, # Will be filled after earnings
|
||||
revenue_estimate=calendar.get('Revenue Estimate', 0),
|
||||
revenue_actual=None,
|
||||
surprise_percent=None
|
||||
)
|
||||
events.append(event)
|
||||
|
||||
self.earnings_calendar = events
|
||||
logger.info(f"Fetched {len(events)} upcoming earnings events")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error fetching earnings calendar: {e}")
|
||||
|
||||
return events
|
||||
|
||||
async def fetch_market_sentiment(self, ticker: str) -> Dict[str, Any]:
|
||||
"""
|
||||
Fetch market sentiment from various sources
|
||||
|
||||
Returns:
|
||||
Dictionary with sentiment data
|
||||
"""
|
||||
sentiment = {
|
||||
'ticker': ticker,
|
||||
'overall_sentiment': 'neutral',
|
||||
'sentiment_score': 0.0,
|
||||
'sources': {}
|
||||
}
|
||||
|
||||
try:
|
||||
# Fetch news sentiment using existing TradingAgents tools
|
||||
news_interface = AlphaVantageNewsInterface()
|
||||
news_data = news_interface.get_news(ticker, datetime.now().strftime('%Y-%m-%d'))
|
||||
|
||||
if news_data:
|
||||
# Simple sentiment analysis based on news
|
||||
positive_keywords = ['beat', 'surge', 'jump', 'gain', 'profit', 'upgrade']
|
||||
negative_keywords = ['miss', 'fall', 'drop', 'loss', 'downgrade', 'concern']
|
||||
|
||||
positive_count = 0
|
||||
negative_count = 0
|
||||
|
||||
for article in news_data[:10]: # Check first 10 articles
|
||||
title = article.get('title', '').lower()
|
||||
for keyword in positive_keywords:
|
||||
if keyword in title:
|
||||
positive_count += 1
|
||||
for keyword in negative_keywords:
|
||||
if keyword in title:
|
||||
negative_count += 1
|
||||
|
||||
# Calculate sentiment score
|
||||
total = positive_count + negative_count
|
||||
if total > 0:
|
||||
sentiment['sentiment_score'] = (positive_count - negative_count) / total
|
||||
|
||||
if sentiment['sentiment_score'] > 0.2:
|
||||
sentiment['overall_sentiment'] = 'positive'
|
||||
elif sentiment['sentiment_score'] < -0.2:
|
||||
sentiment['overall_sentiment'] = 'negative'
|
||||
|
||||
sentiment['sources']['news'] = {
|
||||
'positive_articles': positive_count,
|
||||
'negative_articles': negative_count,
|
||||
'total_articles': len(news_data)
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error fetching sentiment: {e}")
|
||||
|
||||
return sentiment
|
||||
|
||||
async def aggregate_signals(self, tickers: List[str]) -> List[MarketSignal]:
|
||||
"""
|
||||
Aggregate all data sources into trading signals
|
||||
|
||||
Args:
|
||||
tickers: List of tickers to analyze
|
||||
|
||||
Returns:
|
||||
List of market signals
|
||||
"""
|
||||
signals = []
|
||||
|
||||
# Fetch all data
|
||||
congress_trades = await self.fetch_congressional_trades(tickers, days_back=7)
|
||||
|
||||
for ticker in tickers:
|
||||
# Congressional signal
|
||||
ticker_congress = [t for t in congress_trades if t.ticker == ticker]
|
||||
if ticker_congress:
|
||||
recent_purchases = [t for t in ticker_congress if 'purchase' in t.action.lower()]
|
||||
recent_sales = [t for t in ticker_congress if 'sale' in t.action.lower()]
|
||||
|
||||
if len(recent_purchases) > len(recent_sales):
|
||||
signal = MarketSignal(
|
||||
ticker=ticker,
|
||||
signal_type='congressional',
|
||||
action='BUY',
|
||||
confidence=min(80 + len(recent_purchases) * 5, 95),
|
||||
data={
|
||||
'trades': [
|
||||
{
|
||||
'politician': t.politician,
|
||||
'amount': t.amount_range,
|
||||
'date': t.transaction_date.isoformat()
|
||||
}
|
||||
for t in recent_purchases[:3]
|
||||
]
|
||||
},
|
||||
timestamp=datetime.now()
|
||||
)
|
||||
signals.append(signal)
|
||||
|
||||
# Insider signal
|
||||
insider_trades = await self.fetch_insider_trades(ticker, days_back=30)
|
||||
if insider_trades:
|
||||
recent_buys = [t for t in insider_trades if t.action == 'Buy']
|
||||
recent_sells = [t for t in insider_trades if t.action == 'Sell']
|
||||
|
||||
if len(recent_buys) > len(recent_sells) * 2: # Strong buy signal
|
||||
signal = MarketSignal(
|
||||
ticker=ticker,
|
||||
signal_type='insider',
|
||||
action='BUY',
|
||||
confidence=min(70 + len(recent_buys) * 3, 90),
|
||||
data={
|
||||
'insider_buys': len(recent_buys),
|
||||
'insider_sells': len(recent_sells),
|
||||
'net_buying': sum(t.value for t in recent_buys)
|
||||
},
|
||||
timestamp=datetime.now()
|
||||
)
|
||||
signals.append(signal)
|
||||
|
||||
# Sentiment signal
|
||||
sentiment = await self.fetch_market_sentiment(ticker)
|
||||
if sentiment['overall_sentiment'] == 'positive' and sentiment['sentiment_score'] > 0.3:
|
||||
signal = MarketSignal(
|
||||
ticker=ticker,
|
||||
signal_type='sentiment',
|
||||
action='BUY',
|
||||
confidence=min(60 + sentiment['sentiment_score'] * 100, 85),
|
||||
data=sentiment,
|
||||
timestamp=datetime.now()
|
||||
)
|
||||
signals.append(signal)
|
||||
|
||||
self.market_signals = signals
|
||||
logger.info(f"Generated {len(signals)} market signals")
|
||||
return signals
|
||||
|
||||
def get_top_opportunities(self, n: int = 5) -> List[MarketSignal]:
|
||||
"""
|
||||
Get top trading opportunities based on confidence
|
||||
|
||||
Args:
|
||||
n: Number of opportunities to return
|
||||
|
||||
Returns:
|
||||
Top n signals by confidence
|
||||
"""
|
||||
sorted_signals = sorted(self.market_signals, key=lambda x: x.confidence, reverse=True)
|
||||
return sorted_signals[:n]
|
||||
|
||||
|
||||
# Example usage
|
||||
async def main():
|
||||
"""Example of using the data aggregator"""
|
||||
config = {
|
||||
'quiver_api_key': os.getenv('QUIVER_API_KEY'),
|
||||
'alpha_vantage_api_key': os.getenv('ALPHA_VANTAGE_API_KEY')
|
||||
}
|
||||
|
||||
aggregator = DataAggregator(config)
|
||||
|
||||
# Your portfolio tickers
|
||||
portfolio_tickers = ["AVGO", "MSFT", "MU", "NVDA", "TSM"]
|
||||
|
||||
# Fetch congressional trades
|
||||
congress_trades = await aggregator.fetch_congressional_trades(portfolio_tickers)
|
||||
print(f"\nCongressional Trades:")
|
||||
for trade in congress_trades:
|
||||
print(f" {trade.politician} - {trade.action} {trade.ticker} ({trade.amount_range})")
|
||||
|
||||
# Aggregate all signals
|
||||
signals = await aggregator.aggregate_signals(portfolio_tickers)
|
||||
print(f"\nTop Trading Signals:")
|
||||
for signal in aggregator.get_top_opportunities(3):
|
||||
print(f" {signal.ticker}: {signal.action} (Confidence: {signal.confidence}%) - {signal.signal_type}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
|
|
@ -0,0 +1,336 @@
|
|||
"""
|
||||
IBKR (Interactive Brokers) Connector
|
||||
====================================
|
||||
|
||||
Manages live connection to IBKR TWS/Gateway for portfolio monitoring and trading.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
from typing import Dict, List, Optional, Any
|
||||
from datetime import datetime, timedelta
|
||||
from dataclasses import dataclass
|
||||
import os
|
||||
|
||||
# Note: ib_insync needs to be installed
|
||||
try:
|
||||
from ib_insync import IB, Stock, Contract, MarketOrder, LimitOrder, util
|
||||
IBKR_AVAILABLE = True
|
||||
except ImportError:
|
||||
IBKR_AVAILABLE = False
|
||||
print("Warning: ib_insync not installed. Install with: pip install ib_insync")
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Position:
|
||||
"""Represents a portfolio position"""
|
||||
ticker: str
|
||||
shares: int
|
||||
avg_cost: float
|
||||
current_price: float
|
||||
market_value: float
|
||||
unrealized_pnl: float
|
||||
realized_pnl: float
|
||||
percent_change: float
|
||||
|
||||
|
||||
@dataclass
|
||||
class AccountInfo:
|
||||
"""Account information from IBKR"""
|
||||
net_liquidation: float
|
||||
buying_power: float
|
||||
cash_balance: float
|
||||
total_positions: int
|
||||
day_pnl: float
|
||||
total_pnl: float
|
||||
|
||||
|
||||
class IBKRConnector:
|
||||
"""
|
||||
Handles all interactions with Interactive Brokers API
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
host: str = "127.0.0.1",
|
||||
port: int = 7497, # 7497 for TWS paper, 7496 for TWS live
|
||||
client_id: int = 1):
|
||||
"""
|
||||
Initialize IBKR connector
|
||||
|
||||
Args:
|
||||
host: IP address of TWS/Gateway
|
||||
port: Port number (7497 for paper, 7496 for live)
|
||||
client_id: Unique client identifier
|
||||
"""
|
||||
self.host = host
|
||||
self.port = port
|
||||
self.client_id = client_id
|
||||
self.ib = None
|
||||
self.positions: Dict[str, Position] = {}
|
||||
self.account_info: Optional[AccountInfo] = None
|
||||
self.is_connected = False
|
||||
|
||||
async def connect(self) -> bool:
|
||||
"""Connect to IBKR TWS/Gateway"""
|
||||
if not IBKR_AVAILABLE:
|
||||
logger.error("ib_insync not installed")
|
||||
return False
|
||||
|
||||
try:
|
||||
self.ib = IB()
|
||||
await self.ib.connectAsync(self.host, self.port, self.client_id)
|
||||
self.is_connected = True
|
||||
logger.info(f"Connected to IBKR at {self.host}:{self.port}")
|
||||
|
||||
# Request account updates
|
||||
self.ib.reqAccountUpdates()
|
||||
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to connect to IBKR: {e}")
|
||||
self.is_connected = False
|
||||
return False
|
||||
|
||||
async def disconnect(self):
|
||||
"""Disconnect from IBKR"""
|
||||
if self.ib and self.is_connected:
|
||||
self.ib.disconnect()
|
||||
self.is_connected = False
|
||||
logger.info("Disconnected from IBKR")
|
||||
|
||||
async def sync_portfolio(self) -> Dict[str, Position]:
|
||||
"""
|
||||
Sync portfolio positions from IBKR
|
||||
|
||||
Returns:
|
||||
Dictionary of positions keyed by ticker
|
||||
"""
|
||||
if not self.is_connected:
|
||||
logger.error("Not connected to IBKR")
|
||||
return {}
|
||||
|
||||
try:
|
||||
# Get all positions
|
||||
ib_positions = self.ib.positions()
|
||||
self.positions.clear()
|
||||
|
||||
for pos in ib_positions:
|
||||
if pos.position != 0: # Skip closed positions
|
||||
ticker = pos.contract.symbol
|
||||
|
||||
# Get current market price
|
||||
contract = Stock(ticker, 'SMART', 'USD')
|
||||
self.ib.qualifyContracts(contract)
|
||||
ticker_data = self.ib.reqMktData(contract, '', False, False)
|
||||
await asyncio.sleep(1) # Wait for price data
|
||||
|
||||
current_price = ticker_data.marketPrice()
|
||||
if current_price is None or current_price <= 0:
|
||||
current_price = ticker_data.last or pos.avgCost
|
||||
|
||||
market_value = pos.position * current_price
|
||||
unrealized_pnl = market_value - (pos.position * pos.avgCost)
|
||||
percent_change = ((current_price - pos.avgCost) / pos.avgCost) * 100
|
||||
|
||||
position = Position(
|
||||
ticker=ticker,
|
||||
shares=int(pos.position),
|
||||
avg_cost=pos.avgCost,
|
||||
current_price=current_price,
|
||||
market_value=market_value,
|
||||
unrealized_pnl=unrealized_pnl,
|
||||
realized_pnl=0, # Would need to track trades
|
||||
percent_change=percent_change
|
||||
)
|
||||
|
||||
self.positions[ticker] = position
|
||||
logger.info(f"Synced position: {ticker} - {position.shares} shares @ ${current_price:.2f}")
|
||||
|
||||
logger.info(f"Portfolio sync complete: {len(self.positions)} positions")
|
||||
return self.positions
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error syncing portfolio: {e}")
|
||||
return self.positions
|
||||
|
||||
async def get_account_info(self) -> Optional[AccountInfo]:
|
||||
"""Get account information"""
|
||||
if not self.is_connected:
|
||||
return None
|
||||
|
||||
try:
|
||||
account_values = self.ib.accountValues()
|
||||
|
||||
# Extract key values
|
||||
values_dict = {av.tag: av.value for av in account_values}
|
||||
|
||||
self.account_info = AccountInfo(
|
||||
net_liquidation=float(values_dict.get('NetLiquidation', 0)),
|
||||
buying_power=float(values_dict.get('BuyingPower', 0)),
|
||||
cash_balance=float(values_dict.get('TotalCashBalance', 0)),
|
||||
total_positions=len(self.positions),
|
||||
day_pnl=float(values_dict.get('DailyPnL', 0)),
|
||||
total_pnl=float(values_dict.get('UnrealizedPnL', 0))
|
||||
)
|
||||
|
||||
return self.account_info
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting account info: {e}")
|
||||
return None
|
||||
|
||||
async def get_position(self, ticker: str) -> Optional[Position]:
|
||||
"""Get specific position by ticker"""
|
||||
return self.positions.get(ticker)
|
||||
|
||||
async def place_order(self,
|
||||
ticker: str,
|
||||
action: str, # 'BUY' or 'SELL'
|
||||
quantity: int,
|
||||
order_type: str = 'LIMIT',
|
||||
limit_price: Optional[float] = None,
|
||||
stop_price: Optional[float] = None) -> Optional[str]:
|
||||
"""
|
||||
Place an order (with safety checks)
|
||||
|
||||
Args:
|
||||
ticker: Stock symbol
|
||||
action: 'BUY' or 'SELL'
|
||||
quantity: Number of shares
|
||||
order_type: 'MARKET', 'LIMIT', 'STOP', 'STOP_LIMIT'
|
||||
limit_price: Limit price for limit orders
|
||||
stop_price: Stop price for stop orders
|
||||
|
||||
Returns:
|
||||
Order ID if successful, None otherwise
|
||||
"""
|
||||
if not self.is_connected:
|
||||
logger.error("Not connected to IBKR")
|
||||
return None
|
||||
|
||||
# Safety check - require confirmation for large orders
|
||||
if quantity > 100 or (limit_price and limit_price * quantity > 10000):
|
||||
logger.warning(f"Large order detected: {action} {quantity} {ticker}")
|
||||
# In production, you'd want manual confirmation here
|
||||
|
||||
try:
|
||||
# Create contract
|
||||
contract = Stock(ticker, 'SMART', 'USD')
|
||||
self.ib.qualifyContracts(contract)
|
||||
|
||||
# Create order based on type
|
||||
if order_type == 'MARKET':
|
||||
order = MarketOrder(action, quantity)
|
||||
elif order_type == 'LIMIT' and limit_price:
|
||||
order = LimitOrder(action, quantity, limit_price)
|
||||
else:
|
||||
logger.error(f"Unsupported order type: {order_type}")
|
||||
return None
|
||||
|
||||
# Place the order
|
||||
trade = self.ib.placeOrder(contract, order)
|
||||
|
||||
# Wait for order to be acknowledged
|
||||
await asyncio.sleep(1)
|
||||
|
||||
if trade.orderStatus.status in ['Submitted', 'PreSubmitted', 'Filled']:
|
||||
logger.info(f"Order placed: {action} {quantity} {ticker} - ID: {trade.order.orderId}")
|
||||
return str(trade.order.orderId)
|
||||
else:
|
||||
logger.error(f"Order failed: {trade.orderStatus.status}")
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error placing order: {e}")
|
||||
return None
|
||||
|
||||
async def get_market_data(self, ticker: str) -> Optional[Dict[str, float]]:
|
||||
"""
|
||||
Get real-time market data for a ticker
|
||||
|
||||
Returns:
|
||||
Dictionary with price data
|
||||
"""
|
||||
if not self.is_connected:
|
||||
return None
|
||||
|
||||
try:
|
||||
contract = Stock(ticker, 'SMART', 'USD')
|
||||
self.ib.qualifyContracts(contract)
|
||||
|
||||
ticker_data = self.ib.reqMktData(contract, '', False, False)
|
||||
await asyncio.sleep(2) # Wait for data
|
||||
|
||||
return {
|
||||
'last': ticker_data.last or 0,
|
||||
'bid': ticker_data.bid or 0,
|
||||
'ask': ticker_data.ask or 0,
|
||||
'volume': ticker_data.volume or 0,
|
||||
'high': ticker_data.high or 0,
|
||||
'low': ticker_data.low or 0,
|
||||
'close': ticker_data.close or 0
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting market data for {ticker}: {e}")
|
||||
return None
|
||||
|
||||
def get_portfolio_summary(self) -> Dict[str, Any]:
|
||||
"""Get portfolio summary"""
|
||||
if not self.positions:
|
||||
return {}
|
||||
|
||||
total_value = sum(p.market_value for p in self.positions.values())
|
||||
total_cost = sum(p.shares * p.avg_cost for p in self.positions.values())
|
||||
total_pnl = sum(p.unrealized_pnl for p in self.positions.values())
|
||||
|
||||
return {
|
||||
'total_value': total_value,
|
||||
'total_cost': total_cost,
|
||||
'total_unrealized_pnl': total_pnl,
|
||||
'total_percent_change': ((total_value - total_cost) / total_cost * 100) if total_cost > 0 else 0,
|
||||
'position_count': len(self.positions),
|
||||
'positions': [
|
||||
{
|
||||
'ticker': p.ticker,
|
||||
'shares': p.shares,
|
||||
'value': p.market_value,
|
||||
'pnl': p.unrealized_pnl,
|
||||
'percent': p.percent_change
|
||||
}
|
||||
for p in self.positions.values()
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
# Example usage
|
||||
async def main():
|
||||
"""Example of using the IBKR connector"""
|
||||
connector = IBKRConnector(port=7497) # Paper trading port
|
||||
|
||||
# Connect
|
||||
if await connector.connect():
|
||||
# Sync portfolio
|
||||
positions = await connector.sync_portfolio()
|
||||
print(f"Found {len(positions)} positions")
|
||||
|
||||
# Get account info
|
||||
account = await connector.get_account_info()
|
||||
if account:
|
||||
print(f"Net Liquidation: ${account.net_liquidation:,.2f}")
|
||||
print(f"Buying Power: ${account.buying_power:,.2f}")
|
||||
|
||||
# Get market data
|
||||
data = await connector.get_market_data("AAPL")
|
||||
if data:
|
||||
print(f"AAPL Last Price: ${data['last']}")
|
||||
|
||||
# Disconnect
|
||||
await connector.disconnect()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# For testing
|
||||
asyncio.run(main())
|
||||
|
|
@ -0,0 +1,522 @@
|
|||
"""
|
||||
Scheduler
|
||||
=========
|
||||
|
||||
Orchestrates periodic tasks for the autonomous trading system.
|
||||
Uses APScheduler for task scheduling and asyncio for async execution.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
from typing import Dict, List, Optional, Any
|
||||
from datetime import datetime, time, timedelta
|
||||
import os
|
||||
import sys
|
||||
|
||||
# Add parent directory to path for imports
|
||||
sys.path.append('..')
|
||||
|
||||
from apscheduler.schedulers.asyncio import AsyncIOScheduler
|
||||
from apscheduler.triggers.cron import CronTrigger
|
||||
from apscheduler.triggers.interval import IntervalTrigger
|
||||
|
||||
from .ibkr_connector import IBKRConnector
|
||||
from .data_aggregator import DataAggregator
|
||||
from .signal_processor import SignalProcessor
|
||||
from .alert_engine import AlertEngine, AlertType, AlertPriority
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AutonomousScheduler:
|
||||
"""
|
||||
Main scheduler for the autonomous trading system
|
||||
"""
|
||||
|
||||
def __init__(self, config: Optional[Dict] = None):
|
||||
"""
|
||||
Initialize the scheduler
|
||||
|
||||
Args:
|
||||
config: Configuration dictionary
|
||||
"""
|
||||
self.config = config or {}
|
||||
self.scheduler = AsyncIOScheduler()
|
||||
|
||||
# Initialize components
|
||||
self.ibkr = IBKRConnector(
|
||||
host=self.config.get('ibkr_host', '127.0.0.1'),
|
||||
port=self.config.get('ibkr_port', 7497),
|
||||
client_id=self.config.get('ibkr_client_id', 1)
|
||||
)
|
||||
|
||||
self.data_agg = DataAggregator(config)
|
||||
self.signal_processor = SignalProcessor(self.ibkr, self.data_agg, config)
|
||||
self.alert_engine = AlertEngine(config)
|
||||
|
||||
# Track system state
|
||||
self.is_running = False
|
||||
self.last_portfolio_check = None
|
||||
self.last_market_scan = None
|
||||
self.trading_enabled = self.config.get('trading_enabled', False)
|
||||
|
||||
# Your portfolio tickers
|
||||
self.portfolio_tickers = ["AVGO", "MSFT", "MU", "NVDA", "TSM"]
|
||||
self.watchlist = self.config.get('watchlist', ["AAPL", "TSLA", "META", "GOOGL"])
|
||||
|
||||
async def initialize(self) -> bool:
|
||||
"""
|
||||
Initialize connections and verify system readiness
|
||||
|
||||
Returns:
|
||||
True if initialization successful
|
||||
"""
|
||||
try:
|
||||
# Connect to IBKR
|
||||
logger.info("Connecting to IBKR...")
|
||||
connected = await self.ibkr.connect()
|
||||
|
||||
if not connected:
|
||||
logger.error("Failed to connect to IBKR. Make sure TWS/Gateway is running.")
|
||||
await self.alert_engine.send_alert(
|
||||
title="System Initialization Failed",
|
||||
message="Could not connect to IBKR. Check TWS/Gateway.",
|
||||
alert_type=AlertType.SYSTEM,
|
||||
priority=AlertPriority.CRITICAL
|
||||
)
|
||||
return False
|
||||
|
||||
# Initial portfolio sync
|
||||
logger.info("Syncing portfolio...")
|
||||
await self.ibkr.sync_portfolio()
|
||||
|
||||
# Send startup notification
|
||||
await self.alert_engine.send_alert(
|
||||
title="🚀 Autonomous Trading System Started",
|
||||
message=f"System initialized successfully.\nMonitoring {len(self.portfolio_tickers)} positions.",
|
||||
alert_type=AlertType.SYSTEM,
|
||||
priority=AlertPriority.INFO
|
||||
)
|
||||
|
||||
self.is_running = True
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Initialization error: {e}")
|
||||
return False
|
||||
|
||||
def setup_schedules(self):
|
||||
"""Configure all scheduled tasks"""
|
||||
|
||||
# Portfolio monitoring - every 5 minutes
|
||||
self.scheduler.add_job(
|
||||
self.monitor_portfolio,
|
||||
IntervalTrigger(minutes=5),
|
||||
id='portfolio_monitor',
|
||||
name='Portfolio Monitor',
|
||||
misfire_grace_time=60
|
||||
)
|
||||
|
||||
# Market scanning - every 15 minutes during market hours
|
||||
self.scheduler.add_job(
|
||||
self.scan_markets,
|
||||
IntervalTrigger(minutes=15),
|
||||
id='market_scanner',
|
||||
name='Market Scanner',
|
||||
misfire_grace_time=120
|
||||
)
|
||||
|
||||
# Congressional trades check - every hour
|
||||
self.scheduler.add_job(
|
||||
self.check_congressional_trades,
|
||||
IntervalTrigger(hours=1),
|
||||
id='congressional_monitor',
|
||||
name='Congressional Trade Monitor',
|
||||
misfire_grace_time=300
|
||||
)
|
||||
|
||||
# News monitoring - every 30 minutes
|
||||
self.scheduler.add_job(
|
||||
self.monitor_news,
|
||||
IntervalTrigger(minutes=30),
|
||||
id='news_monitor',
|
||||
name='News Monitor',
|
||||
misfire_grace_time=120
|
||||
)
|
||||
|
||||
# Daily portfolio summary - at 4:30 PM ET (after market close)
|
||||
self.scheduler.add_job(
|
||||
self.daily_summary,
|
||||
CronTrigger(hour=16, minute=30),
|
||||
id='daily_summary',
|
||||
name='Daily Summary',
|
||||
misfire_grace_time=300
|
||||
)
|
||||
|
||||
# Risk check - every 30 minutes during market hours
|
||||
self.scheduler.add_job(
|
||||
self.check_risk,
|
||||
IntervalTrigger(minutes=30),
|
||||
id='risk_monitor',
|
||||
name='Risk Monitor',
|
||||
misfire_grace_time=120
|
||||
)
|
||||
|
||||
# Pre-market check - at 8:30 AM ET
|
||||
self.scheduler.add_job(
|
||||
self.premarket_check,
|
||||
CronTrigger(hour=8, minute=30),
|
||||
id='premarket_check',
|
||||
name='Pre-Market Check',
|
||||
misfire_grace_time=300
|
||||
)
|
||||
|
||||
logger.info(f"Scheduled {len(self.scheduler.get_jobs())} jobs")
|
||||
|
||||
async def monitor_portfolio(self):
|
||||
"""Monitor portfolio positions and P&L"""
|
||||
if not self.is_running:
|
||||
return
|
||||
|
||||
try:
|
||||
logger.info("Running portfolio monitor...")
|
||||
|
||||
# Sync positions
|
||||
positions = await self.ibkr.sync_portfolio()
|
||||
|
||||
if not positions:
|
||||
logger.warning("No positions found")
|
||||
return
|
||||
|
||||
# Check for significant changes
|
||||
for ticker, position in positions.items():
|
||||
# Alert on large losses
|
||||
if position.percent_change < -5:
|
||||
await self.alert_engine.send_alert(
|
||||
title=f"⚠️ Risk Alert: {ticker}",
|
||||
message=f"{ticker} is down {abs(position.percent_change):.1f}%\n"
|
||||
f"Current: ${position.current_price:.2f}\n"
|
||||
f"P&L: ${position.unrealized_pnl:,.2f}",
|
||||
alert_type=AlertType.RISK_WARNING,
|
||||
priority=AlertPriority.HIGH,
|
||||
data={'ticker': ticker, 'loss_percent': position.percent_change}
|
||||
)
|
||||
|
||||
# Alert on large gains
|
||||
elif position.percent_change > 10:
|
||||
await self.alert_engine.send_alert(
|
||||
title=f"📈 Profit Alert: {ticker}",
|
||||
message=f"{ticker} is up {position.percent_change:.1f}%\n"
|
||||
f"Consider taking profits.\n"
|
||||
f"P&L: ${position.unrealized_pnl:,.2f}",
|
||||
alert_type=AlertType.PORTFOLIO_UPDATE,
|
||||
priority=AlertPriority.MEDIUM,
|
||||
data={'ticker': ticker, 'gain_percent': position.percent_change}
|
||||
)
|
||||
|
||||
self.last_portfolio_check = datetime.now()
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Portfolio monitor error: {e}")
|
||||
|
||||
async def scan_markets(self):
|
||||
"""Scan markets for trading opportunities"""
|
||||
if not self.is_running:
|
||||
return
|
||||
|
||||
try:
|
||||
logger.info("Running market scanner...")
|
||||
|
||||
# Get all tickers to scan
|
||||
all_tickers = list(set(self.portfolio_tickers + self.watchlist))
|
||||
|
||||
# Aggregate signals
|
||||
await self.data_agg.aggregate_signals(all_tickers)
|
||||
|
||||
# Process signals
|
||||
recommendations = []
|
||||
for ticker in all_tickers:
|
||||
rec = await self.signal_processor.process_signals(ticker)
|
||||
if rec and rec.action != 'HOLD' and rec.confidence > 70:
|
||||
recommendations.append(rec)
|
||||
|
||||
# Send top recommendations
|
||||
recommendations.sort(key=lambda x: x.confidence, reverse=True)
|
||||
|
||||
for rec in recommendations[:3]: # Top 3 opportunities
|
||||
await self.alert_engine.send_trading_signal(rec)
|
||||
await asyncio.sleep(1) # Avoid flooding
|
||||
|
||||
self.last_market_scan = datetime.now()
|
||||
logger.info(f"Found {len(recommendations)} opportunities")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Market scanner error: {e}")
|
||||
|
||||
async def check_congressional_trades(self):
|
||||
"""Check for new congressional trades"""
|
||||
if not self.is_running:
|
||||
return
|
||||
|
||||
try:
|
||||
logger.info("Checking congressional trades...")
|
||||
|
||||
# Get recent trades
|
||||
trades = await self.data_agg.fetch_congressional_trades(
|
||||
self.portfolio_tickers,
|
||||
days_back=2
|
||||
)
|
||||
|
||||
# Alert on trades matching portfolio
|
||||
for trade in trades:
|
||||
if trade.ticker in self.portfolio_tickers:
|
||||
emoji = "🟢" if 'purchase' in trade.action else "🔴"
|
||||
await self.alert_engine.send_alert(
|
||||
title=f"🏛️ Congressional Trade: {trade.ticker}",
|
||||
message=f"{emoji} {trade.politician} ({trade.party}-{trade.state}) "
|
||||
f"{trade.action} {trade.ticker}\n"
|
||||
f"Amount: {trade.amount_range}\n"
|
||||
f"Filed: {trade.filing_date.strftime('%Y-%m-%d')}",
|
||||
alert_type=AlertType.CONGRESSIONAL_TRADE,
|
||||
priority=AlertPriority.HIGH,
|
||||
data={
|
||||
'ticker': trade.ticker,
|
||||
'politician': trade.politician,
|
||||
'action': trade.action
|
||||
}
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Congressional trade check error: {e}")
|
||||
|
||||
async def monitor_news(self):
|
||||
"""Monitor news for portfolio companies"""
|
||||
if not self.is_running:
|
||||
return
|
||||
|
||||
try:
|
||||
logger.info("Monitoring news...")
|
||||
|
||||
for ticker in self.portfolio_tickers[:3]: # Limit to avoid rate limits
|
||||
sentiment = await self.data_agg.fetch_market_sentiment(ticker)
|
||||
|
||||
# Alert on strong sentiment
|
||||
if sentiment['sentiment_score'] > 0.5:
|
||||
await self.alert_engine.send_alert(
|
||||
title=f"📰 Positive News: {ticker}",
|
||||
message=f"Strong positive sentiment detected for {ticker}\n"
|
||||
f"Sentiment Score: {sentiment['sentiment_score']:.2f}",
|
||||
alert_type=AlertType.MARKET_MOVE,
|
||||
priority=AlertPriority.MEDIUM,
|
||||
data=sentiment
|
||||
)
|
||||
|
||||
await asyncio.sleep(2) # Rate limiting
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"News monitor error: {e}")
|
||||
|
||||
async def check_risk(self):
|
||||
"""Monitor portfolio risk levels"""
|
||||
if not self.is_running:
|
||||
return
|
||||
|
||||
try:
|
||||
logger.info("Checking portfolio risk...")
|
||||
|
||||
# Get account info
|
||||
account_info = await self.ibkr.get_account_info()
|
||||
|
||||
if not account_info:
|
||||
return
|
||||
|
||||
portfolio_summary = self.ibkr.get_portfolio_summary()
|
||||
|
||||
# Check daily P&L
|
||||
if account_info.day_pnl < -1000: # Lost more than $1000 today
|
||||
await self.alert_engine.send_alert(
|
||||
title="⚠️ Daily Loss Alert",
|
||||
message=f"Today's P&L: ${account_info.day_pnl:,.2f}\n"
|
||||
f"Consider reducing risk or stopping for the day.",
|
||||
alert_type=AlertType.RISK_WARNING,
|
||||
priority=AlertPriority.HIGH
|
||||
)
|
||||
|
||||
# Check concentration risk
|
||||
if portfolio_summary['positions']:
|
||||
max_position = max(portfolio_summary['positions'],
|
||||
key=lambda x: x['value'])
|
||||
concentration = max_position['value'] / portfolio_summary['total_value']
|
||||
|
||||
if concentration > 0.30: # More than 30% in one position
|
||||
await self.alert_engine.send_alert(
|
||||
title=f"⚠️ Concentration Risk: {max_position['ticker']}",
|
||||
message=f"{max_position['ticker']} is {concentration:.1%} of portfolio\n"
|
||||
f"Consider rebalancing to reduce risk.",
|
||||
alert_type=AlertType.RISK_WARNING,
|
||||
priority=AlertPriority.MEDIUM
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Risk check error: {e}")
|
||||
|
||||
async def premarket_check(self):
|
||||
"""Pre-market preparation and alerts"""
|
||||
if not self.is_running:
|
||||
return
|
||||
|
||||
try:
|
||||
logger.info("Running pre-market check...")
|
||||
|
||||
# Check earnings today
|
||||
earnings = await self.data_agg.fetch_earnings_calendar(
|
||||
self.portfolio_tickers,
|
||||
days_ahead=1
|
||||
)
|
||||
|
||||
for event in earnings:
|
||||
await self.alert_engine.send_alert(
|
||||
title=f"📊 Earnings Today: {event.ticker}",
|
||||
message=f"{event.ticker} reports earnings today\n"
|
||||
f"EPS Estimate: ${event.eps_estimate:.2f}",
|
||||
alert_type=AlertType.EARNINGS,
|
||||
priority=AlertPriority.HIGH
|
||||
)
|
||||
|
||||
# Run full market scan
|
||||
await self.scan_markets()
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Pre-market check error: {e}")
|
||||
|
||||
async def daily_summary(self):
|
||||
"""Generate and send daily portfolio summary"""
|
||||
if not self.is_running:
|
||||
return
|
||||
|
||||
try:
|
||||
logger.info("Generating daily summary...")
|
||||
|
||||
# Get portfolio summary
|
||||
portfolio_summary = self.ibkr.get_portfolio_summary()
|
||||
account_info = await self.ibkr.get_account_info()
|
||||
|
||||
if portfolio_summary and account_info:
|
||||
await self.alert_engine.send_portfolio_summary(portfolio_summary)
|
||||
|
||||
# Additional daily stats
|
||||
message = f"""
|
||||
📊 **Daily Trading Summary**
|
||||
|
||||
Account Value: ${account_info.net_liquidation:,.2f}
|
||||
Today's P&L: ${account_info.day_pnl:+,.2f}
|
||||
Buying Power: ${account_info.buying_power:,.2f}
|
||||
|
||||
Top Opportunities Found: {len(self.signal_processor.recommendations)}
|
||||
Alerts Sent Today: {len(self.alert_engine.sent_alerts)}
|
||||
|
||||
System Status: ✅ All systems operational
|
||||
"""
|
||||
await self.alert_engine.send_alert(
|
||||
title="📈 End of Day Report",
|
||||
message=message,
|
||||
alert_type=AlertType.PORTFOLIO_UPDATE,
|
||||
priority=AlertPriority.INFO
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Daily summary error: {e}")
|
||||
|
||||
async def start(self):
|
||||
"""Start the autonomous trading system"""
|
||||
logger.info("Starting Autonomous Trading System...")
|
||||
|
||||
# Initialize
|
||||
if not await self.initialize():
|
||||
logger.error("Failed to initialize system")
|
||||
return
|
||||
|
||||
# Setup schedules
|
||||
self.setup_schedules()
|
||||
|
||||
# Start scheduler
|
||||
self.scheduler.start()
|
||||
logger.info("Scheduler started")
|
||||
|
||||
# Run initial scans
|
||||
await self.monitor_portfolio()
|
||||
await self.scan_markets()
|
||||
|
||||
logger.info("Autonomous Trading System is running")
|
||||
|
||||
# Keep running
|
||||
try:
|
||||
while self.is_running:
|
||||
await asyncio.sleep(60) # Check every minute
|
||||
except KeyboardInterrupt:
|
||||
logger.info("Shutting down...")
|
||||
await self.stop()
|
||||
|
||||
async def stop(self):
|
||||
"""Stop the autonomous trading system"""
|
||||
logger.info("Stopping Autonomous Trading System...")
|
||||
|
||||
self.is_running = False
|
||||
|
||||
# Shutdown scheduler
|
||||
if self.scheduler.running:
|
||||
self.scheduler.shutdown(wait=False)
|
||||
|
||||
# Disconnect from IBKR
|
||||
await self.ibkr.disconnect()
|
||||
|
||||
# Send shutdown notification
|
||||
await self.alert_engine.send_alert(
|
||||
title="🛑 System Shutdown",
|
||||
message="Autonomous Trading System stopped",
|
||||
alert_type=AlertType.SYSTEM,
|
||||
priority=AlertPriority.INFO
|
||||
)
|
||||
|
||||
logger.info("System stopped")
|
||||
|
||||
|
||||
# Main entry point
|
||||
async def main():
|
||||
"""Main entry point for the autonomous system"""
|
||||
|
||||
# Load configuration
|
||||
config = {
|
||||
# IBKR settings
|
||||
'ibkr_host': os.getenv('IBKR_HOST', '127.0.0.1'),
|
||||
'ibkr_port': int(os.getenv('IBKR_PORT', 7497)), # 7497 for paper, 7496 for live
|
||||
'ibkr_client_id': int(os.getenv('IBKR_CLIENT_ID', 1)),
|
||||
|
||||
# API keys
|
||||
'quiver_api_key': os.getenv('QUIVER_API_KEY'),
|
||||
'alpha_vantage_api_key': os.getenv('ALPHA_VANTAGE_API_KEY'),
|
||||
'openai_api_key': os.getenv('OPENAI_API_KEY'),
|
||||
|
||||
# Notification settings
|
||||
'discord_webhook_url': os.getenv('DISCORD_WEBHOOK_URL'),
|
||||
'telegram_bot_token': os.getenv('TELEGRAM_BOT_TOKEN'),
|
||||
'telegram_chat_id': os.getenv('TELEGRAM_CHAT_ID'),
|
||||
|
||||
# Trading settings
|
||||
'trading_enabled': os.getenv('TRADING_ENABLED', 'false').lower() == 'true',
|
||||
'watchlist': ['AAPL', 'TSLA', 'META', 'GOOGL', 'AMZN']
|
||||
}
|
||||
|
||||
# Setup logging
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
||||
)
|
||||
|
||||
# Create and start scheduler
|
||||
scheduler = AutonomousScheduler(config)
|
||||
await scheduler.start()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
|
|
@ -0,0 +1,414 @@
|
|||
"""
|
||||
Signal Processor
|
||||
===============
|
||||
|
||||
Processes signals from multiple sources and generates actionable trading recommendations
|
||||
with specific entry/exit prices using TradingAgents AI.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
from typing import Dict, List, Optional, Any, Tuple
|
||||
from datetime import datetime, timedelta
|
||||
from dataclasses import dataclass
|
||||
import statistics
|
||||
|
||||
# Import TradingAgents
|
||||
import sys
|
||||
sys.path.append('..')
|
||||
from tradingagents.graph.trading_graph import TradingAgentsGraph
|
||||
from tradingagents.default_config import DEFAULT_CONFIG
|
||||
|
||||
# Import our modules
|
||||
from .ibkr_connector import IBKRConnector, Position
|
||||
from .data_aggregator import DataAggregator, MarketSignal
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@dataclass
|
||||
class TradingRecommendation:
|
||||
"""Complete trading recommendation with entry/exit points"""
|
||||
ticker: str
|
||||
action: str # 'BUY', 'SELL', 'HOLD'
|
||||
current_price: float
|
||||
entry_price_min: float
|
||||
entry_price_max: float
|
||||
target_price_1: float
|
||||
target_price_2: float
|
||||
stop_loss: float
|
||||
confidence: float # 0-100
|
||||
position_size: float # Percentage of portfolio
|
||||
reasoning: str
|
||||
data_sources: List[str]
|
||||
risk_level: str # 'LOW', 'MEDIUM', 'HIGH'
|
||||
timestamp: datetime
|
||||
|
||||
|
||||
class SignalProcessor:
|
||||
"""
|
||||
Processes signals and generates trading recommendations
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
ibkr_connector: IBKRConnector,
|
||||
data_aggregator: DataAggregator,
|
||||
config: Optional[Dict] = None):
|
||||
"""
|
||||
Initialize signal processor
|
||||
|
||||
Args:
|
||||
ibkr_connector: IBKR connection instance
|
||||
data_aggregator: Data aggregator instance
|
||||
config: Configuration dictionary
|
||||
"""
|
||||
self.ibkr = ibkr_connector
|
||||
self.data_agg = data_aggregator
|
||||
self.config = config or DEFAULT_CONFIG.copy()
|
||||
|
||||
# Configure TradingAgents for fast processing
|
||||
self.config['deep_think_llm'] = 'gpt-4o-mini'
|
||||
self.config['quick_think_llm'] = 'gpt-4o-mini'
|
||||
self.config['max_debate_rounds'] = 1
|
||||
|
||||
self.trading_agents = TradingAgentsGraph(debug=False, config=self.config)
|
||||
self.recommendations: List[TradingRecommendation] = []
|
||||
|
||||
async def calculate_technical_levels(self, ticker: str) -> Dict[str, float]:
|
||||
"""
|
||||
Calculate technical support/resistance levels
|
||||
|
||||
Args:
|
||||
ticker: Stock ticker
|
||||
|
||||
Returns:
|
||||
Dictionary with technical levels
|
||||
"""
|
||||
try:
|
||||
import yfinance as yf
|
||||
stock = yf.Ticker(ticker)
|
||||
|
||||
# Get recent price data
|
||||
hist = stock.history(period="3mo")
|
||||
|
||||
if hist.empty:
|
||||
return {}
|
||||
|
||||
current_price = hist['Close'].iloc[-1]
|
||||
high_3m = hist['High'].max()
|
||||
low_3m = hist['Low'].min()
|
||||
|
||||
# Calculate moving averages
|
||||
ma_20 = hist['Close'].tail(20).mean()
|
||||
ma_50 = hist['Close'].tail(50).mean() if len(hist) >= 50 else ma_20
|
||||
|
||||
# Calculate support/resistance levels
|
||||
# Using pivot points
|
||||
last_high = hist['High'].iloc[-1]
|
||||
last_low = hist['Low'].iloc[-1]
|
||||
last_close = hist['Close'].iloc[-1]
|
||||
pivot = (last_high + last_low + last_close) / 3
|
||||
|
||||
resistance_1 = 2 * pivot - last_low
|
||||
resistance_2 = pivot + (last_high - last_low)
|
||||
support_1 = 2 * pivot - last_high
|
||||
support_2 = pivot - (last_high - last_low)
|
||||
|
||||
# Calculate RSI
|
||||
delta = hist['Close'].diff()
|
||||
gain = (delta.where(delta > 0, 0)).rolling(window=14).mean()
|
||||
loss = (-delta.where(delta < 0, 0)).rolling(window=14).mean()
|
||||
rs = gain / loss
|
||||
rsi = 100 - (100 / (1 + rs))
|
||||
current_rsi = rsi.iloc[-1]
|
||||
|
||||
return {
|
||||
'current_price': current_price,
|
||||
'resistance_1': resistance_1,
|
||||
'resistance_2': resistance_2,
|
||||
'support_1': support_1,
|
||||
'support_2': support_2,
|
||||
'ma_20': ma_20,
|
||||
'ma_50': ma_50,
|
||||
'high_3m': high_3m,
|
||||
'low_3m': low_3m,
|
||||
'rsi': current_rsi,
|
||||
'pivot': pivot
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error calculating technical levels for {ticker}: {e}")
|
||||
return {}
|
||||
|
||||
async def analyze_position_sizing(self,
|
||||
ticker: str,
|
||||
confidence: float,
|
||||
risk_level: str) -> float:
|
||||
"""
|
||||
Calculate appropriate position size based on portfolio and risk
|
||||
|
||||
Args:
|
||||
ticker: Stock ticker
|
||||
confidence: Signal confidence (0-100)
|
||||
risk_level: Risk level of the trade
|
||||
|
||||
Returns:
|
||||
Position size as percentage of portfolio
|
||||
"""
|
||||
# Get current portfolio info
|
||||
await self.ibkr.sync_portfolio()
|
||||
portfolio_summary = self.ibkr.get_portfolio_summary()
|
||||
|
||||
# Check if we already have a position
|
||||
existing_position = await self.ibkr.get_position(ticker)
|
||||
|
||||
# Base position sizing rules
|
||||
max_position = 0.20 # Max 20% in any single position
|
||||
base_size = 0.10 # Base 10% position
|
||||
|
||||
# Adjust based on confidence
|
||||
if confidence > 90:
|
||||
size_multiplier = 1.5
|
||||
elif confidence > 80:
|
||||
size_multiplier = 1.2
|
||||
elif confidence > 70:
|
||||
size_multiplier = 1.0
|
||||
else:
|
||||
size_multiplier = 0.7
|
||||
|
||||
# Adjust based on risk
|
||||
risk_multipliers = {
|
||||
'LOW': 1.2,
|
||||
'MEDIUM': 1.0,
|
||||
'HIGH': 0.6
|
||||
}
|
||||
risk_multiplier = risk_multipliers.get(risk_level, 1.0)
|
||||
|
||||
# Calculate position size
|
||||
position_size = base_size * size_multiplier * risk_multiplier
|
||||
|
||||
# If we already have a position, consider scaling
|
||||
if existing_position:
|
||||
current_weight = existing_position.market_value / portfolio_summary['total_value']
|
||||
remaining_capacity = max_position - current_weight
|
||||
position_size = min(position_size, remaining_capacity)
|
||||
|
||||
# Ensure within limits
|
||||
position_size = max(0.05, min(position_size, max_position)) # Between 5% and 20%
|
||||
|
||||
return round(position_size, 3)
|
||||
|
||||
async def process_signals(self, ticker: str) -> Optional[TradingRecommendation]:
|
||||
"""
|
||||
Process all signals for a ticker and generate recommendation
|
||||
|
||||
Args:
|
||||
ticker: Stock ticker
|
||||
|
||||
Returns:
|
||||
Trading recommendation if signals are strong enough
|
||||
"""
|
||||
try:
|
||||
# Get all signals for this ticker
|
||||
signals = [s for s in self.data_agg.market_signals if s.ticker == ticker]
|
||||
|
||||
if not signals:
|
||||
logger.info(f"No signals for {ticker}")
|
||||
return None
|
||||
|
||||
# Calculate technical levels
|
||||
tech_levels = await self.calculate_technical_levels(ticker)
|
||||
|
||||
if not tech_levels:
|
||||
logger.warning(f"Could not calculate technical levels for {ticker}")
|
||||
return None
|
||||
|
||||
current_price = tech_levels['current_price']
|
||||
|
||||
# Run TradingAgents analysis
|
||||
logger.info(f"Running TradingAgents analysis for {ticker}")
|
||||
_, ai_decision = self.trading_agents.propagate(ticker, datetime.now().strftime('%Y-%m-%d'))
|
||||
|
||||
# Combine AI decision with our signals
|
||||
avg_confidence = statistics.mean([s.confidence for s in signals])
|
||||
|
||||
# Determine action based on signals and AI
|
||||
buy_signals = [s for s in signals if s.action == 'BUY']
|
||||
sell_signals = [s for s in signals if s.action == 'SELL']
|
||||
|
||||
if len(buy_signals) > len(sell_signals) and 'buy' in ai_decision.lower():
|
||||
action = 'BUY'
|
||||
# Entry points near support or current price
|
||||
entry_min = min(current_price * 0.995, tech_levels['support_1'])
|
||||
entry_max = current_price * 1.005
|
||||
|
||||
# Targets based on resistance levels
|
||||
target_1 = tech_levels['resistance_1']
|
||||
target_2 = tech_levels['resistance_2']
|
||||
|
||||
# Stop loss below support
|
||||
stop_loss = tech_levels['support_1'] * 0.98
|
||||
|
||||
elif len(sell_signals) > len(buy_signals) or 'sell' in ai_decision.lower():
|
||||
action = 'SELL'
|
||||
# For selling, entry is current price range
|
||||
entry_min = current_price * 0.995
|
||||
entry_max = current_price * 1.01
|
||||
|
||||
# Targets for selling (lower prices)
|
||||
target_1 = tech_levels['support_1']
|
||||
target_2 = tech_levels['support_2']
|
||||
|
||||
# Stop loss above resistance for shorts
|
||||
stop_loss = tech_levels['resistance_1'] * 1.02
|
||||
|
||||
else:
|
||||
action = 'HOLD'
|
||||
entry_min = entry_max = current_price
|
||||
target_1 = target_2 = current_price
|
||||
stop_loss = current_price * 0.95
|
||||
|
||||
# Determine risk level
|
||||
rsi = tech_levels.get('rsi', 50)
|
||||
if rsi > 70 or rsi < 30:
|
||||
risk_level = 'HIGH'
|
||||
elif 40 <= rsi <= 60:
|
||||
risk_level = 'LOW'
|
||||
else:
|
||||
risk_level = 'MEDIUM'
|
||||
|
||||
# Calculate position size
|
||||
position_size = await self.analyze_position_sizing(ticker, avg_confidence, risk_level)
|
||||
|
||||
# Build reasoning
|
||||
reasoning_parts = [
|
||||
f"AI Analysis: {ai_decision[:200]}",
|
||||
f"Signals: {len(buy_signals)} buy, {len(sell_signals)} sell"
|
||||
]
|
||||
|
||||
for signal in signals[:2]: # Include top 2 signals
|
||||
if signal.signal_type == 'congressional':
|
||||
reasoning_parts.append(f"Congressional activity detected")
|
||||
elif signal.signal_type == 'insider':
|
||||
reasoning_parts.append(f"Insider buying activity")
|
||||
elif signal.signal_type == 'sentiment':
|
||||
reasoning_parts.append(f"Positive market sentiment")
|
||||
|
||||
reasoning_parts.append(f"RSI: {rsi:.1f}")
|
||||
|
||||
# Create recommendation
|
||||
recommendation = TradingRecommendation(
|
||||
ticker=ticker,
|
||||
action=action,
|
||||
current_price=current_price,
|
||||
entry_price_min=round(entry_min, 2),
|
||||
entry_price_max=round(entry_max, 2),
|
||||
target_price_1=round(target_1, 2),
|
||||
target_price_2=round(target_2, 2),
|
||||
stop_loss=round(stop_loss, 2),
|
||||
confidence=avg_confidence,
|
||||
position_size=position_size,
|
||||
reasoning=' | '.join(reasoning_parts),
|
||||
data_sources=[s.signal_type for s in signals],
|
||||
risk_level=risk_level,
|
||||
timestamp=datetime.now()
|
||||
)
|
||||
|
||||
self.recommendations.append(recommendation)
|
||||
return recommendation
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error processing signals for {ticker}: {e}")
|
||||
return None
|
||||
|
||||
async def process_portfolio(self) -> List[TradingRecommendation]:
|
||||
"""
|
||||
Process entire portfolio and generate recommendations
|
||||
|
||||
Returns:
|
||||
List of trading recommendations
|
||||
"""
|
||||
# Get current positions
|
||||
await self.ibkr.sync_portfolio()
|
||||
positions = self.ibkr.positions
|
||||
|
||||
# Get tickers from portfolio
|
||||
portfolio_tickers = list(positions.keys())
|
||||
|
||||
# Also analyze some high-opportunity tickers even if not in portfolio
|
||||
watchlist = ["NVDA", "TSLA", "AAPL", "MSFT", "AVGO"]
|
||||
all_tickers = list(set(portfolio_tickers + watchlist))
|
||||
|
||||
# Get signals for all tickers
|
||||
await self.data_agg.aggregate_signals(all_tickers)
|
||||
|
||||
# Process each ticker
|
||||
recommendations = []
|
||||
for ticker in all_tickers:
|
||||
logger.info(f"Processing {ticker}")
|
||||
rec = await self.process_signals(ticker)
|
||||
if rec and rec.action != 'HOLD':
|
||||
recommendations.append(rec)
|
||||
|
||||
# Sort by confidence
|
||||
recommendations.sort(key=lambda x: x.confidence, reverse=True)
|
||||
|
||||
logger.info(f"Generated {len(recommendations)} recommendations")
|
||||
return recommendations
|
||||
|
||||
def format_recommendation(self, rec: TradingRecommendation) -> str:
|
||||
"""
|
||||
Format recommendation for display
|
||||
|
||||
Args:
|
||||
rec: Trading recommendation
|
||||
|
||||
Returns:
|
||||
Formatted string
|
||||
"""
|
||||
# Calculate percentage gains
|
||||
gain_1 = ((rec.target_price_1 - rec.current_price) / rec.current_price) * 100
|
||||
gain_2 = ((rec.target_price_2 - rec.current_price) / rec.current_price) * 100
|
||||
loss = ((rec.stop_loss - rec.current_price) / rec.current_price) * 100
|
||||
|
||||
formatted = f"""
|
||||
━━━━━━━━━━━━━━━━━━━━━━━
|
||||
🎯 ACTION: {rec.action}
|
||||
📈 TICKER: {rec.ticker}
|
||||
💰 CURRENT: ${rec.current_price:.2f}
|
||||
📍 ENTRY: ${rec.entry_price_min:.2f} - ${rec.entry_price_max:.2f}
|
||||
🎯 TARGET 1: ${rec.target_price_1:.2f} ({gain_1:+.1f}%)
|
||||
🎯 TARGET 2: ${rec.target_price_2:.2f} ({gain_2:+.1f}%)
|
||||
🛑 STOP LOSS: ${rec.stop_loss:.2f} ({loss:.1f}%)
|
||||
📊 CONFIDENCE: {rec.confidence:.0f}%
|
||||
💼 POSITION SIZE: {rec.position_size:.1%} of portfolio
|
||||
⚠️ RISK: {rec.risk_level}
|
||||
|
||||
📝 REASONING:
|
||||
{rec.reasoning}
|
||||
|
||||
📅 Generated: {rec.timestamp.strftime('%Y-%m-%d %H:%M')}
|
||||
━━━━━━━━━━━━━━━━━━━━━━━
|
||||
"""
|
||||
return formatted
|
||||
|
||||
|
||||
# Example usage
|
||||
async def main():
|
||||
"""Example of using the signal processor"""
|
||||
# Initialize components
|
||||
ibkr = IBKRConnector()
|
||||
data_agg = DataAggregator()
|
||||
processor = SignalProcessor(ibkr, data_agg)
|
||||
|
||||
# Connect to IBKR (would need TWS running)
|
||||
# await ibkr.connect()
|
||||
|
||||
# Process a single ticker
|
||||
rec = await processor.process_signals("NVDA")
|
||||
if rec:
|
||||
print(processor.format_recommendation(rec))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
|
|
@ -0,0 +1,102 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Autonomous Trading System Main Entry Point
|
||||
==========================================
|
||||
|
||||
Run this to start the 24/7 autonomous trading system.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
import sys
|
||||
import signal
|
||||
from pathlib import Path
|
||||
|
||||
# Add current directory to path
|
||||
sys.path.insert(0, str(Path(__file__).parent))
|
||||
|
||||
from autonomous.scheduler import AutonomousScheduler
|
||||
from autonomous.config.settings import Config
|
||||
|
||||
|
||||
# Global scheduler instance for signal handling
|
||||
scheduler = None
|
||||
|
||||
|
||||
def signal_handler(sig, frame):
|
||||
"""Handle shutdown signals gracefully"""
|
||||
print("\n🛑 Shutdown signal received...")
|
||||
if scheduler:
|
||||
asyncio.create_task(scheduler.stop())
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
async def main():
|
||||
"""Main entry point"""
|
||||
global scheduler
|
||||
|
||||
# Setup logging
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
||||
handlers=[
|
||||
logging.FileHandler('autonomous_trader.log'),
|
||||
logging.StreamHandler()
|
||||
]
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
print("""
|
||||
╔══════════════════════════════════════════════════════════════╗
|
||||
║ ║
|
||||
║ 🤖 AUTONOMOUS TRADING SYSTEM 🤖 ║
|
||||
║ ║
|
||||
║ 24/7 Market Monitoring | AI-Powered | Multi-Source ║
|
||||
║ ║
|
||||
╚══════════════════════════════════════════════════════════════╝
|
||||
""")
|
||||
|
||||
# Validate configuration
|
||||
if not Config.validate():
|
||||
logger.error("Configuration validation failed. Please check your settings.")
|
||||
return
|
||||
|
||||
# Display configuration
|
||||
print(f"📊 Portfolio: {', '.join(Config.PORTFOLIO_TICKERS)}")
|
||||
print(f"👀 Watchlist: {', '.join(Config.WATCHLIST)}")
|
||||
print(f"🎯 Mode: {'PAPER TRADING' if Config.PAPER_TRADING else '⚠️ LIVE TRADING'}")
|
||||
print(f"💼 Trading: {'ENABLED' if Config.TRADING_ENABLED else 'DISABLED (Monitoring Only)'}")
|
||||
print()
|
||||
|
||||
# Confirm before starting
|
||||
if not Config.PAPER_TRADING and Config.TRADING_ENABLED:
|
||||
response = input("⚠️ WARNING: Live trading is enabled! Continue? (yes/no): ")
|
||||
if response.lower() != 'yes':
|
||||
print("Aborted.")
|
||||
return
|
||||
|
||||
# Create scheduler
|
||||
scheduler = AutonomousScheduler(Config.to_dict())
|
||||
|
||||
# Setup signal handlers
|
||||
signal.signal(signal.SIGINT, signal_handler)
|
||||
signal.signal(signal.SIGTERM, signal_handler)
|
||||
|
||||
try:
|
||||
# Start the system
|
||||
await scheduler.start()
|
||||
except Exception as e:
|
||||
logger.error(f"Fatal error: {e}", exc_info=True)
|
||||
if scheduler:
|
||||
await scheduler.stop()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
asyncio.run(main())
|
||||
except KeyboardInterrupt:
|
||||
print("\n👋 Goodbye!")
|
||||
except Exception as e:
|
||||
print(f"❌ Error: {e}")
|
||||
sys.exit(1)
|
||||
17
main.py
17
main.py
|
|
@ -23,9 +23,22 @@ config["data_vendors"] = {
|
|||
# Initialize with custom config
|
||||
ta = TradingAgentsGraph(debug=True, config=config)
|
||||
|
||||
# forward propagate
|
||||
_, decision = ta.propagate("NVDA", "2024-05-10")
|
||||
# Your IBKR portfolio tickers
|
||||
PORTFOLIO_TICKERS = ["AVGO", "MSFT", "MU", "NVDA", "TSM"] # Excluding SXRV (ETF)
|
||||
|
||||
# Analyze your largest position (AVGO - 43 shares)
|
||||
print("Analyzing AVGO (Broadcom) - Your largest position...")
|
||||
_, decision = ta.propagate("AVGO", "2024-10-01")
|
||||
print("\n" + "="*60)
|
||||
print("AVGO Analysis Result:")
|
||||
print("="*60)
|
||||
print(decision)
|
||||
|
||||
# Uncomment below to analyze all positions:
|
||||
# for ticker in PORTFOLIO_TICKERS:
|
||||
# print(f"\nAnalyzing {ticker}...")
|
||||
# _, decision = ta.propagate(ticker, "2024-10-01")
|
||||
# print(f"{ticker} Decision: {decision[:200]}...") # First 200 chars
|
||||
|
||||
# Memorize mistakes and reflect
|
||||
# ta.reflect_and_remember(1000) # parameter is the position returns
|
||||
|
|
|
|||
|
|
@ -0,0 +1,54 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Quick demo of TradingAgents analyzing a stock"""
|
||||
|
||||
from tradingagents.graph.trading_graph import TradingAgentsGraph
|
||||
from tradingagents.default_config import DEFAULT_CONFIG
|
||||
from dotenv import load_dotenv
|
||||
import datetime
|
||||
|
||||
# Load environment variables
|
||||
load_dotenv()
|
||||
|
||||
print("=" * 60)
|
||||
print("🚀 TradingAgents Quick Demo")
|
||||
print("=" * 60)
|
||||
|
||||
# Configure for fast testing
|
||||
config = DEFAULT_CONFIG.copy()
|
||||
config["deep_think_llm"] = "gpt-4o-mini" # Use faster model
|
||||
config["quick_think_llm"] = "gpt-4o-mini" # Use faster model
|
||||
config["max_debate_rounds"] = 1 # Reduce debate rounds for speed
|
||||
|
||||
# Configure data sources
|
||||
config["data_vendors"] = {
|
||||
"core_stock_apis": "yfinance",
|
||||
"technical_indicators": "yfinance",
|
||||
"fundamental_data": "alpha_vantage",
|
||||
"news_data": "alpha_vantage",
|
||||
}
|
||||
|
||||
print("\n📊 Analyzing NVDA stock for 2024-05-10...")
|
||||
print(" Using: gpt-4o-mini (fast mode)")
|
||||
print(" Data: yfinance + Alpha Vantage")
|
||||
print("\n🔄 This will take 1-2 minutes as agents analyze the data...\n")
|
||||
|
||||
# Initialize the trading graph
|
||||
ta = TradingAgentsGraph(debug=True, config=config)
|
||||
|
||||
# Run analysis
|
||||
try:
|
||||
_, decision = ta.propagate("NVDA", "2024-05-10")
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("📈 TRADING DECISION")
|
||||
print("=" * 60)
|
||||
print(decision)
|
||||
|
||||
except Exception as e:
|
||||
print(f"\n❌ Error during analysis: {e}")
|
||||
print("\nThis might happen if:")
|
||||
print("- API rate limits are reached")
|
||||
print("- Network connectivity issues")
|
||||
print("- Invalid API keys")
|
||||
|
||||
print("\n✅ Demo complete!")
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
# Autonomous Trading System Requirements
|
||||
# Install with: pip install -r requirements_autonomous.txt
|
||||
|
||||
# === Core Trading Requirements ===
|
||||
# Already in main requirements.txt but listed for clarity
|
||||
yfinance>=0.2.63
|
||||
pandas>=2.3.0
|
||||
numpy>=2.1.0
|
||||
|
||||
# === IBKR Connection ===
|
||||
ib_insync==0.9.86
|
||||
|
||||
# === Database ===
|
||||
psycopg2-binary==2.9.9
|
||||
sqlalchemy==2.0.23
|
||||
alembic==1.13.1
|
||||
|
||||
# === Scheduling & Async ===
|
||||
APScheduler==3.10.4
|
||||
asyncio-throttle==1.0.2
|
||||
aiohttp>=3.8.5
|
||||
nest-asyncio>=1.5.8
|
||||
|
||||
# === Alternative Data Sources ===
|
||||
quiverquant==0.1.8 # Congressional trades
|
||||
polygon-api-client==1.13.2 # Real-time market data
|
||||
newsapi-python==0.2.7 # News aggregation
|
||||
alpaca-py==0.28.0 # Alternative market data
|
||||
|
||||
# === Notifications ===
|
||||
discord.py==2.3.2 # Discord alerts
|
||||
python-telegram-bot==20.7 # Telegram alerts
|
||||
|
||||
# === Web Dashboard (Optional) ===
|
||||
fastapi==0.109.0
|
||||
uvicorn[standard]==0.25.0
|
||||
jinja2==3.1.3
|
||||
websockets>=12.0
|
||||
plotly==5.18.0 # Interactive charts
|
||||
|
||||
# === Redis (for caching and task queue) ===
|
||||
redis==5.0.1
|
||||
celery==5.3.4
|
||||
|
||||
# === Additional Analysis Tools ===
|
||||
ta==0.11.0 # Technical analysis
|
||||
scipy>=1.11.4 # Statistical analysis
|
||||
scikit-learn>=1.3.2 # ML for pattern recognition
|
||||
|
||||
# === Monitoring & Logging ===
|
||||
prometheus-client==0.19.0 # Metrics
|
||||
python-json-logger==2.0.7 # Structured logging
|
||||
sentry-sdk==1.39.1 # Error tracking (optional)
|
||||
|
||||
# === Testing ===
|
||||
pytest==7.4.3
|
||||
pytest-asyncio==0.21.1
|
||||
pytest-mock==3.12.0
|
||||
|
||||
# === Development Tools ===
|
||||
python-dotenv==1.0.0 # Environment variables
|
||||
black==23.12.1 # Code formatting
|
||||
pylint==3.0.3 # Code linting
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Quick test to verify OpenAI API is working"""
|
||||
|
||||
import os
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# Load environment variables
|
||||
load_dotenv()
|
||||
|
||||
def test_openai():
|
||||
"""Test OpenAI API connectivity"""
|
||||
api_key = os.getenv("OPENAI_API_KEY")
|
||||
|
||||
if not api_key:
|
||||
print("❌ No OpenAI API key found")
|
||||
return False
|
||||
|
||||
print(f"✅ OpenAI API Key found: {api_key[:20]}...")
|
||||
|
||||
try:
|
||||
from openai import OpenAI
|
||||
client = OpenAI(api_key=api_key)
|
||||
|
||||
# Test with a simple completion
|
||||
response = client.chat.completions.create(
|
||||
model="gpt-4o-mini",
|
||||
messages=[{"role": "user", "content": "Say 'API working!'"}],
|
||||
max_tokens=10
|
||||
)
|
||||
|
||||
result = response.choices[0].message.content
|
||||
print(f"✅ OpenAI API Response: {result}")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ OpenAI API Error: {e}")
|
||||
return False
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("Testing OpenAI API...")
|
||||
test_openai()
|
||||
|
|
@ -0,0 +1,154 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Test script to verify TradingAgents setup and API connectivity"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# Load environment variables
|
||||
load_dotenv()
|
||||
|
||||
def check_api_keys():
|
||||
"""Check if required API keys are set"""
|
||||
print("🔍 Checking API Keys...")
|
||||
|
||||
alpha_vantage_key = os.getenv("ALPHA_VANTAGE_API_KEY")
|
||||
openai_key = os.getenv("OPENAI_API_KEY")
|
||||
|
||||
status = {}
|
||||
|
||||
if alpha_vantage_key and alpha_vantage_key != "alpha_vantage_api_key_placeholder":
|
||||
print("✅ Alpha Vantage API Key: SET")
|
||||
status["alpha_vantage"] = True
|
||||
else:
|
||||
print("❌ Alpha Vantage API Key: NOT SET or using placeholder")
|
||||
status["alpha_vantage"] = False
|
||||
|
||||
if openai_key and openai_key != "openai_api_key_placeholder":
|
||||
print("✅ OpenAI API Key: SET")
|
||||
status["openai"] = True
|
||||
else:
|
||||
print("❌ OpenAI API Key: NOT SET or using placeholder")
|
||||
status["openai"] = False
|
||||
|
||||
return status
|
||||
|
||||
def test_imports():
|
||||
"""Test if all required packages can be imported"""
|
||||
print("\n🔍 Testing Package Imports...")
|
||||
|
||||
packages = [
|
||||
"langchain_openai",
|
||||
"langchain_experimental",
|
||||
"pandas",
|
||||
"yfinance",
|
||||
"langgraph",
|
||||
"tradingagents"
|
||||
]
|
||||
|
||||
failed = []
|
||||
for package in packages:
|
||||
try:
|
||||
__import__(package)
|
||||
print(f"✅ {package}: OK")
|
||||
except ImportError as e:
|
||||
print(f"❌ {package}: FAILED - {e}")
|
||||
failed.append(package)
|
||||
|
||||
return len(failed) == 0
|
||||
|
||||
def test_alpha_vantage_connection():
|
||||
"""Test Alpha Vantage API connectivity"""
|
||||
print("\n🔍 Testing Alpha Vantage API...")
|
||||
|
||||
api_key = os.getenv("ALPHA_VANTAGE_API_KEY")
|
||||
if not api_key or api_key == "alpha_vantage_api_key_placeholder":
|
||||
print("⏭️ Skipping Alpha Vantage test - API key not set")
|
||||
return False
|
||||
|
||||
try:
|
||||
import requests
|
||||
# Test with a simple quote endpoint
|
||||
url = f"https://www.alphavantage.co/query?function=GLOBAL_QUOTE&symbol=AAPL&apikey={api_key}"
|
||||
response = requests.get(url, timeout=10)
|
||||
data = response.json()
|
||||
|
||||
if "Global Quote" in data:
|
||||
print("✅ Alpha Vantage API: Connected successfully")
|
||||
return True
|
||||
elif "Note" in data:
|
||||
print("⚠️ Alpha Vantage API: Rate limit reached")
|
||||
return True
|
||||
elif "Error Message" in data:
|
||||
print(f"❌ Alpha Vantage API: {data['Error Message']}")
|
||||
return False
|
||||
else:
|
||||
print("❌ Alpha Vantage API: Unexpected response")
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"❌ Alpha Vantage API: Connection failed - {e}")
|
||||
return False
|
||||
|
||||
def test_yfinance():
|
||||
"""Test yfinance data fetching"""
|
||||
print("\n🔍 Testing yfinance...")
|
||||
|
||||
try:
|
||||
import yfinance as yf
|
||||
ticker = yf.Ticker("AAPL")
|
||||
info = ticker.info
|
||||
if info and "symbol" in info:
|
||||
print("✅ yfinance: Working correctly")
|
||||
return True
|
||||
else:
|
||||
print("❌ yfinance: Failed to fetch data")
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"❌ yfinance: Error - {e}")
|
||||
return False
|
||||
|
||||
def main():
|
||||
print("=" * 60)
|
||||
print("🚀 TradingAgents Setup Test")
|
||||
print("=" * 60)
|
||||
|
||||
# Check API keys
|
||||
api_status = check_api_keys()
|
||||
|
||||
# Test imports
|
||||
imports_ok = test_imports()
|
||||
|
||||
# Test connections if keys are available
|
||||
if api_status["alpha_vantage"]:
|
||||
test_alpha_vantage_connection()
|
||||
|
||||
test_yfinance()
|
||||
|
||||
# Summary
|
||||
print("\n" + "=" * 60)
|
||||
print("📊 SUMMARY")
|
||||
print("=" * 60)
|
||||
|
||||
if not api_status["openai"]:
|
||||
print("\n⚠️ IMPORTANT: You need to set your OpenAI API key!")
|
||||
print(" Either:")
|
||||
print(" 1. Add it to the .env file")
|
||||
print(" 2. Export it: export OPENAI_API_KEY='your-key-here'")
|
||||
|
||||
if imports_ok and api_status["alpha_vantage"]:
|
||||
print("\n✅ Basic setup is complete!")
|
||||
print("\n📝 Next steps:")
|
||||
if not api_status["openai"]:
|
||||
print("1. Add your OpenAI API key")
|
||||
print("2. Run: python main.py")
|
||||
print(" OR")
|
||||
print(" python -m cli.main")
|
||||
else:
|
||||
print("1. Run: python main.py")
|
||||
print(" OR")
|
||||
print(" python -m cli.main")
|
||||
else:
|
||||
print("\n❌ Setup incomplete. Please fix the issues above.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Loading…
Reference in New Issue