From 22ff8d8a4fd8f3fba8888193cdd916152e545b2b Mon Sep 17 00:00:00 2001 From: Zygmunt Dyras Date: Wed, 8 Oct 2025 00:49:35 +0200 Subject: [PATCH] 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. --- AUTONOMOUS_README.md | 282 ++++++++++++++++++ AUTONOMOUS_TRADING_SYSTEM.md | 272 +++++++++++++++++ analyze_portfolio.py | 105 +++++++ autonomous/__init__.py | 21 ++ autonomous/alert_engine.py | 468 +++++++++++++++++++++++++++++ autonomous/config/settings.py | 180 ++++++++++++ autonomous/data_aggregator.py | 495 +++++++++++++++++++++++++++++++ autonomous/ibkr_connector.py | 336 +++++++++++++++++++++ autonomous/scheduler.py | 522 +++++++++++++++++++++++++++++++++ autonomous/signal_processor.py | 414 ++++++++++++++++++++++++++ autonomous_trader.py | 102 +++++++ main.py | 17 +- quick_demo.py | 54 ++++ requirements_autonomous.txt | 63 ++++ test_api.py | 41 +++ test_setup.py | 154 ++++++++++ 16 files changed, 3524 insertions(+), 2 deletions(-) create mode 100644 AUTONOMOUS_README.md create mode 100644 AUTONOMOUS_TRADING_SYSTEM.md create mode 100644 analyze_portfolio.py create mode 100644 autonomous/__init__.py create mode 100644 autonomous/alert_engine.py create mode 100644 autonomous/config/settings.py create mode 100644 autonomous/data_aggregator.py create mode 100644 autonomous/ibkr_connector.py create mode 100644 autonomous/scheduler.py create mode 100644 autonomous/signal_processor.py create mode 100644 autonomous_trader.py create mode 100644 quick_demo.py create mode 100644 requirements_autonomous.txt create mode 100644 test_api.py create mode 100644 test_setup.py diff --git a/AUTONOMOUS_README.md b/AUTONOMOUS_README.md new file mode 100644 index 00000000..49ee21d7 --- /dev/null +++ b/AUTONOMOUS_README.md @@ -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! 🎯 \ No newline at end of file diff --git a/AUTONOMOUS_TRADING_SYSTEM.md b/AUTONOMOUS_TRADING_SYSTEM.md new file mode 100644 index 00000000..00a5cde7 --- /dev/null +++ b/AUTONOMOUS_TRADING_SYSTEM.md @@ -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: \ No newline at end of file diff --git a/analyze_portfolio.py b/analyze_portfolio.py new file mode 100644 index 00000000..0e4da728 --- /dev/null +++ b/analyze_portfolio.py @@ -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) \ No newline at end of file diff --git a/autonomous/__init__.py b/autonomous/__init__.py new file mode 100644 index 00000000..e30145ac --- /dev/null +++ b/autonomous/__init__.py @@ -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", +] \ No newline at end of file diff --git a/autonomous/alert_engine.py b/autonomous/alert_engine.py new file mode 100644 index 00000000..706ebba2 --- /dev/null +++ b/autonomous/alert_engine.py @@ -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""" + + +

{title}

+

{message.replace(chr(10), '
')}

+
+

Alert Type: {alert_type.value} | Priority: {priority.value}

+ + + """ + + # 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()) \ No newline at end of file diff --git a/autonomous/config/settings.py b/autonomous/config/settings.py new file mode 100644 index 00000000..c92598fe --- /dev/null +++ b/autonomous/config/settings.py @@ -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") \ No newline at end of file diff --git a/autonomous/data_aggregator.py b/autonomous/data_aggregator.py new file mode 100644 index 00000000..0500eeb6 --- /dev/null +++ b/autonomous/data_aggregator.py @@ -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()) \ No newline at end of file diff --git a/autonomous/ibkr_connector.py b/autonomous/ibkr_connector.py new file mode 100644 index 00000000..e22aad15 --- /dev/null +++ b/autonomous/ibkr_connector.py @@ -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()) \ No newline at end of file diff --git a/autonomous/scheduler.py b/autonomous/scheduler.py new file mode 100644 index 00000000..671f6eb1 --- /dev/null +++ b/autonomous/scheduler.py @@ -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()) \ No newline at end of file diff --git a/autonomous/signal_processor.py b/autonomous/signal_processor.py new file mode 100644 index 00000000..832c856a --- /dev/null +++ b/autonomous/signal_processor.py @@ -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()) \ No newline at end of file diff --git a/autonomous_trader.py b/autonomous_trader.py new file mode 100644 index 00000000..3fcb3b49 --- /dev/null +++ b/autonomous_trader.py @@ -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) \ No newline at end of file diff --git a/main.py b/main.py index a85ee6ec..2cf1b8d2 100644 --- a/main.py +++ b/main.py @@ -23,9 +23,22 @@ config["data_vendors"] = { # Initialize with custom config ta = TradingAgentsGraph(debug=True, config=config) -# forward propagate -_, decision = ta.propagate("NVDA", "2024-05-10") +# Your IBKR portfolio tickers +PORTFOLIO_TICKERS = ["AVGO", "MSFT", "MU", "NVDA", "TSM"] # Excluding SXRV (ETF) + +# Analyze your largest position (AVGO - 43 shares) +print("Analyzing AVGO (Broadcom) - Your largest position...") +_, decision = ta.propagate("AVGO", "2024-10-01") +print("\n" + "="*60) +print("AVGO Analysis Result:") +print("="*60) print(decision) +# Uncomment below to analyze all positions: +# for ticker in PORTFOLIO_TICKERS: +# print(f"\nAnalyzing {ticker}...") +# _, decision = ta.propagate(ticker, "2024-10-01") +# print(f"{ticker} Decision: {decision[:200]}...") # First 200 chars + # Memorize mistakes and reflect # ta.reflect_and_remember(1000) # parameter is the position returns diff --git a/quick_demo.py b/quick_demo.py new file mode 100644 index 00000000..4d003d62 --- /dev/null +++ b/quick_demo.py @@ -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!") \ No newline at end of file diff --git a/requirements_autonomous.txt b/requirements_autonomous.txt new file mode 100644 index 00000000..06f7e5d8 --- /dev/null +++ b/requirements_autonomous.txt @@ -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 \ No newline at end of file diff --git a/test_api.py b/test_api.py new file mode 100644 index 00000000..38a5beb8 --- /dev/null +++ b/test_api.py @@ -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() \ No newline at end of file diff --git a/test_setup.py b/test_setup.py new file mode 100644 index 00000000..a7554bc4 --- /dev/null +++ b/test_setup.py @@ -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() \ No newline at end of file