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
|
# Initialize with custom config
|
||||||
ta = TradingAgentsGraph(debug=True, config=config)
|
ta = TradingAgentsGraph(debug=True, config=config)
|
||||||
|
|
||||||
# forward propagate
|
# Your IBKR portfolio tickers
|
||||||
_, decision = ta.propagate("NVDA", "2024-05-10")
|
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)
|
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
|
# Memorize mistakes and reflect
|
||||||
# ta.reflect_and_remember(1000) # parameter is the position returns
|
# 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