463 lines
12 KiB
Python
Executable File
463 lines
12 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
TradingAgents Web Interface
|
|
|
|
A beautiful web UI for running TradingAgents analysis and managing trades.
|
|
|
|
Usage:
|
|
chainlit run web_app.py -w
|
|
|
|
Then open http://localhost:8000 in your browser!
|
|
"""
|
|
|
|
import chainlit as cl
|
|
from decimal import Decimal
|
|
from datetime import datetime
|
|
import json
|
|
from typing import Optional
|
|
|
|
from tradingagents.graph.trading_graph import TradingAgentsGraph
|
|
from tradingagents.default_config import DEFAULT_CONFIG
|
|
from tradingagents.brokers import AlpacaBroker
|
|
from tradingagents.brokers.base import OrderSide, OrderType
|
|
|
|
|
|
# Global state
|
|
ta_graph: Optional[TradingAgentsGraph] = None
|
|
broker: Optional[AlpacaBroker] = None
|
|
|
|
|
|
@cl.on_chat_start
|
|
async def start():
|
|
"""Initialize the chat session."""
|
|
await cl.Message(
|
|
content="""# 🤖 Welcome to TradingAgents!
|
|
|
|
I'm your AI-powered trading assistant. I can help you:
|
|
|
|
📊 **Analyze Stocks** - Deep analysis using multiple expert agents
|
|
💼 **Manage Positions** - Track your portfolio and P&L
|
|
📈 **Execute Trades** - Paper trading integration with Alpaca
|
|
📉 **View Reports** - Detailed analysis and recommendations
|
|
|
|
**Quick Commands:**
|
|
- `analyze AAPL` - Analyze a stock
|
|
- `portfolio` - View current positions
|
|
- `account` - Check account status
|
|
- `help` - Show all commands
|
|
|
|
**Getting Started:**
|
|
1. Make sure your `.env` is configured
|
|
2. Try analyzing a stock: `analyze NVDA`
|
|
3. Review the detailed analysis
|
|
4. Execute trades based on signals!
|
|
|
|
What would you like to do?
|
|
"""
|
|
).send()
|
|
|
|
# Store settings in session
|
|
cl.user_session.set("config", DEFAULT_CONFIG.copy())
|
|
cl.user_session.set("broker_connected", False)
|
|
|
|
|
|
@cl.on_message
|
|
async def main(message: cl.Message):
|
|
"""Handle incoming messages."""
|
|
global ta_graph, broker
|
|
|
|
msg_content = message.content.strip().lower()
|
|
parts = msg_content.split()
|
|
|
|
if not parts:
|
|
await cl.Message(content="Please enter a command. Type `help` for options.").send()
|
|
return
|
|
|
|
command = parts[0]
|
|
|
|
# Help command
|
|
if command == "help":
|
|
await show_help()
|
|
|
|
# Analyze command
|
|
elif command == "analyze":
|
|
if len(parts) < 2:
|
|
await cl.Message(content="Usage: `analyze TICKER`\n\nExample: `analyze AAPL`").send()
|
|
return
|
|
|
|
ticker = parts[1].upper()
|
|
await analyze_stock(ticker)
|
|
|
|
# Portfolio command
|
|
elif command == "portfolio":
|
|
await show_portfolio()
|
|
|
|
# Account command
|
|
elif command == "account":
|
|
await show_account()
|
|
|
|
# Connect broker command
|
|
elif command == "connect":
|
|
await connect_broker()
|
|
|
|
# Buy command
|
|
elif command == "buy":
|
|
if len(parts) < 3:
|
|
await cl.Message(content="Usage: `buy TICKER QUANTITY`\n\nExample: `buy AAPL 10`").send()
|
|
return
|
|
|
|
ticker = parts[1].upper()
|
|
try:
|
|
quantity = Decimal(parts[2])
|
|
await execute_buy(ticker, quantity)
|
|
except ValueError:
|
|
await cl.Message(content="Invalid quantity. Please use a number.").send()
|
|
|
|
# Sell command
|
|
elif command == "sell":
|
|
if len(parts) < 3:
|
|
await cl.Message(content="Usage: `sell TICKER QUANTITY`\n\nExample: `sell AAPL 10`").send()
|
|
return
|
|
|
|
ticker = parts[1].upper()
|
|
try:
|
|
quantity = Decimal(parts[2])
|
|
await execute_sell(ticker, quantity)
|
|
except ValueError:
|
|
await cl.Message(content="Invalid quantity. Please use a number.").send()
|
|
|
|
# Settings command
|
|
elif command == "settings":
|
|
await show_settings()
|
|
|
|
# Set LLM provider
|
|
elif command == "provider":
|
|
if len(parts) < 2:
|
|
await cl.Message(content="Usage: `provider PROVIDER`\n\nOptions: openai, anthropic, google").send()
|
|
return
|
|
|
|
provider = parts[1].lower()
|
|
await set_provider(provider)
|
|
|
|
else:
|
|
await cl.Message(
|
|
content=f"Unknown command: `{command}`\n\nType `help` to see available commands."
|
|
).send()
|
|
|
|
|
|
async def show_help():
|
|
"""Show help message."""
|
|
await cl.Message(
|
|
content="""# 📚 TradingAgents Commands
|
|
|
|
## Analysis
|
|
- `analyze TICKER` - Analyze a stock with all agents
|
|
- `settings` - View current settings
|
|
- `provider NAME` - Change LLM provider (openai/anthropic/google)
|
|
|
|
## Trading
|
|
- `connect` - Connect to paper trading broker
|
|
- `account` - View account balance and buying power
|
|
- `portfolio` - View all positions and P&L
|
|
- `buy TICKER QTY` - Buy shares (e.g., `buy AAPL 10`)
|
|
- `sell TICKER QTY` - Sell shares (e.g., `sell AAPL 10`)
|
|
|
|
## Examples
|
|
```
|
|
analyze NVDA
|
|
buy NVDA 5
|
|
portfolio
|
|
sell NVDA 5
|
|
```
|
|
|
|
**Tips:**
|
|
- Start with `analyze` to get AI insights
|
|
- Use `connect` to enable paper trading
|
|
- Check `portfolio` regularly to track P&L
|
|
- All trades are paper trading (no real money!)
|
|
"""
|
|
).send()
|
|
|
|
|
|
async def analyze_stock(ticker: str):
|
|
"""Analyze a stock using TradingAgents."""
|
|
global ta_graph
|
|
|
|
# Show loading message
|
|
msg = cl.Message(content=f"🔍 Analyzing **{ticker}** with TradingAgents...\n\nThis may take 1-2 minutes...")
|
|
await msg.send()
|
|
|
|
try:
|
|
# Initialize TradingAgents if needed
|
|
if ta_graph is None:
|
|
config = cl.user_session.get("config")
|
|
ta_graph = TradingAgentsGraph(
|
|
selected_analysts=["market", "fundamentals", "news"],
|
|
config=config
|
|
)
|
|
|
|
# Run analysis
|
|
trade_date = datetime.now().strftime("%Y-%m-%d")
|
|
final_state, signal = ta_graph.propagate(ticker, trade_date)
|
|
|
|
# Format results
|
|
result = f"""# 📊 Analysis Complete: {ticker}
|
|
|
|
## 🎯 Trading Signal: **{signal}**
|
|
|
|
### Market Analysis
|
|
{final_state.get('market_report', 'No market data available')[:500]}...
|
|
|
|
### Fundamentals Analysis
|
|
{final_state.get('fundamentals_report', 'No fundamentals data available')[:500]}...
|
|
|
|
### News Sentiment
|
|
{final_state.get('news_report', 'No news data available')[:500]}...
|
|
|
|
### Investment Decision
|
|
{final_state.get('trader_investment_plan', 'No decision available')[:500]}...
|
|
|
|
---
|
|
|
|
**Recommendation:** {signal}
|
|
|
|
Would you like to execute this signal? Use:
|
|
- `buy {ticker} <quantity>` if signal is BUY
|
|
- `sell {ticker} <quantity>` if signal is SELL
|
|
"""
|
|
|
|
await cl.Message(content=result).send()
|
|
|
|
# Store analysis in session
|
|
cl.user_session.set("last_analysis", {
|
|
"ticker": ticker,
|
|
"signal": signal,
|
|
"state": final_state
|
|
})
|
|
|
|
except Exception as e:
|
|
await cl.Message(
|
|
content=f"❌ Analysis failed: {str(e)}\n\nThis might be due to:\n- API quota limits\n- Network issues\n- Invalid ticker\n\nPlease try again or check your configuration."
|
|
).send()
|
|
|
|
|
|
async def connect_broker():
|
|
"""Connect to paper trading broker."""
|
|
global broker
|
|
|
|
if cl.user_session.get("broker_connected"):
|
|
await cl.Message(content="✓ Already connected to Alpaca paper trading!").send()
|
|
return
|
|
|
|
msg = cl.Message(content="🔌 Connecting to Alpaca paper trading...")
|
|
await msg.send()
|
|
|
|
try:
|
|
broker = AlpacaBroker(paper_trading=True)
|
|
broker.connect()
|
|
|
|
account = broker.get_account()
|
|
|
|
cl.user_session.set("broker_connected", True)
|
|
|
|
await cl.Message(
|
|
content=f"""✓ Connected to Alpaca Paper Trading!
|
|
|
|
**Account:** {account.account_number}
|
|
**Cash:** ${account.cash:,.2f}
|
|
**Buying Power:** ${account.buying_power:,.2f}
|
|
**Portfolio Value:** ${account.portfolio_value:,.2f}
|
|
|
|
You can now execute trades!
|
|
"""
|
|
).send()
|
|
|
|
except Exception as e:
|
|
await cl.Message(
|
|
content=f"""❌ Connection failed: {str(e)}
|
|
|
|
**Setup Required:**
|
|
1. Sign up at https://alpaca.markets
|
|
2. Get your API keys
|
|
3. Add to `.env`:
|
|
```
|
|
ALPACA_API_KEY=your_key
|
|
ALPACA_SECRET_KEY=your_secret
|
|
ALPACA_PAPER_TRADING=true
|
|
```
|
|
4. Restart the app
|
|
"""
|
|
).send()
|
|
|
|
|
|
async def show_account():
|
|
"""Show account information."""
|
|
global broker
|
|
|
|
if not broker or not cl.user_session.get("broker_connected"):
|
|
await cl.Message(content="⚠️ Not connected. Use `connect` first!").send()
|
|
return
|
|
|
|
try:
|
|
account = broker.get_account()
|
|
|
|
await cl.Message(
|
|
content=f"""# 💰 Account Status
|
|
|
|
**Account Number:** {account.account_number}
|
|
**Cash Available:** ${account.cash:,.2f}
|
|
**Buying Power:** ${account.buying_power:,.2f}
|
|
**Portfolio Value:** ${account.portfolio_value:,.2f}
|
|
**Total Equity:** ${account.equity:,.2f}
|
|
|
|
**Session P&L:** ${account.equity - account.last_equity:,.2f}
|
|
|
|
Type `portfolio` to see your positions.
|
|
"""
|
|
).send()
|
|
|
|
except Exception as e:
|
|
await cl.Message(content=f"❌ Error: {str(e)}").send()
|
|
|
|
|
|
async def show_portfolio():
|
|
"""Show current positions."""
|
|
global broker
|
|
|
|
if not broker or not cl.user_session.get("broker_connected"):
|
|
await cl.Message(content="⚠️ Not connected. Use `connect` first!").send()
|
|
return
|
|
|
|
try:
|
|
positions = broker.get_positions()
|
|
|
|
if not positions:
|
|
await cl.Message(content="📭 No positions currently held.").send()
|
|
return
|
|
|
|
result = "# 📈 Current Positions\n\n"
|
|
total_value = Decimal("0")
|
|
total_pnl = Decimal("0")
|
|
|
|
for pos in positions:
|
|
result += f"""## {pos.symbol}
|
|
- **Quantity:** {pos.quantity} shares
|
|
- **Avg Cost:** ${pos.avg_entry_price:.2f}
|
|
- **Current Price:** ${pos.current_price:.2f}
|
|
- **Market Value:** ${pos.market_value:,.2f}
|
|
- **P&L:** ${pos.unrealized_pnl:,.2f} ({pos.unrealized_pnl_percent:.2%})
|
|
|
|
"""
|
|
total_value += pos.market_value
|
|
total_pnl += pos.unrealized_pnl
|
|
|
|
result += f"""---
|
|
**Total Position Value:** ${total_value:,.2f}
|
|
**Total Unrealized P&L:** ${total_pnl:,.2f}
|
|
"""
|
|
|
|
await cl.Message(content=result).send()
|
|
|
|
except Exception as e:
|
|
await cl.Message(content=f"❌ Error: {str(e)}").send()
|
|
|
|
|
|
async def execute_buy(ticker: str, quantity: Decimal):
|
|
"""Execute a buy order."""
|
|
global broker
|
|
|
|
if not broker or not cl.user_session.get("broker_connected"):
|
|
await cl.Message(content="⚠️ Not connected. Use `connect` first!").send()
|
|
return
|
|
|
|
msg = cl.Message(content=f"🔄 Placing buy order for {quantity} shares of {ticker}...")
|
|
await msg.send()
|
|
|
|
try:
|
|
order = broker.buy_market(ticker, quantity)
|
|
|
|
await cl.Message(
|
|
content=f"""✓ Buy order placed successfully!
|
|
|
|
**Order ID:** {order.order_id}
|
|
**Symbol:** {order.symbol}
|
|
**Quantity:** {order.quantity}
|
|
**Status:** {order.status.value}
|
|
|
|
Check your `portfolio` to see the position.
|
|
"""
|
|
).send()
|
|
|
|
except Exception as e:
|
|
await cl.Message(content=f"❌ Order failed: {str(e)}").send()
|
|
|
|
|
|
async def execute_sell(ticker: str, quantity: Decimal):
|
|
"""Execute a sell order."""
|
|
global broker
|
|
|
|
if not broker or not cl.user_session.get("broker_connected"):
|
|
await cl.Message(content="⚠️ Not connected. Use `connect` first!").send()
|
|
return
|
|
|
|
msg = cl.Message(content=f"🔄 Placing sell order for {quantity} shares of {ticker}...")
|
|
await msg.send()
|
|
|
|
try:
|
|
order = broker.sell_market(ticker, quantity)
|
|
|
|
await cl.Message(
|
|
content=f"""✓ Sell order placed successfully!
|
|
|
|
**Order ID:** {order.order_id}
|
|
**Symbol:** {order.symbol}
|
|
**Quantity:** {order.quantity}
|
|
**Status:** {order.status.value}
|
|
|
|
Check your `portfolio` to see updated positions.
|
|
"""
|
|
).send()
|
|
|
|
except Exception as e:
|
|
await cl.Message(content=f"❌ Order failed: {str(e)}").send()
|
|
|
|
|
|
async def show_settings():
|
|
"""Show current settings."""
|
|
config = cl.user_session.get("config")
|
|
|
|
await cl.Message(
|
|
content=f"""# ⚙️ Current Settings
|
|
|
|
**LLM Provider:** {config.get('llm_provider', 'openai')}
|
|
**Deep Think Model:** {config.get('deep_think_llm', 'gpt-4o')}
|
|
**Quick Think Model:** {config.get('quick_think_llm', 'gpt-4o-mini')}
|
|
**Broker Connected:** {cl.user_session.get('broker_connected', False)}
|
|
|
|
To change LLM provider, use: `provider NAME`
|
|
|
|
Available providers: openai, anthropic, google
|
|
"""
|
|
).send()
|
|
|
|
|
|
async def set_provider(provider: str):
|
|
"""Set LLM provider."""
|
|
global ta_graph
|
|
|
|
if provider not in ["openai", "anthropic", "google"]:
|
|
await cl.Message(content="❌ Invalid provider. Choose: openai, anthropic, or google").send()
|
|
return
|
|
|
|
config = cl.user_session.get("config")
|
|
config["llm_provider"] = provider
|
|
|
|
# Reset TradingAgents to use new provider
|
|
ta_graph = None
|
|
|
|
await cl.Message(content=f"✓ LLM provider set to **{provider}**\n\nNext analysis will use this provider.").send()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
print("Run with: chainlit run web_app.py -w")
|