Edwin Salguero
commited on
Commit
Β·
9f44dc9
1
Parent(s):
f820bf2
feat(ui): add robust multi-interface UI system (Streamlit, Dash, Jupyter, WebSocket) with launcher, docs, and integration tests [skip ci]
Browse files- README.md +82 -3
- UI_SETUP.md +390 -0
- requirements.txt +54 -19
- tests/test_ui_integration.py +147 -0
- ui/__init__.py +24 -0
- ui/dash_app.py +657 -0
- ui/jupyter_widgets.py +556 -0
- ui/streamlit_app.py +679 -0
- ui/websocket_server.py +461 -0
- ui_launcher.py +269 -0
README.md
CHANGED
|
@@ -23,6 +23,13 @@ A sophisticated algorithmic trading system that combines reinforcement learning
|
|
| 23 |
- **Account Management**: Portfolio monitoring and position tracking
|
| 24 |
- **Order Types**: Market orders, limit orders, and order cancellation
|
| 25 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
### Advanced Features
|
| 27 |
- **Docker Support**: Containerized deployment for consistency
|
| 28 |
- **Comprehensive Logging**: Detailed logs for debugging and performance analysis
|
|
@@ -100,7 +107,22 @@ finrl:
|
|
| 100 |
|
| 101 |
## π Quick Start
|
| 102 |
|
| 103 |
-
### 1.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 104 |
```bash
|
| 105 |
python demo.py
|
| 106 |
```
|
|
@@ -111,12 +133,12 @@ This will:
|
|
| 111 |
- Show trading workflow execution
|
| 112 |
- Run backtesting on historical data
|
| 113 |
|
| 114 |
-
###
|
| 115 |
```bash
|
| 116 |
python -m agentic_ai_system.main --mode live --duration 60
|
| 117 |
```
|
| 118 |
|
| 119 |
-
###
|
| 120 |
```bash
|
| 121 |
python -m agentic_ai_system.main --mode backtest --start-date 2024-01-01 --end-date 2024-01-31
|
| 122 |
```
|
|
@@ -262,6 +284,13 @@ algorithmic_trading/
|
|
| 262 |
β βββ π synthetic_data_generator.py # Test data generation
|
| 263 |
β βββ π logger_config.py # Logging configuration
|
| 264 |
β
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 265 |
βββ π§ͺ tests/ # Test suite
|
| 266 |
β βββ π __init__.py
|
| 267 |
β βββ π test_data_ingestion.py
|
|
@@ -293,6 +322,8 @@ algorithmic_trading/
|
|
| 293 |
β
|
| 294 |
βββ π demo.py # Main demo script
|
| 295 |
βββ π finrl_demo.py # FinRL-specific demo
|
|
|
|
|
|
|
| 296 |
βββ π DOCKER_HUB_SETUP.md # Docker Hub documentation
|
| 297 |
β
|
| 298 |
βββ π .venv/ # Python virtual environment
|
|
@@ -362,6 +393,54 @@ risk:
|
|
| 362 |
take_profit: 0.05
|
| 363 |
```
|
| 364 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 365 |
## π Performance Monitoring
|
| 366 |
|
| 367 |
### Logging
|
|
|
|
| 23 |
- **Account Management**: Portfolio monitoring and position tracking
|
| 24 |
- **Order Types**: Market orders, limit orders, and order cancellation
|
| 25 |
|
| 26 |
+
### π¨ Comprehensive UI System
|
| 27 |
+
- **Streamlit UI**: Quick prototyping and data science workflows
|
| 28 |
+
- **Dash UI**: Enterprise-grade interactive dashboards
|
| 29 |
+
- **Jupyter UI**: Interactive notebook-based interfaces
|
| 30 |
+
- **WebSocket API**: Real-time trading data streaming
|
| 31 |
+
- **Multi-interface Support**: Choose the right UI for your needs
|
| 32 |
+
|
| 33 |
### Advanced Features
|
| 34 |
- **Docker Support**: Containerized deployment for consistency
|
| 35 |
- **Comprehensive Logging**: Detailed logs for debugging and performance analysis
|
|
|
|
| 107 |
|
| 108 |
## π Quick Start
|
| 109 |
|
| 110 |
+
### 1. Launch the UI (Recommended)
|
| 111 |
+
```bash
|
| 112 |
+
# Launch Streamlit UI (best for beginners)
|
| 113 |
+
python ui_launcher.py streamlit
|
| 114 |
+
|
| 115 |
+
# Launch Dash UI (best for production)
|
| 116 |
+
python ui_launcher.py dash
|
| 117 |
+
|
| 118 |
+
# Launch Jupyter Lab
|
| 119 |
+
python ui_launcher.py jupyter
|
| 120 |
+
|
| 121 |
+
# Launch all UIs
|
| 122 |
+
python ui_launcher.py all
|
| 123 |
+
```
|
| 124 |
+
|
| 125 |
+
### 2. Run the Demo
|
| 126 |
```bash
|
| 127 |
python demo.py
|
| 128 |
```
|
|
|
|
| 133 |
- Show trading workflow execution
|
| 134 |
- Run backtesting on historical data
|
| 135 |
|
| 136 |
+
### 3. Start Paper Trading
|
| 137 |
```bash
|
| 138 |
python -m agentic_ai_system.main --mode live --duration 60
|
| 139 |
```
|
| 140 |
|
| 141 |
+
### 4. Run Backtesting
|
| 142 |
```bash
|
| 143 |
python -m agentic_ai_system.main --mode backtest --start-date 2024-01-01 --end-date 2024-01-31
|
| 144 |
```
|
|
|
|
| 284 |
β βββ π synthetic_data_generator.py # Test data generation
|
| 285 |
β βββ π logger_config.py # Logging configuration
|
| 286 |
β
|
| 287 |
+
βββ π¨ ui/ # User interface system
|
| 288 |
+
β βββ π __init__.py # UI package initialization
|
| 289 |
+
β βββ π streamlit_app.py # Streamlit web application
|
| 290 |
+
β βββ π dash_app.py # Dash enterprise dashboard
|
| 291 |
+
β βββ π jupyter_widgets.py # Jupyter interactive widgets
|
| 292 |
+
β βββ π websocket_server.py # Real-time WebSocket server
|
| 293 |
+
β
|
| 294 |
βββ π§ͺ tests/ # Test suite
|
| 295 |
β βββ π __init__.py
|
| 296 |
β βββ π test_data_ingestion.py
|
|
|
|
| 322 |
β
|
| 323 |
βββ π demo.py # Main demo script
|
| 324 |
βββ π finrl_demo.py # FinRL-specific demo
|
| 325 |
+
βββ π ui_launcher.py # UI launcher script
|
| 326 |
+
βββ π UI_SETUP.md # UI setup documentation
|
| 327 |
βββ π DOCKER_HUB_SETUP.md # Docker Hub documentation
|
| 328 |
β
|
| 329 |
βββ π .venv/ # Python virtual environment
|
|
|
|
| 393 |
take_profit: 0.05
|
| 394 |
```
|
| 395 |
|
| 396 |
+
## π¨ User Interface System
|
| 397 |
+
|
| 398 |
+
The project includes a comprehensive UI system with multiple interface options:
|
| 399 |
+
|
| 400 |
+
### Available UIs
|
| 401 |
+
|
| 402 |
+
#### **Streamlit UI** (Recommended for beginners)
|
| 403 |
+
- **URL**: http://localhost:8501
|
| 404 |
+
- **Features**: Interactive widgets, real-time data visualization, easy configuration
|
| 405 |
+
- **Best for**: Data scientists, quick experiments, rapid prototyping
|
| 406 |
+
|
| 407 |
+
#### **Dash UI** (Recommended for production)
|
| 408 |
+
- **URL**: http://localhost:8050
|
| 409 |
+
- **Features**: Enterprise-grade dashboards, advanced charts, professional styling
|
| 410 |
+
- **Best for**: Production dashboards, real-time monitoring, complex analytics
|
| 411 |
+
|
| 412 |
+
#### **Jupyter UI** (For research)
|
| 413 |
+
- **URL**: http://localhost:8888
|
| 414 |
+
- **Features**: Interactive notebooks, code execution, rich documentation
|
| 415 |
+
- **Best for**: Research, experimentation, educational purposes
|
| 416 |
+
|
| 417 |
+
#### **WebSocket API** (For developers)
|
| 418 |
+
- **URL**: ws://localhost:8765
|
| 419 |
+
- **Features**: Real-time data streaming, trading signals, portfolio updates
|
| 420 |
+
- **Best for**: Real-time trading signals, live data streaming
|
| 421 |
+
|
| 422 |
+
### Quick UI Launch
|
| 423 |
+
```bash
|
| 424 |
+
# Launch individual UIs
|
| 425 |
+
python ui_launcher.py streamlit # Streamlit UI
|
| 426 |
+
python ui_launcher.py dash # Dash UI
|
| 427 |
+
python ui_launcher.py jupyter # Jupyter Lab
|
| 428 |
+
python ui_launcher.py websocket # WebSocket server
|
| 429 |
+
|
| 430 |
+
# Launch all UIs at once
|
| 431 |
+
python ui_launcher.py all
|
| 432 |
+
```
|
| 433 |
+
|
| 434 |
+
### UI Features
|
| 435 |
+
- **Real-time Data Visualization**: Live market data charts and indicators
|
| 436 |
+
- **Portfolio Monitoring**: Real-time portfolio value and P&L tracking
|
| 437 |
+
- **Trading Controls**: Start/stop trading, backtesting, risk management
|
| 438 |
+
- **FinRL Training**: Interactive model training and evaluation
|
| 439 |
+
- **Alpaca Integration**: Account management and order execution
|
| 440 |
+
- **Configuration Management**: Easy parameter tuning and strategy setup
|
| 441 |
+
|
| 442 |
+
For detailed UI documentation, see [UI_SETUP.md](UI_SETUP.md).
|
| 443 |
+
|
| 444 |
## π Performance Monitoring
|
| 445 |
|
| 446 |
### Logging
|
UI_SETUP.md
ADDED
|
@@ -0,0 +1,390 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# UI Integration Guide
|
| 2 |
+
|
| 3 |
+
This guide covers the comprehensive UI system for the Algorithmic Trading project, providing multiple interface options for different use cases.
|
| 4 |
+
|
| 5 |
+
## π― UI Options Overview
|
| 6 |
+
|
| 7 |
+
### 1. **Streamlit UI** - Quick Prototyping
|
| 8 |
+
- **Best for**: Data scientists, quick experiments, rapid prototyping
|
| 9 |
+
- **Features**: Interactive widgets, real-time data visualization, easy configuration
|
| 10 |
+
- **Port**: 8501
|
| 11 |
+
- **URL**: http://localhost:8501
|
| 12 |
+
|
| 13 |
+
### 2. **Dash UI** - Enterprise Dashboards
|
| 14 |
+
- **Best for**: Production dashboards, real-time monitoring, complex analytics
|
| 15 |
+
- **Features**: Advanced charts, real-time updates, professional styling
|
| 16 |
+
- **Port**: 8050
|
| 17 |
+
- **URL**: http://localhost:8050
|
| 18 |
+
|
| 19 |
+
### 3. **Jupyter UI** - Interactive Notebooks
|
| 20 |
+
- **Best for**: Research, experimentation, educational purposes
|
| 21 |
+
- **Features**: Interactive widgets, code execution, rich documentation
|
| 22 |
+
- **Port**: 8888
|
| 23 |
+
- **URL**: http://localhost:8888
|
| 24 |
+
|
| 25 |
+
### 4. **WebSocket Server** - Real-time Data
|
| 26 |
+
- **Best for**: Real-time trading signals, live data streaming
|
| 27 |
+
- **Features**: WebSocket API, real-time updates, trading signals
|
| 28 |
+
- **Port**: 8765
|
| 29 |
+
- **URL**: ws://localhost:8765
|
| 30 |
+
|
| 31 |
+
## π Quick Start
|
| 32 |
+
|
| 33 |
+
### Prerequisites
|
| 34 |
+
```bash
|
| 35 |
+
# Install UI dependencies
|
| 36 |
+
pip install -r requirements.txt
|
| 37 |
+
|
| 38 |
+
# Verify installation
|
| 39 |
+
python -c "import streamlit, dash, plotly, ipywidgets; print('β
All UI dependencies installed')"
|
| 40 |
+
```
|
| 41 |
+
|
| 42 |
+
### Launch Individual UIs
|
| 43 |
+
|
| 44 |
+
#### Streamlit (Recommended for beginners)
|
| 45 |
+
```bash
|
| 46 |
+
python ui_launcher.py streamlit
|
| 47 |
+
```
|
| 48 |
+
|
| 49 |
+
#### Dash (Recommended for production)
|
| 50 |
+
```bash
|
| 51 |
+
python ui_launcher.py dash
|
| 52 |
+
```
|
| 53 |
+
|
| 54 |
+
#### Jupyter Lab
|
| 55 |
+
```bash
|
| 56 |
+
python ui_launcher.py jupyter
|
| 57 |
+
```
|
| 58 |
+
|
| 59 |
+
#### WebSocket Server
|
| 60 |
+
```bash
|
| 61 |
+
python ui_launcher.py websocket
|
| 62 |
+
```
|
| 63 |
+
|
| 64 |
+
#### Launch All UIs
|
| 65 |
+
```bash
|
| 66 |
+
python ui_launcher.py all
|
| 67 |
+
```
|
| 68 |
+
|
| 69 |
+
## π Streamlit UI Features
|
| 70 |
+
|
| 71 |
+
### Dashboard
|
| 72 |
+
- **System Status**: Real-time trading status, portfolio value, P&L
|
| 73 |
+
- **Configuration Management**: Load and modify trading parameters
|
| 74 |
+
- **Quick Actions**: One-click data loading, Alpaca connection, model training
|
| 75 |
+
|
| 76 |
+
### Data Ingestion
|
| 77 |
+
- **Multiple Sources**: CSV, Alpaca API, Synthetic data
|
| 78 |
+
- **Data Validation**: Automatic data quality checks
|
| 79 |
+
- **Technical Indicators**: Automatic calculation of moving averages, RSI, MACD
|
| 80 |
+
- **Interactive Charts**: Candlestick, line, volume charts with Plotly
|
| 81 |
+
|
| 82 |
+
### Alpaca Integration
|
| 83 |
+
- **Account Connection**: Secure API key management
|
| 84 |
+
- **Market Status**: Real-time market hours and status
|
| 85 |
+
- **Position Monitoring**: Current positions and portfolio value
|
| 86 |
+
- **Order Management**: Buy/sell order execution
|
| 87 |
+
|
| 88 |
+
### FinRL Training
|
| 89 |
+
- **Algorithm Selection**: PPO, A2C, DDPG, TD3
|
| 90 |
+
- **Hyperparameter Tuning**: Learning rate, batch size, training steps
|
| 91 |
+
- **Training Progress**: Real-time training metrics and progress
|
| 92 |
+
- **Model Evaluation**: Performance metrics and backtesting
|
| 93 |
+
|
| 94 |
+
### Trading Controls
|
| 95 |
+
- **Live Trading**: Start/stop live trading with Alpaca
|
| 96 |
+
- **Backtesting**: Historical strategy testing
|
| 97 |
+
- **Risk Management**: Position sizing and drawdown limits
|
| 98 |
+
- **Emergency Stop**: Immediate trading halt
|
| 99 |
+
|
| 100 |
+
### Portfolio Monitoring
|
| 101 |
+
- **Real-time Portfolio**: Live portfolio value and P&L
|
| 102 |
+
- **Position Analysis**: Individual position performance
|
| 103 |
+
- **Allocation Charts**: Portfolio allocation visualization
|
| 104 |
+
- **Risk Metrics**: Sharpe ratio, drawdown analysis
|
| 105 |
+
|
| 106 |
+
## π Dash UI Features
|
| 107 |
+
|
| 108 |
+
### Enterprise Dashboard
|
| 109 |
+
- **Professional Styling**: Bootstrap themes and responsive design
|
| 110 |
+
- **Real-time Updates**: Live data streaming and updates
|
| 111 |
+
- **Advanced Charts**: Interactive Plotly charts with zoom, pan, hover
|
| 112 |
+
- **Multi-page Navigation**: Tabbed interface for different functions
|
| 113 |
+
|
| 114 |
+
### Advanced Analytics
|
| 115 |
+
- **Technical Analysis**: Advanced charting with indicators
|
| 116 |
+
- **Performance Metrics**: Comprehensive trading performance analysis
|
| 117 |
+
- **Risk Management**: Advanced risk monitoring and alerts
|
| 118 |
+
- **Strategy Comparison**: Multiple strategy backtesting and comparison
|
| 119 |
+
|
| 120 |
+
### Real-time Monitoring
|
| 121 |
+
- **Live Trading Activity**: Real-time trade execution monitoring
|
| 122 |
+
- **System Alerts**: Automated alerts for important events
|
| 123 |
+
- **Portfolio Tracking**: Live portfolio updates and analysis
|
| 124 |
+
- **Market Data**: Real-time market data visualization
|
| 125 |
+
|
| 126 |
+
## π Jupyter UI Features
|
| 127 |
+
|
| 128 |
+
### Interactive Development
|
| 129 |
+
- **Widget-based Interface**: Interactive controls for all functions
|
| 130 |
+
- **Code Execution**: Direct Python code execution and experimentation
|
| 131 |
+
- **Data Exploration**: Interactive data analysis and visualization
|
| 132 |
+
- **Model Development**: Iterative model training and testing
|
| 133 |
+
|
| 134 |
+
### Research Tools
|
| 135 |
+
- **Notebook Integration**: Rich documentation and code examples
|
| 136 |
+
- **Data Analysis**: Pandas and NumPy integration
|
| 137 |
+
- **Visualization**: Matplotlib, Seaborn, Plotly integration
|
| 138 |
+
- **Experiment Tracking**: Training history and model comparison
|
| 139 |
+
|
| 140 |
+
## π WebSocket API
|
| 141 |
+
|
| 142 |
+
### Real-time Data Streaming
|
| 143 |
+
```javascript
|
| 144 |
+
// Connect to WebSocket server
|
| 145 |
+
const ws = new WebSocket('ws://localhost:8765');
|
| 146 |
+
|
| 147 |
+
// Listen for market data updates
|
| 148 |
+
ws.onmessage = function(event) {
|
| 149 |
+
const data = JSON.parse(event.data);
|
| 150 |
+
|
| 151 |
+
if (data.type === 'market_data') {
|
| 152 |
+
console.log('Price:', data.price);
|
| 153 |
+
console.log('Volume:', data.volume);
|
| 154 |
+
}
|
| 155 |
+
|
| 156 |
+
if (data.type === 'trading_signal') {
|
| 157 |
+
console.log('Signal:', data.signal);
|
| 158 |
+
}
|
| 159 |
+
|
| 160 |
+
if (data.type === 'portfolio_update') {
|
| 161 |
+
console.log('Portfolio:', data.account);
|
| 162 |
+
}
|
| 163 |
+
};
|
| 164 |
+
```
|
| 165 |
+
|
| 166 |
+
### Available Message Types
|
| 167 |
+
- `market_data`: Real-time price and volume data
|
| 168 |
+
- `trading_signal`: FinRL model trading signals
|
| 169 |
+
- `portfolio_update`: Account and position updates
|
| 170 |
+
- `trading_status`: Trading system status
|
| 171 |
+
- `system_alert`: System alerts and notifications
|
| 172 |
+
|
| 173 |
+
## π οΈ Configuration
|
| 174 |
+
|
| 175 |
+
### Environment Variables
|
| 176 |
+
```bash
|
| 177 |
+
# Alpaca API credentials
|
| 178 |
+
export ALPACA_API_KEY="your_api_key"
|
| 179 |
+
export ALPACA_SECRET_KEY="your_secret_key"
|
| 180 |
+
|
| 181 |
+
# UI configuration
|
| 182 |
+
export STREAMLIT_SERVER_PORT=8501
|
| 183 |
+
export DASH_SERVER_PORT=8050
|
| 184 |
+
export JUPYTER_PORT=8888
|
| 185 |
+
export WEBSOCKET_PORT=8765
|
| 186 |
+
```
|
| 187 |
+
|
| 188 |
+
### Configuration File
|
| 189 |
+
```yaml
|
| 190 |
+
# config.yaml
|
| 191 |
+
ui:
|
| 192 |
+
streamlit:
|
| 193 |
+
server_port: 8501
|
| 194 |
+
server_address: "0.0.0.0"
|
| 195 |
+
theme: "light"
|
| 196 |
+
|
| 197 |
+
dash:
|
| 198 |
+
server_port: 8050
|
| 199 |
+
server_address: "0.0.0.0"
|
| 200 |
+
theme: "bootstrap"
|
| 201 |
+
|
| 202 |
+
jupyter:
|
| 203 |
+
port: 8888
|
| 204 |
+
ip: "0.0.0.0"
|
| 205 |
+
token: ""
|
| 206 |
+
|
| 207 |
+
websocket:
|
| 208 |
+
host: "0.0.0.0"
|
| 209 |
+
port: 8765
|
| 210 |
+
max_connections: 100
|
| 211 |
+
```
|
| 212 |
+
|
| 213 |
+
## π§ Customization
|
| 214 |
+
|
| 215 |
+
### Adding Custom Charts
|
| 216 |
+
```python
|
| 217 |
+
# In ui/streamlit_app.py
|
| 218 |
+
def create_custom_chart(data):
|
| 219 |
+
fig = go.Figure()
|
| 220 |
+
fig.add_trace(go.Scatter(
|
| 221 |
+
x=data['timestamp'],
|
| 222 |
+
y=data['custom_indicator'],
|
| 223 |
+
name='Custom Indicator'
|
| 224 |
+
))
|
| 225 |
+
return fig
|
| 226 |
+
```
|
| 227 |
+
|
| 228 |
+
### Custom Trading Strategies
|
| 229 |
+
```python
|
| 230 |
+
# In ui/dash_app.py
|
| 231 |
+
def custom_strategy(data, config):
|
| 232 |
+
# Implement your custom strategy
|
| 233 |
+
signals = []
|
| 234 |
+
for i in range(len(data)):
|
| 235 |
+
if data['sma_20'][i] > data['sma_50'][i]:
|
| 236 |
+
signals.append('BUY')
|
| 237 |
+
else:
|
| 238 |
+
signals.append('SELL')
|
| 239 |
+
return signals
|
| 240 |
+
```
|
| 241 |
+
|
| 242 |
+
### WebSocket Custom Messages
|
| 243 |
+
```python
|
| 244 |
+
# In ui/websocket_server.py
|
| 245 |
+
async def broadcast_custom_message(self, message_type, data):
|
| 246 |
+
message = {
|
| 247 |
+
"type": message_type,
|
| 248 |
+
"timestamp": datetime.now().isoformat(),
|
| 249 |
+
"data": data
|
| 250 |
+
}
|
| 251 |
+
await self.broadcast(message)
|
| 252 |
+
```
|
| 253 |
+
|
| 254 |
+
## π Deployment
|
| 255 |
+
|
| 256 |
+
### Docker Deployment
|
| 257 |
+
```bash
|
| 258 |
+
# Build UI-enabled Docker image
|
| 259 |
+
docker build -t trading-ui .
|
| 260 |
+
|
| 261 |
+
# Run with UI ports exposed
|
| 262 |
+
docker run -p 8501:8501 -p 8050:8050 -p 8888:8888 -p 8765:8765 trading-ui
|
| 263 |
+
```
|
| 264 |
+
|
| 265 |
+
### Production Deployment
|
| 266 |
+
```bash
|
| 267 |
+
# Using Gunicorn for production
|
| 268 |
+
pip install gunicorn
|
| 269 |
+
|
| 270 |
+
# Start Dash app with Gunicorn
|
| 271 |
+
gunicorn -w 4 -b 0.0.0.0:8050 ui.dash_app:app
|
| 272 |
+
|
| 273 |
+
# Start Streamlit with production settings
|
| 274 |
+
streamlit run ui/streamlit_app.py --server.port 8501 --server.address 0.0.0.0
|
| 275 |
+
```
|
| 276 |
+
|
| 277 |
+
### Cloud Deployment
|
| 278 |
+
```bash
|
| 279 |
+
# Deploy to Heroku
|
| 280 |
+
heroku create trading-ui-app
|
| 281 |
+
git push heroku main
|
| 282 |
+
|
| 283 |
+
# Deploy to AWS
|
| 284 |
+
aws ecs create-service --cluster trading-cluster --service-name trading-ui
|
| 285 |
+
```
|
| 286 |
+
|
| 287 |
+
## π Troubleshooting
|
| 288 |
+
|
| 289 |
+
### Common Issues
|
| 290 |
+
|
| 291 |
+
#### Port Already in Use
|
| 292 |
+
```bash
|
| 293 |
+
# Find process using port
|
| 294 |
+
lsof -i :8501
|
| 295 |
+
|
| 296 |
+
# Kill process
|
| 297 |
+
kill -9 <PID>
|
| 298 |
+
|
| 299 |
+
# Or use different port
|
| 300 |
+
python ui_launcher.py streamlit --port 8502
|
| 301 |
+
```
|
| 302 |
+
|
| 303 |
+
#### Missing Dependencies
|
| 304 |
+
```bash
|
| 305 |
+
# Install missing packages
|
| 306 |
+
pip install streamlit dash plotly ipywidgets
|
| 307 |
+
|
| 308 |
+
# Or reinstall all requirements
|
| 309 |
+
pip install -r requirements.txt
|
| 310 |
+
```
|
| 311 |
+
|
| 312 |
+
#### Alpaca Connection Issues
|
| 313 |
+
```bash
|
| 314 |
+
# Check API credentials
|
| 315 |
+
echo $ALPACA_API_KEY
|
| 316 |
+
echo $ALPACA_SECRET_KEY
|
| 317 |
+
|
| 318 |
+
# Test connection
|
| 319 |
+
python -c "from agentic_ai_system.alpaca_broker import AlpacaBroker; print('Connection test')"
|
| 320 |
+
```
|
| 321 |
+
|
| 322 |
+
### Debug Mode
|
| 323 |
+
```bash
|
| 324 |
+
# Enable debug logging
|
| 325 |
+
export LOG_LEVEL=DEBUG
|
| 326 |
+
|
| 327 |
+
# Run with debug output
|
| 328 |
+
python ui_launcher.py streamlit --debug
|
| 329 |
+
```
|
| 330 |
+
|
| 331 |
+
## π API Reference
|
| 332 |
+
|
| 333 |
+
### Streamlit Functions
|
| 334 |
+
- `create_streamlit_app()`: Create Streamlit application
|
| 335 |
+
- `TradingUI.run()`: Run the main UI application
|
| 336 |
+
- `load_configuration()`: Load trading configuration
|
| 337 |
+
- `display_system_status()`: Show system status
|
| 338 |
+
|
| 339 |
+
### Dash Functions
|
| 340 |
+
- `create_dash_app()`: Create Dash application
|
| 341 |
+
- `TradingDashApp.setup_layout()`: Setup dashboard layout
|
| 342 |
+
- `TradingDashApp.setup_callbacks()`: Setup interactive callbacks
|
| 343 |
+
|
| 344 |
+
### Jupyter Functions
|
| 345 |
+
- `create_jupyter_interface()`: Create Jupyter interface
|
| 346 |
+
- `TradingJupyterUI.display_interface()`: Display interactive widgets
|
| 347 |
+
- `TradingJupyterUI.update_chart()`: Update chart displays
|
| 348 |
+
|
| 349 |
+
### WebSocket Functions
|
| 350 |
+
- `create_websocket_server()`: Create WebSocket server
|
| 351 |
+
- `TradingWebSocketServer.broadcast()`: Broadcast messages
|
| 352 |
+
- `TradingWebSocketServer.handle_client_message()`: Handle client messages
|
| 353 |
+
|
| 354 |
+
## π€ Contributing
|
| 355 |
+
|
| 356 |
+
### Adding New UI Features
|
| 357 |
+
1. Create feature branch: `git checkout -b feature/new-ui-feature`
|
| 358 |
+
2. Implement feature in appropriate UI module
|
| 359 |
+
3. Add tests in `tests/ui/` directory
|
| 360 |
+
4. Update documentation
|
| 361 |
+
5. Submit pull request
|
| 362 |
+
|
| 363 |
+
### UI Development Guidelines
|
| 364 |
+
- Follow PEP 8 style guidelines
|
| 365 |
+
- Add type hints for all functions
|
| 366 |
+
- Include docstrings for all classes and methods
|
| 367 |
+
- Write unit tests for new features
|
| 368 |
+
- Update documentation for new features
|
| 369 |
+
|
| 370 |
+
## π Support
|
| 371 |
+
|
| 372 |
+
For UI-related issues:
|
| 373 |
+
1. Check the troubleshooting section
|
| 374 |
+
2. Review the logs in `logs/ui/` directory
|
| 375 |
+
3. Create an issue on GitHub with detailed error information
|
| 376 |
+
4. Include system information and error logs
|
| 377 |
+
|
| 378 |
+
## π Updates
|
| 379 |
+
|
| 380 |
+
### UI Version History
|
| 381 |
+
- **v1.0.0**: Initial UI implementation with Streamlit, Dash, Jupyter, and WebSocket
|
| 382 |
+
- **v1.1.0**: Added real-time data streaming and advanced charts
|
| 383 |
+
- **v1.2.0**: Enhanced portfolio monitoring and risk management
|
| 384 |
+
- **v1.3.0**: Added custom strategy development tools
|
| 385 |
+
|
| 386 |
+
### Upcoming Features
|
| 387 |
+
- **v1.4.0**: Machine learning model visualization
|
| 388 |
+
- **v1.5.0**: Advanced backtesting interface
|
| 389 |
+
- **v1.6.0**: Multi-asset portfolio management
|
| 390 |
+
- **v1.7.0**: Social trading features
|
requirements.txt
CHANGED
|
@@ -1,19 +1,54 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
torch
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Core dependencies
|
| 2 |
+
numpy>=1.21.0
|
| 3 |
+
pandas>=1.3.0
|
| 4 |
+
scikit-learn>=1.0.0
|
| 5 |
+
matplotlib>=3.5.0
|
| 6 |
+
seaborn>=0.11.0
|
| 7 |
+
yaml>=5.4.0
|
| 8 |
+
requests>=2.25.0
|
| 9 |
+
python-dotenv>=0.19.0
|
| 10 |
+
|
| 11 |
+
# FinRL dependencies
|
| 12 |
+
stable-baselines3>=1.5.0
|
| 13 |
+
gym>=0.21.0
|
| 14 |
+
torch>=1.9.0
|
| 15 |
+
|
| 16 |
+
# Alpaca integration
|
| 17 |
+
alpaca-trade-api>=2.0.0
|
| 18 |
+
|
| 19 |
+
# Testing
|
| 20 |
+
pytest>=6.0.0
|
| 21 |
+
pytest-cov>=2.12.0
|
| 22 |
+
|
| 23 |
+
# UI Dependencies
|
| 24 |
+
streamlit>=1.28.0
|
| 25 |
+
plotly>=5.15.0
|
| 26 |
+
dash>=2.14.0
|
| 27 |
+
dash-bootstrap-components>=1.4.0
|
| 28 |
+
dash-extensions>=1.0.0
|
| 29 |
+
dash-table>=5.0.0
|
| 30 |
+
dash-cytoscape>=0.3.0
|
| 31 |
+
dash-mantine-components>=0.12.0
|
| 32 |
+
dash-iconify>=0.1.0
|
| 33 |
+
dash-uploader>=0.6.0
|
| 34 |
+
dash-daq>=0.5.0
|
| 35 |
+
|
| 36 |
+
# Additional UI enhancements
|
| 37 |
+
rich>=13.0.0
|
| 38 |
+
tqdm>=4.64.0
|
| 39 |
+
ipywidgets>=8.0.0
|
| 40 |
+
voila>=0.4.0
|
| 41 |
+
jupyter-dash>=0.4.0
|
| 42 |
+
|
| 43 |
+
# Real-time updates
|
| 44 |
+
websockets>=10.0
|
| 45 |
+
asyncio-mqtt>=0.11.0
|
| 46 |
+
|
| 47 |
+
# Data visualization
|
| 48 |
+
bokeh>=3.0.0
|
| 49 |
+
altair>=5.0.0
|
| 50 |
+
vega-lite>=5.0.0
|
| 51 |
+
|
| 52 |
+
# Authentication and security
|
| 53 |
+
dash-auth>=2.0.0
|
| 54 |
+
flask-login>=0.6.0
|
tests/test_ui_integration.py
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Test UI integration for the algorithmic trading system
|
| 3 |
+
"""
|
| 4 |
+
|
| 5 |
+
import pytest
|
| 6 |
+
import sys
|
| 7 |
+
import os
|
| 8 |
+
from unittest.mock import patch, MagicMock
|
| 9 |
+
|
| 10 |
+
# Add project root to path
|
| 11 |
+
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
| 12 |
+
|
| 13 |
+
def test_ui_imports():
|
| 14 |
+
"""Test that UI modules can be imported"""
|
| 15 |
+
try:
|
| 16 |
+
from ui import create_streamlit_app, create_dash_app, create_jupyter_interface, TradingWebSocketServer
|
| 17 |
+
assert True
|
| 18 |
+
except ImportError as e:
|
| 19 |
+
pytest.fail(f"Failed to import UI modules: {e}")
|
| 20 |
+
|
| 21 |
+
def test_streamlit_app_creation():
|
| 22 |
+
"""Test Streamlit app creation"""
|
| 23 |
+
try:
|
| 24 |
+
from ui.streamlit_app import TradingUI
|
| 25 |
+
ui = TradingUI()
|
| 26 |
+
assert ui is not None
|
| 27 |
+
assert hasattr(ui, 'config')
|
| 28 |
+
assert hasattr(ui, 'data')
|
| 29 |
+
assert hasattr(ui, 'alpaca_broker')
|
| 30 |
+
except Exception as e:
|
| 31 |
+
pytest.fail(f"Failed to create Streamlit UI: {e}")
|
| 32 |
+
|
| 33 |
+
def test_dash_app_creation():
|
| 34 |
+
"""Test Dash app creation"""
|
| 35 |
+
try:
|
| 36 |
+
from ui.dash_app import TradingDashApp
|
| 37 |
+
app = TradingDashApp()
|
| 38 |
+
assert app is not None
|
| 39 |
+
assert hasattr(app, 'app')
|
| 40 |
+
assert hasattr(app, 'config')
|
| 41 |
+
except Exception as e:
|
| 42 |
+
pytest.fail(f"Failed to create Dash app: {e}")
|
| 43 |
+
|
| 44 |
+
def test_jupyter_ui_creation():
|
| 45 |
+
"""Test Jupyter UI creation"""
|
| 46 |
+
try:
|
| 47 |
+
from ui.jupyter_widgets import TradingJupyterUI
|
| 48 |
+
ui = TradingJupyterUI()
|
| 49 |
+
assert ui is not None
|
| 50 |
+
assert hasattr(ui, 'config')
|
| 51 |
+
assert hasattr(ui, 'data')
|
| 52 |
+
except Exception as e:
|
| 53 |
+
pytest.fail(f"Failed to create Jupyter UI: {e}")
|
| 54 |
+
|
| 55 |
+
def test_websocket_server_creation():
|
| 56 |
+
"""Test WebSocket server creation"""
|
| 57 |
+
try:
|
| 58 |
+
from ui.websocket_server import TradingWebSocketServer
|
| 59 |
+
server = TradingWebSocketServer(host="localhost", port=8765)
|
| 60 |
+
assert server is not None
|
| 61 |
+
assert server.host == "localhost"
|
| 62 |
+
assert server.port == 8765
|
| 63 |
+
assert hasattr(server, 'clients')
|
| 64 |
+
except Exception as e:
|
| 65 |
+
pytest.fail(f"Failed to create WebSocket server: {e}")
|
| 66 |
+
|
| 67 |
+
def test_ui_launcher_imports():
|
| 68 |
+
"""Test UI launcher imports"""
|
| 69 |
+
try:
|
| 70 |
+
import ui_launcher
|
| 71 |
+
assert hasattr(ui_launcher, 'check_dependencies')
|
| 72 |
+
assert hasattr(ui_launcher, 'launch_streamlit')
|
| 73 |
+
assert hasattr(ui_launcher, 'launch_dash')
|
| 74 |
+
assert hasattr(ui_launcher, 'launch_jupyter')
|
| 75 |
+
assert hasattr(ui_launcher, 'launch_websocket_server')
|
| 76 |
+
except Exception as e:
|
| 77 |
+
pytest.fail(f"Failed to import UI launcher: {e}")
|
| 78 |
+
|
| 79 |
+
@patch('subprocess.run')
|
| 80 |
+
def test_ui_launcher_functions(mock_run):
|
| 81 |
+
"""Test UI launcher functions"""
|
| 82 |
+
mock_run.return_value = MagicMock()
|
| 83 |
+
|
| 84 |
+
try:
|
| 85 |
+
import ui_launcher
|
| 86 |
+
|
| 87 |
+
# Test dependency check
|
| 88 |
+
result = ui_launcher.check_dependencies()
|
| 89 |
+
assert isinstance(result, bool)
|
| 90 |
+
|
| 91 |
+
# Test launcher functions (they should not raise exceptions)
|
| 92 |
+
ui_launcher.launch_streamlit()
|
| 93 |
+
ui_launcher.launch_dash()
|
| 94 |
+
ui_launcher.launch_jupyter()
|
| 95 |
+
ui_launcher.launch_websocket_server()
|
| 96 |
+
|
| 97 |
+
except Exception as e:
|
| 98 |
+
pytest.fail(f"Failed to test UI launcher functions: {e}")
|
| 99 |
+
|
| 100 |
+
def test_ui_configuration():
|
| 101 |
+
"""Test UI configuration loading"""
|
| 102 |
+
try:
|
| 103 |
+
from agentic_ai_system.main import load_config
|
| 104 |
+
config = load_config()
|
| 105 |
+
|
| 106 |
+
# Check if UI-related config can be added
|
| 107 |
+
config['ui'] = {
|
| 108 |
+
'streamlit': {
|
| 109 |
+
'server_port': 8501,
|
| 110 |
+
'server_address': "0.0.0.0"
|
| 111 |
+
},
|
| 112 |
+
'dash': {
|
| 113 |
+
'server_port': 8050,
|
| 114 |
+
'server_address': "0.0.0.0"
|
| 115 |
+
}
|
| 116 |
+
}
|
| 117 |
+
|
| 118 |
+
assert 'ui' in config
|
| 119 |
+
assert 'streamlit' in config['ui']
|
| 120 |
+
assert 'dash' in config['ui']
|
| 121 |
+
|
| 122 |
+
except Exception as e:
|
| 123 |
+
pytest.fail(f"Failed to test UI configuration: {e}")
|
| 124 |
+
|
| 125 |
+
def test_ui_dependencies():
|
| 126 |
+
"""Test that UI dependencies are available"""
|
| 127 |
+
required_packages = [
|
| 128 |
+
'streamlit',
|
| 129 |
+
'dash',
|
| 130 |
+
'plotly',
|
| 131 |
+
'ipywidgets'
|
| 132 |
+
]
|
| 133 |
+
|
| 134 |
+
missing_packages = []
|
| 135 |
+
for package in required_packages:
|
| 136 |
+
try:
|
| 137 |
+
__import__(package)
|
| 138 |
+
except ImportError:
|
| 139 |
+
missing_packages.append(package)
|
| 140 |
+
|
| 141 |
+
if missing_packages:
|
| 142 |
+
pytest.skip(f"Missing UI dependencies: {missing_packages}")
|
| 143 |
+
else:
|
| 144 |
+
assert True
|
| 145 |
+
|
| 146 |
+
if __name__ == "__main__":
|
| 147 |
+
pytest.main([__file__])
|
ui/__init__.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
UI Package for Algorithmic Trading System
|
| 3 |
+
|
| 4 |
+
This package provides multiple UI options:
|
| 5 |
+
- Streamlit: Quick prototyping and data science workflows
|
| 6 |
+
- Dash: Enterprise-grade interactive dashboards
|
| 7 |
+
- Jupyter: Notebook-based interfaces
|
| 8 |
+
- WebSocket: Real-time trading interfaces
|
| 9 |
+
"""
|
| 10 |
+
|
| 11 |
+
__version__ = "1.0.0"
|
| 12 |
+
__author__ = "Algorithmic Trading Team"
|
| 13 |
+
|
| 14 |
+
from .streamlit_app import create_streamlit_app
|
| 15 |
+
from .dash_app import create_dash_app
|
| 16 |
+
from .jupyter_widgets import create_jupyter_interface
|
| 17 |
+
from .websocket_server import TradingWebSocketServer
|
| 18 |
+
|
| 19 |
+
__all__ = [
|
| 20 |
+
"create_streamlit_app",
|
| 21 |
+
"create_dash_app",
|
| 22 |
+
"create_jupyter_interface",
|
| 23 |
+
"TradingWebSocketServer"
|
| 24 |
+
]
|
ui/dash_app.py
ADDED
|
@@ -0,0 +1,657 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Dash UI for Algorithmic Trading System
|
| 3 |
+
|
| 4 |
+
Enterprise-grade interactive dashboard with:
|
| 5 |
+
- Real-time market data visualization
|
| 6 |
+
- Advanced trading analytics
|
| 7 |
+
- Portfolio management
|
| 8 |
+
- Risk monitoring
|
| 9 |
+
- Strategy backtesting
|
| 10 |
+
"""
|
| 11 |
+
|
| 12 |
+
import dash
|
| 13 |
+
from dash import dcc, html, Input, Output, State, callback_context
|
| 14 |
+
import dash_bootstrap_components as dbc
|
| 15 |
+
from dash_bootstrap_components import themes
|
| 16 |
+
import plotly.graph_objects as go
|
| 17 |
+
import plotly.express as px
|
| 18 |
+
import pandas as pd
|
| 19 |
+
import numpy as np
|
| 20 |
+
import yaml
|
| 21 |
+
import os
|
| 22 |
+
import sys
|
| 23 |
+
from datetime import datetime, timedelta
|
| 24 |
+
import asyncio
|
| 25 |
+
import threading
|
| 26 |
+
import time
|
| 27 |
+
from typing import Dict, Any, Optional
|
| 28 |
+
|
| 29 |
+
# Add project root to path
|
| 30 |
+
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
| 31 |
+
|
| 32 |
+
from agentic_ai_system.main import load_config
|
| 33 |
+
from agentic_ai_system.data_ingestion import load_data, validate_data, add_technical_indicators
|
| 34 |
+
from agentic_ai_system.finrl_agent import FinRLAgent, FinRLConfig
|
| 35 |
+
from agentic_ai_system.alpaca_broker import AlpacaBroker
|
| 36 |
+
from agentic_ai_system.orchestrator import run_backtest, run_live_trading
|
| 37 |
+
|
| 38 |
+
class TradingDashApp:
|
| 39 |
+
def __init__(self):
|
| 40 |
+
self.app = dash.Dash(
|
| 41 |
+
__name__,
|
| 42 |
+
external_stylesheets=[
|
| 43 |
+
themes.BOOTSTRAP,
|
| 44 |
+
"https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css"
|
| 45 |
+
],
|
| 46 |
+
suppress_callback_exceptions=True
|
| 47 |
+
)
|
| 48 |
+
|
| 49 |
+
self.config = None
|
| 50 |
+
self.data = None
|
| 51 |
+
self.alpaca_broker = None
|
| 52 |
+
self.finrl_agent = None
|
| 53 |
+
|
| 54 |
+
self.setup_layout()
|
| 55 |
+
self.setup_callbacks()
|
| 56 |
+
|
| 57 |
+
def setup_layout(self):
|
| 58 |
+
"""Setup the main application layout"""
|
| 59 |
+
self.app.layout = dbc.Container([
|
| 60 |
+
# Header
|
| 61 |
+
dbc.Row([
|
| 62 |
+
dbc.Col([
|
| 63 |
+
html.H1([
|
| 64 |
+
html.I(className="fas fa-chart-line me-3"),
|
| 65 |
+
"Algorithmic Trading System"
|
| 66 |
+
], className="text-primary mb-4 text-center")
|
| 67 |
+
])
|
| 68 |
+
]),
|
| 69 |
+
|
| 70 |
+
# Navigation tabs
|
| 71 |
+
dbc.Tabs([
|
| 72 |
+
dbc.Tab(self.create_dashboard_tab(), label="Dashboard", tab_id="dashboard"),
|
| 73 |
+
dbc.Tab(self.create_data_tab(), label="Data", tab_id="data"),
|
| 74 |
+
dbc.Tab(self.create_trading_tab(), label="Trading", tab_id="trading"),
|
| 75 |
+
dbc.Tab(self.create_analytics_tab(), label="Analytics", tab_id="analytics"),
|
| 76 |
+
dbc.Tab(self.create_portfolio_tab(), label="Portfolio", tab_id="portfolio"),
|
| 77 |
+
dbc.Tab(self.create_settings_tab(), label="Settings", tab_id="settings")
|
| 78 |
+
], id="tabs", active_tab="dashboard"),
|
| 79 |
+
|
| 80 |
+
# Store components for data persistence
|
| 81 |
+
dcc.Store(id="config-store"),
|
| 82 |
+
dcc.Store(id="data-store"),
|
| 83 |
+
dcc.Store(id="alpaca-store"),
|
| 84 |
+
dcc.Store(id="finrl-store"),
|
| 85 |
+
dcc.Store(id="trading-status-store"),
|
| 86 |
+
|
| 87 |
+
# Interval for real-time updates
|
| 88 |
+
dcc.Interval(
|
| 89 |
+
id="interval-component",
|
| 90 |
+
interval=5*1000, # 5 seconds
|
| 91 |
+
n_intervals=0
|
| 92 |
+
)
|
| 93 |
+
], fluid=True)
|
| 94 |
+
|
| 95 |
+
def create_dashboard_tab(self):
|
| 96 |
+
"""Create the main dashboard tab"""
|
| 97 |
+
return dbc.Container([
|
| 98 |
+
# System status cards
|
| 99 |
+
dbc.Row([
|
| 100 |
+
dbc.Col(self.create_status_card("Trading Status", "Active", "success"), width=3),
|
| 101 |
+
dbc.Col(self.create_status_card("Portfolio Value", "$100,000", "info"), width=3),
|
| 102 |
+
dbc.Col(self.create_status_card("Daily P&L", "+$1,250", "success"), width=3),
|
| 103 |
+
dbc.Col(self.create_status_card("Risk Level", "Low", "warning"), width=3)
|
| 104 |
+
], className="mb-4"),
|
| 105 |
+
|
| 106 |
+
# Charts row
|
| 107 |
+
dbc.Row([
|
| 108 |
+
dbc.Col([
|
| 109 |
+
dbc.Card([
|
| 110 |
+
dbc.CardHeader("Price Chart"),
|
| 111 |
+
dbc.CardBody([
|
| 112 |
+
dcc.Graph(id="price-chart", style={"height": "400px"})
|
| 113 |
+
])
|
| 114 |
+
])
|
| 115 |
+
], width=8),
|
| 116 |
+
dbc.Col([
|
| 117 |
+
dbc.Card([
|
| 118 |
+
dbc.CardHeader("Portfolio Allocation"),
|
| 119 |
+
dbc.CardBody([
|
| 120 |
+
dcc.Graph(id="allocation-chart", style={"height": "400px"})
|
| 121 |
+
])
|
| 122 |
+
])
|
| 123 |
+
], width=4)
|
| 124 |
+
], className="mb-4"),
|
| 125 |
+
|
| 126 |
+
# Trading activity and alerts
|
| 127 |
+
dbc.Row([
|
| 128 |
+
dbc.Col([
|
| 129 |
+
dbc.Card([
|
| 130 |
+
dbc.CardHeader("Recent Trades"),
|
| 131 |
+
dbc.CardBody([
|
| 132 |
+
html.Div(id="trades-table")
|
| 133 |
+
])
|
| 134 |
+
])
|
| 135 |
+
], width=6),
|
| 136 |
+
dbc.Col([
|
| 137 |
+
dbc.Card([
|
| 138 |
+
dbc.CardHeader("System Alerts"),
|
| 139 |
+
dbc.CardBody([
|
| 140 |
+
html.Div(id="alerts-list")
|
| 141 |
+
])
|
| 142 |
+
])
|
| 143 |
+
], width=6)
|
| 144 |
+
])
|
| 145 |
+
])
|
| 146 |
+
|
| 147 |
+
def create_data_tab(self):
|
| 148 |
+
"""Create the data management tab"""
|
| 149 |
+
return dbc.Container([
|
| 150 |
+
dbc.Row([
|
| 151 |
+
dbc.Col([
|
| 152 |
+
dbc.Card([
|
| 153 |
+
dbc.CardHeader("Data Configuration"),
|
| 154 |
+
dbc.CardBody([
|
| 155 |
+
dbc.Row([
|
| 156 |
+
dbc.Col([
|
| 157 |
+
dbc.Label("Data Source"),
|
| 158 |
+
dbc.Select(
|
| 159 |
+
id="data-source-select",
|
| 160 |
+
options=[
|
| 161 |
+
{"label": "CSV File", "value": "csv"},
|
| 162 |
+
{"label": "Alpaca API", "value": "alpaca"},
|
| 163 |
+
{"label": "Synthetic Data", "value": "synthetic"}
|
| 164 |
+
],
|
| 165 |
+
value="csv"
|
| 166 |
+
)
|
| 167 |
+
], width=4),
|
| 168 |
+
dbc.Col([
|
| 169 |
+
dbc.Label("Symbol"),
|
| 170 |
+
dbc.Input(
|
| 171 |
+
id="symbol-input",
|
| 172 |
+
type="text",
|
| 173 |
+
value="AAPL",
|
| 174 |
+
placeholder="Enter symbol"
|
| 175 |
+
)
|
| 176 |
+
], width=4),
|
| 177 |
+
dbc.Col([
|
| 178 |
+
dbc.Label("Timeframe"),
|
| 179 |
+
dbc.Select(
|
| 180 |
+
id="timeframe-select",
|
| 181 |
+
options=[
|
| 182 |
+
{"label": "1 Minute", "value": "1m"},
|
| 183 |
+
{"label": "5 Minutes", "value": "5m"},
|
| 184 |
+
{"label": "15 Minutes", "value": "15m"},
|
| 185 |
+
{"label": "1 Hour", "value": "1h"},
|
| 186 |
+
{"label": "1 Day", "value": "1d"}
|
| 187 |
+
],
|
| 188 |
+
value="1m"
|
| 189 |
+
)
|
| 190 |
+
], width=4)
|
| 191 |
+
], className="mb-3"),
|
| 192 |
+
dbc.Row([
|
| 193 |
+
dbc.Col([
|
| 194 |
+
dbc.Button("Load Data", id="load-data-btn", color="primary", className="me-2"),
|
| 195 |
+
dbc.Button("Refresh Data", id="refresh-data-btn", color="secondary")
|
| 196 |
+
])
|
| 197 |
+
])
|
| 198 |
+
])
|
| 199 |
+
])
|
| 200 |
+
], width=6),
|
| 201 |
+
dbc.Col([
|
| 202 |
+
dbc.Card([
|
| 203 |
+
dbc.CardHeader("Data Statistics"),
|
| 204 |
+
dbc.CardBody([
|
| 205 |
+
html.Div(id="data-stats")
|
| 206 |
+
])
|
| 207 |
+
])
|
| 208 |
+
], width=6)
|
| 209 |
+
], className="mb-4"),
|
| 210 |
+
|
| 211 |
+
# Data visualization
|
| 212 |
+
dbc.Row([
|
| 213 |
+
dbc.Col([
|
| 214 |
+
dbc.Card([
|
| 215 |
+
dbc.CardHeader([
|
| 216 |
+
html.Span("Market Data Visualization"),
|
| 217 |
+
dbc.ButtonGroup([
|
| 218 |
+
dbc.Button("Candlestick", id="candlestick-btn", size="sm"),
|
| 219 |
+
dbc.Button("Line", id="line-btn", size="sm"),
|
| 220 |
+
dbc.Button("Volume", id="volume-btn", size="sm")
|
| 221 |
+
], className="float-end")
|
| 222 |
+
]),
|
| 223 |
+
dbc.CardBody([
|
| 224 |
+
dcc.Graph(id="market-chart", style={"height": "500px"})
|
| 225 |
+
])
|
| 226 |
+
])
|
| 227 |
+
])
|
| 228 |
+
])
|
| 229 |
+
])
|
| 230 |
+
|
| 231 |
+
def create_trading_tab(self):
|
| 232 |
+
"""Create the trading controls tab"""
|
| 233 |
+
return dbc.Container([
|
| 234 |
+
# Trading configuration
|
| 235 |
+
dbc.Row([
|
| 236 |
+
dbc.Col([
|
| 237 |
+
dbc.Card([
|
| 238 |
+
dbc.CardHeader("Trading Configuration"),
|
| 239 |
+
dbc.CardBody([
|
| 240 |
+
dbc.Row([
|
| 241 |
+
dbc.Col([
|
| 242 |
+
dbc.Label("Capital"),
|
| 243 |
+
dbc.Input(
|
| 244 |
+
id="capital-input",
|
| 245 |
+
type="number",
|
| 246 |
+
value=100000,
|
| 247 |
+
step=1000
|
| 248 |
+
)
|
| 249 |
+
], width=3),
|
| 250 |
+
dbc.Col([
|
| 251 |
+
dbc.Label("Order Size"),
|
| 252 |
+
dbc.Input(
|
| 253 |
+
id="order-size-input",
|
| 254 |
+
type="number",
|
| 255 |
+
value=10,
|
| 256 |
+
step=1
|
| 257 |
+
)
|
| 258 |
+
], width=3),
|
| 259 |
+
dbc.Col([
|
| 260 |
+
dbc.Label("Max Position"),
|
| 261 |
+
dbc.Input(
|
| 262 |
+
id="max-position-input",
|
| 263 |
+
type="number",
|
| 264 |
+
value=100,
|
| 265 |
+
step=10
|
| 266 |
+
)
|
| 267 |
+
], width=3),
|
| 268 |
+
dbc.Col([
|
| 269 |
+
dbc.Label("Max Drawdown"),
|
| 270 |
+
dbc.Input(
|
| 271 |
+
id="max-drawdown-input",
|
| 272 |
+
type="number",
|
| 273 |
+
value=0.05,
|
| 274 |
+
step=0.01,
|
| 275 |
+
min=0,
|
| 276 |
+
max=1
|
| 277 |
+
)
|
| 278 |
+
], width=3)
|
| 279 |
+
], className="mb-3"),
|
| 280 |
+
dbc.Row([
|
| 281 |
+
dbc.Col([
|
| 282 |
+
dbc.Button("Start Trading", id="start-trading-btn", color="success", className="me-2"),
|
| 283 |
+
dbc.Button("Stop Trading", id="stop-trading-btn", color="danger", className="me-2"),
|
| 284 |
+
dbc.Button("Emergency Stop", id="emergency-stop-btn", color="warning")
|
| 285 |
+
])
|
| 286 |
+
])
|
| 287 |
+
])
|
| 288 |
+
])
|
| 289 |
+
], width=6),
|
| 290 |
+
dbc.Col([
|
| 291 |
+
dbc.Card([
|
| 292 |
+
dbc.CardHeader("Alpaca Connection"),
|
| 293 |
+
dbc.CardBody([
|
| 294 |
+
dbc.Row([
|
| 295 |
+
dbc.Col([
|
| 296 |
+
dbc.Label("API Key"),
|
| 297 |
+
dbc.Input(
|
| 298 |
+
id="alpaca-api-key",
|
| 299 |
+
type="password",
|
| 300 |
+
placeholder="Enter Alpaca API key"
|
| 301 |
+
)
|
| 302 |
+
], width=6),
|
| 303 |
+
dbc.Col([
|
| 304 |
+
dbc.Label("Secret Key"),
|
| 305 |
+
dbc.Input(
|
| 306 |
+
id="alpaca-secret-key",
|
| 307 |
+
type="password",
|
| 308 |
+
placeholder="Enter Alpaca secret key"
|
| 309 |
+
)
|
| 310 |
+
], width=6)
|
| 311 |
+
], className="mb-3"),
|
| 312 |
+
dbc.Row([
|
| 313 |
+
dbc.Col([
|
| 314 |
+
dbc.Button("Connect", id="connect-alpaca-btn", color="primary", className="me-2"),
|
| 315 |
+
dbc.Button("Disconnect", id="disconnect-alpaca-btn", color="secondary")
|
| 316 |
+
])
|
| 317 |
+
])
|
| 318 |
+
])
|
| 319 |
+
])
|
| 320 |
+
], width=6)
|
| 321 |
+
], className="mb-4"),
|
| 322 |
+
|
| 323 |
+
# Trading activity
|
| 324 |
+
dbc.Row([
|
| 325 |
+
dbc.Col([
|
| 326 |
+
dbc.Card([
|
| 327 |
+
dbc.CardHeader("Live Trading Activity"),
|
| 328 |
+
dbc.CardBody([
|
| 329 |
+
html.Div(id="trading-activity")
|
| 330 |
+
])
|
| 331 |
+
])
|
| 332 |
+
])
|
| 333 |
+
])
|
| 334 |
+
])
|
| 335 |
+
|
| 336 |
+
def create_analytics_tab(self):
|
| 337 |
+
"""Create the analytics tab"""
|
| 338 |
+
return dbc.Container([
|
| 339 |
+
# FinRL training
|
| 340 |
+
dbc.Row([
|
| 341 |
+
dbc.Col([
|
| 342 |
+
dbc.Card([
|
| 343 |
+
dbc.CardHeader("FinRL Model Training"),
|
| 344 |
+
dbc.CardBody([
|
| 345 |
+
dbc.Row([
|
| 346 |
+
dbc.Col([
|
| 347 |
+
dbc.Label("Algorithm"),
|
| 348 |
+
dbc.Select(
|
| 349 |
+
id="finrl-algorithm-select",
|
| 350 |
+
options=[
|
| 351 |
+
{"label": "PPO", "value": "PPO"},
|
| 352 |
+
{"label": "A2C", "value": "A2C"},
|
| 353 |
+
{"label": "DDPG", "value": "DDPG"},
|
| 354 |
+
{"label": "TD3", "value": "TD3"}
|
| 355 |
+
],
|
| 356 |
+
value="PPO"
|
| 357 |
+
)
|
| 358 |
+
], width=3),
|
| 359 |
+
dbc.Col([
|
| 360 |
+
dbc.Label("Learning Rate"),
|
| 361 |
+
dbc.Input(
|
| 362 |
+
id="learning-rate-input",
|
| 363 |
+
type="number",
|
| 364 |
+
value=0.0003,
|
| 365 |
+
step=0.0001,
|
| 366 |
+
min=0.0001,
|
| 367 |
+
max=0.01
|
| 368 |
+
)
|
| 369 |
+
], width=3),
|
| 370 |
+
dbc.Col([
|
| 371 |
+
dbc.Label("Training Steps"),
|
| 372 |
+
dbc.Input(
|
| 373 |
+
id="training-steps-input",
|
| 374 |
+
type="number",
|
| 375 |
+
value=100000,
|
| 376 |
+
step=1000
|
| 377 |
+
)
|
| 378 |
+
], width=3),
|
| 379 |
+
dbc.Col([
|
| 380 |
+
dbc.Label("Batch Size"),
|
| 381 |
+
dbc.Select(
|
| 382 |
+
id="batch-size-select",
|
| 383 |
+
options=[
|
| 384 |
+
{"label": "32", "value": 32},
|
| 385 |
+
{"label": "64", "value": 64},
|
| 386 |
+
{"label": "128", "value": 128},
|
| 387 |
+
{"label": "256", "value": 256}
|
| 388 |
+
],
|
| 389 |
+
value=64
|
| 390 |
+
)
|
| 391 |
+
], width=3)
|
| 392 |
+
], className="mb-3"),
|
| 393 |
+
dbc.Row([
|
| 394 |
+
dbc.Col([
|
| 395 |
+
dbc.Button("Start Training", id="start-training-btn", color="primary", className="me-2"),
|
| 396 |
+
dbc.Button("Stop Training", id="stop-training-btn", color="danger")
|
| 397 |
+
])
|
| 398 |
+
])
|
| 399 |
+
])
|
| 400 |
+
])
|
| 401 |
+
], width=6),
|
| 402 |
+
dbc.Col([
|
| 403 |
+
dbc.Card([
|
| 404 |
+
dbc.CardHeader("Training Progress"),
|
| 405 |
+
dbc.CardBody([
|
| 406 |
+
dbc.Progress(id="training-progress", value=0, className="mb-3"),
|
| 407 |
+
html.Div(id="training-metrics")
|
| 408 |
+
])
|
| 409 |
+
])
|
| 410 |
+
], width=6)
|
| 411 |
+
], className="mb-4"),
|
| 412 |
+
|
| 413 |
+
# Backtesting
|
| 414 |
+
dbc.Row([
|
| 415 |
+
dbc.Col([
|
| 416 |
+
dbc.Card([
|
| 417 |
+
dbc.CardHeader("Strategy Backtesting"),
|
| 418 |
+
dbc.CardBody([
|
| 419 |
+
dbc.Row([
|
| 420 |
+
dbc.Col([
|
| 421 |
+
dbc.Button("Run Backtest", id="run-backtest-btn", color="primary", className="me-2"),
|
| 422 |
+
dbc.Button("Export Results", id="export-backtest-btn", color="secondary")
|
| 423 |
+
])
|
| 424 |
+
]),
|
| 425 |
+
html.Div(id="backtest-results")
|
| 426 |
+
])
|
| 427 |
+
])
|
| 428 |
+
])
|
| 429 |
+
])
|
| 430 |
+
])
|
| 431 |
+
|
| 432 |
+
def create_portfolio_tab(self):
|
| 433 |
+
"""Create the portfolio management tab"""
|
| 434 |
+
return dbc.Container([
|
| 435 |
+
# Portfolio overview
|
| 436 |
+
dbc.Row([
|
| 437 |
+
dbc.Col([
|
| 438 |
+
dbc.Card([
|
| 439 |
+
dbc.CardHeader("Portfolio Overview"),
|
| 440 |
+
dbc.CardBody([
|
| 441 |
+
dbc.Row([
|
| 442 |
+
dbc.Col([
|
| 443 |
+
html.H4("Total Value", className="text-muted"),
|
| 444 |
+
html.H3(id="total-value", children="$100,000")
|
| 445 |
+
], width=3),
|
| 446 |
+
dbc.Col([
|
| 447 |
+
html.H4("Cash", className="text-muted"),
|
| 448 |
+
html.H3(id="cash-value", children="$25,000")
|
| 449 |
+
], width=3),
|
| 450 |
+
dbc.Col([
|
| 451 |
+
html.H4("Invested", className="text-muted"),
|
| 452 |
+
html.H3(id="invested-value", children="$75,000")
|
| 453 |
+
], width=3),
|
| 454 |
+
dbc.Col([
|
| 455 |
+
html.H4("P&L", className="text-muted"),
|
| 456 |
+
html.H3(id="pnl-value", children="+$1,250", className="text-success")
|
| 457 |
+
], width=3)
|
| 458 |
+
])
|
| 459 |
+
])
|
| 460 |
+
])
|
| 461 |
+
])
|
| 462 |
+
], className="mb-4"),
|
| 463 |
+
|
| 464 |
+
# Positions and allocation
|
| 465 |
+
dbc.Row([
|
| 466 |
+
dbc.Col([
|
| 467 |
+
dbc.Card([
|
| 468 |
+
dbc.CardHeader("Current Positions"),
|
| 469 |
+
dbc.CardBody([
|
| 470 |
+
html.Div(id="positions-table")
|
| 471 |
+
])
|
| 472 |
+
])
|
| 473 |
+
], width=8),
|
| 474 |
+
dbc.Col([
|
| 475 |
+
dbc.Card([
|
| 476 |
+
dbc.CardHeader("Allocation Chart"),
|
| 477 |
+
dbc.CardBody([
|
| 478 |
+
dcc.Graph(id="portfolio-allocation-chart", style={"height": "300px"})
|
| 479 |
+
])
|
| 480 |
+
])
|
| 481 |
+
], width=4)
|
| 482 |
+
])
|
| 483 |
+
])
|
| 484 |
+
|
| 485 |
+
def create_settings_tab(self):
|
| 486 |
+
"""Create the settings tab"""
|
| 487 |
+
return dbc.Container([
|
| 488 |
+
dbc.Row([
|
| 489 |
+
dbc.Col([
|
| 490 |
+
dbc.Card([
|
| 491 |
+
dbc.CardHeader("System Configuration"),
|
| 492 |
+
dbc.CardBody([
|
| 493 |
+
dbc.Row([
|
| 494 |
+
dbc.Col([
|
| 495 |
+
dbc.Label("Config File"),
|
| 496 |
+
dbc.Input(
|
| 497 |
+
id="config-file-input",
|
| 498 |
+
type="text",
|
| 499 |
+
value="config.yaml",
|
| 500 |
+
placeholder="Enter config file path"
|
| 501 |
+
)
|
| 502 |
+
], width=6),
|
| 503 |
+
dbc.Col([
|
| 504 |
+
dbc.Label("Log Level"),
|
| 505 |
+
dbc.Select(
|
| 506 |
+
id="log-level-select",
|
| 507 |
+
options=[
|
| 508 |
+
{"label": "DEBUG", "value": "DEBUG"},
|
| 509 |
+
{"label": "INFO", "value": "INFO"},
|
| 510 |
+
{"label": "WARNING", "value": "WARNING"},
|
| 511 |
+
{"label": "ERROR", "value": "ERROR"}
|
| 512 |
+
],
|
| 513 |
+
value="INFO"
|
| 514 |
+
)
|
| 515 |
+
], width=6)
|
| 516 |
+
], className="mb-3"),
|
| 517 |
+
dbc.Row([
|
| 518 |
+
dbc.Col([
|
| 519 |
+
dbc.Button("Load Config", id="load-config-btn", color="primary", className="me-2"),
|
| 520 |
+
dbc.Button("Save Config", id="save-config-btn", color="success")
|
| 521 |
+
])
|
| 522 |
+
])
|
| 523 |
+
])
|
| 524 |
+
])
|
| 525 |
+
], width=6),
|
| 526 |
+
dbc.Col([
|
| 527 |
+
dbc.Card([
|
| 528 |
+
dbc.CardHeader("System Status"),
|
| 529 |
+
dbc.CardBody([
|
| 530 |
+
html.Div(id="system-status")
|
| 531 |
+
])
|
| 532 |
+
])
|
| 533 |
+
], width=6)
|
| 534 |
+
])
|
| 535 |
+
])
|
| 536 |
+
|
| 537 |
+
def create_status_card(self, title, value, color):
|
| 538 |
+
"""Create a status card component"""
|
| 539 |
+
return dbc.Card([
|
| 540 |
+
dbc.CardBody([
|
| 541 |
+
html.H5(title, className="card-title text-muted"),
|
| 542 |
+
html.H3(value, className=f"text-{color}")
|
| 543 |
+
])
|
| 544 |
+
])
|
| 545 |
+
|
| 546 |
+
def setup_callbacks(self):
|
| 547 |
+
"""Setup all Dash callbacks"""
|
| 548 |
+
|
| 549 |
+
@self.app.callback(
|
| 550 |
+
Output("config-store", "data"),
|
| 551 |
+
Input("load-config-btn", "n_clicks"),
|
| 552 |
+
State("config-file-input", "value"),
|
| 553 |
+
prevent_initial_call=True
|
| 554 |
+
)
|
| 555 |
+
def load_configuration(n_clicks, config_file):
|
| 556 |
+
if n_clicks:
|
| 557 |
+
try:
|
| 558 |
+
config = load_config(config_file)
|
| 559 |
+
return config
|
| 560 |
+
except Exception as e:
|
| 561 |
+
return {"error": str(e)}
|
| 562 |
+
return dash.no_update
|
| 563 |
+
|
| 564 |
+
@self.app.callback(
|
| 565 |
+
Output("data-store", "data"),
|
| 566 |
+
Input("load-data-btn", "n_clicks"),
|
| 567 |
+
State("config-store", "data"),
|
| 568 |
+
prevent_initial_call=True
|
| 569 |
+
)
|
| 570 |
+
def load_market_data(n_clicks, config):
|
| 571 |
+
if n_clicks and config:
|
| 572 |
+
try:
|
| 573 |
+
data = load_data(config)
|
| 574 |
+
if data is not None:
|
| 575 |
+
return data.to_dict('records')
|
| 576 |
+
except Exception as e:
|
| 577 |
+
return {"error": str(e)}
|
| 578 |
+
return dash.no_update
|
| 579 |
+
|
| 580 |
+
@self.app.callback(
|
| 581 |
+
Output("price-chart", "figure"),
|
| 582 |
+
Input("data-store", "data"),
|
| 583 |
+
Input("interval-component", "n_intervals")
|
| 584 |
+
)
|
| 585 |
+
def update_price_chart(data, n_intervals):
|
| 586 |
+
if data and isinstance(data, list):
|
| 587 |
+
df = pd.DataFrame(data)
|
| 588 |
+
if not df.empty:
|
| 589 |
+
fig = go.Figure(data=[go.Candlestick(
|
| 590 |
+
x=df['timestamp'],
|
| 591 |
+
open=df['open'],
|
| 592 |
+
high=df['high'],
|
| 593 |
+
low=df['low'],
|
| 594 |
+
close=df['close']
|
| 595 |
+
)])
|
| 596 |
+
fig.update_layout(
|
| 597 |
+
title="Market Data",
|
| 598 |
+
xaxis_title="Date",
|
| 599 |
+
yaxis_title="Price ($)",
|
| 600 |
+
height=400
|
| 601 |
+
)
|
| 602 |
+
return fig
|
| 603 |
+
return go.Figure()
|
| 604 |
+
|
| 605 |
+
@self.app.callback(
|
| 606 |
+
Output("allocation-chart", "figure"),
|
| 607 |
+
Input("alpaca-store", "data"),
|
| 608 |
+
Input("interval-component", "n_intervals")
|
| 609 |
+
)
|
| 610 |
+
def update_allocation_chart(alpaca_data, n_intervals):
|
| 611 |
+
# Mock portfolio allocation data
|
| 612 |
+
labels = ['AAPL', 'GOOGL', 'MSFT', 'TSLA', 'Cash']
|
| 613 |
+
values = [30, 25, 20, 15, 10]
|
| 614 |
+
|
| 615 |
+
fig = go.Figure(data=[go.Pie(labels=labels, values=values)])
|
| 616 |
+
fig.update_layout(
|
| 617 |
+
title="Portfolio Allocation",
|
| 618 |
+
height=400
|
| 619 |
+
)
|
| 620 |
+
return fig
|
| 621 |
+
|
| 622 |
+
@self.app.callback(
|
| 623 |
+
Output("trading-activity", "children"),
|
| 624 |
+
Input("interval-component", "n_intervals")
|
| 625 |
+
)
|
| 626 |
+
def update_trading_activity(n_intervals):
|
| 627 |
+
# Mock trading activity
|
| 628 |
+
trades = [
|
| 629 |
+
{"time": "09:30:15", "symbol": "AAPL", "action": "BUY", "quantity": 10, "price": 150.25},
|
| 630 |
+
{"time": "09:35:22", "symbol": "GOOGL", "action": "SELL", "quantity": 5, "price": 2750.50},
|
| 631 |
+
{"time": "09:40:08", "symbol": "MSFT", "action": "BUY", "quantity": 15, "price": 320.75}
|
| 632 |
+
]
|
| 633 |
+
|
| 634 |
+
table_rows = []
|
| 635 |
+
for trade in trades:
|
| 636 |
+
color = "success" if trade["action"] == "BUY" else "danger"
|
| 637 |
+
table_rows.append(
|
| 638 |
+
dbc.Row([
|
| 639 |
+
dbc.Col(trade["time"], width=2),
|
| 640 |
+
dbc.Col(trade["symbol"], width=2),
|
| 641 |
+
dbc.Col(trade["action"], width=2, className=f"text-{color}"),
|
| 642 |
+
dbc.Col(str(trade["quantity"]), width=2),
|
| 643 |
+
dbc.Col(f"${trade['price']:.2f}", width=2),
|
| 644 |
+
dbc.Col(f"${trade['quantity'] * trade['price']:.2f}", width=2)
|
| 645 |
+
], className="mb-2")
|
| 646 |
+
)
|
| 647 |
+
|
| 648 |
+
return table_rows
|
| 649 |
+
|
| 650 |
+
def create_dash_app():
|
| 651 |
+
"""Create and return the Dash application"""
|
| 652 |
+
app = TradingDashApp()
|
| 653 |
+
return app.app
|
| 654 |
+
|
| 655 |
+
if __name__ == "__main__":
|
| 656 |
+
app = create_dash_app()
|
| 657 |
+
app.run_server(debug=True, host="0.0.0.0", port=8050)
|
ui/jupyter_widgets.py
ADDED
|
@@ -0,0 +1,556 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Jupyter Widgets UI for Algorithmic Trading System
|
| 3 |
+
|
| 4 |
+
Interactive notebook interface for:
|
| 5 |
+
- Data exploration and visualization
|
| 6 |
+
- Strategy development and testing
|
| 7 |
+
- Model training and evaluation
|
| 8 |
+
- Real-time trading simulation
|
| 9 |
+
"""
|
| 10 |
+
|
| 11 |
+
import ipywidgets as widgets
|
| 12 |
+
from IPython.display import display, HTML, clear_output
|
| 13 |
+
import plotly.graph_objects as go
|
| 14 |
+
import plotly.express as px
|
| 15 |
+
import pandas as pd
|
| 16 |
+
import numpy as np
|
| 17 |
+
import yaml
|
| 18 |
+
import os
|
| 19 |
+
import sys
|
| 20 |
+
from datetime import datetime, timedelta
|
| 21 |
+
from typing import Dict, Any, Optional
|
| 22 |
+
import asyncio
|
| 23 |
+
import threading
|
| 24 |
+
import time
|
| 25 |
+
|
| 26 |
+
# Add project root to path
|
| 27 |
+
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
| 28 |
+
|
| 29 |
+
from agentic_ai_system.main import load_config
|
| 30 |
+
from agentic_ai_system.data_ingestion import load_data, validate_data, add_technical_indicators
|
| 31 |
+
from agentic_ai_system.finrl_agent import FinRLAgent, FinRLConfig
|
| 32 |
+
from agentic_ai_system.alpaca_broker import AlpacaBroker
|
| 33 |
+
from agentic_ai_system.orchestrator import run_backtest, run_live_trading
|
| 34 |
+
|
| 35 |
+
class TradingJupyterUI:
|
| 36 |
+
def __init__(self):
|
| 37 |
+
self.config = None
|
| 38 |
+
self.data = None
|
| 39 |
+
self.alpaca_broker = None
|
| 40 |
+
self.finrl_agent = None
|
| 41 |
+
self.trading_active = False
|
| 42 |
+
|
| 43 |
+
self.setup_widgets()
|
| 44 |
+
|
| 45 |
+
def setup_widgets(self):
|
| 46 |
+
"""Setup all interactive widgets"""
|
| 47 |
+
|
| 48 |
+
# Configuration widgets
|
| 49 |
+
self.config_file = widgets.Text(
|
| 50 |
+
value='config.yaml',
|
| 51 |
+
description='Config File:',
|
| 52 |
+
style={'description_width': '120px'}
|
| 53 |
+
)
|
| 54 |
+
|
| 55 |
+
self.load_config_btn = widgets.Button(
|
| 56 |
+
description='Load Configuration',
|
| 57 |
+
button_style='primary',
|
| 58 |
+
icon='cog'
|
| 59 |
+
)
|
| 60 |
+
|
| 61 |
+
self.config_output = widgets.Output()
|
| 62 |
+
|
| 63 |
+
# Data widgets
|
| 64 |
+
self.data_source = widgets.Dropdown(
|
| 65 |
+
options=['csv', 'alpaca', 'synthetic'],
|
| 66 |
+
value='csv',
|
| 67 |
+
description='Data Source:',
|
| 68 |
+
style={'description_width': '120px'}
|
| 69 |
+
)
|
| 70 |
+
|
| 71 |
+
self.symbol_input = widgets.Text(
|
| 72 |
+
value='AAPL',
|
| 73 |
+
description='Symbol:',
|
| 74 |
+
style={'description_width': '120px'}
|
| 75 |
+
)
|
| 76 |
+
|
| 77 |
+
self.timeframe_input = widgets.Dropdown(
|
| 78 |
+
options=['1m', '5m', '15m', '1h', '1d'],
|
| 79 |
+
value='1m',
|
| 80 |
+
description='Timeframe:',
|
| 81 |
+
style={'description_width': '120px'}
|
| 82 |
+
)
|
| 83 |
+
|
| 84 |
+
self.load_data_btn = widgets.Button(
|
| 85 |
+
description='Load Data',
|
| 86 |
+
button_style='success',
|
| 87 |
+
icon='database'
|
| 88 |
+
)
|
| 89 |
+
|
| 90 |
+
self.data_output = widgets.Output()
|
| 91 |
+
|
| 92 |
+
# Alpaca widgets
|
| 93 |
+
self.alpaca_api_key = widgets.Password(
|
| 94 |
+
description='API Key:',
|
| 95 |
+
style={'description_width': '120px'}
|
| 96 |
+
)
|
| 97 |
+
|
| 98 |
+
self.alpaca_secret_key = widgets.Password(
|
| 99 |
+
description='Secret Key:',
|
| 100 |
+
style={'description_width': '120px'}
|
| 101 |
+
)
|
| 102 |
+
|
| 103 |
+
self.connect_alpaca_btn = widgets.Button(
|
| 104 |
+
description='Connect to Alpaca',
|
| 105 |
+
button_style='info',
|
| 106 |
+
icon='link'
|
| 107 |
+
)
|
| 108 |
+
|
| 109 |
+
self.alpaca_output = widgets.Output()
|
| 110 |
+
|
| 111 |
+
# FinRL widgets
|
| 112 |
+
self.finrl_algorithm = widgets.Dropdown(
|
| 113 |
+
options=['PPO', 'A2C', 'DDPG', 'TD3'],
|
| 114 |
+
value='PPO',
|
| 115 |
+
description='Algorithm:',
|
| 116 |
+
style={'description_width': '120px'}
|
| 117 |
+
)
|
| 118 |
+
|
| 119 |
+
self.learning_rate = widgets.FloatSlider(
|
| 120 |
+
value=0.0003,
|
| 121 |
+
min=0.0001,
|
| 122 |
+
max=0.01,
|
| 123 |
+
step=0.0001,
|
| 124 |
+
description='Learning Rate:',
|
| 125 |
+
style={'description_width': '120px'},
|
| 126 |
+
readout_format='.4f'
|
| 127 |
+
)
|
| 128 |
+
|
| 129 |
+
self.training_steps = widgets.IntSlider(
|
| 130 |
+
value=100000,
|
| 131 |
+
min=1000,
|
| 132 |
+
max=1000000,
|
| 133 |
+
step=1000,
|
| 134 |
+
description='Training Steps:',
|
| 135 |
+
style={'description_width': '120px'}
|
| 136 |
+
)
|
| 137 |
+
|
| 138 |
+
self.batch_size = widgets.Dropdown(
|
| 139 |
+
options=[32, 64, 128, 256],
|
| 140 |
+
value=64,
|
| 141 |
+
description='Batch Size:',
|
| 142 |
+
style={'description_width': '120px'}
|
| 143 |
+
)
|
| 144 |
+
|
| 145 |
+
self.start_training_btn = widgets.Button(
|
| 146 |
+
description='Start Training',
|
| 147 |
+
button_style='warning',
|
| 148 |
+
icon='play'
|
| 149 |
+
)
|
| 150 |
+
|
| 151 |
+
self.finrl_output = widgets.Output()
|
| 152 |
+
|
| 153 |
+
# Trading widgets
|
| 154 |
+
self.capital_input = widgets.IntText(
|
| 155 |
+
value=100000,
|
| 156 |
+
description='Capital ($):',
|
| 157 |
+
style={'description_width': '120px'}
|
| 158 |
+
)
|
| 159 |
+
|
| 160 |
+
self.order_size_input = widgets.IntText(
|
| 161 |
+
value=10,
|
| 162 |
+
description='Order Size:',
|
| 163 |
+
style={'description_width': '120px'}
|
| 164 |
+
)
|
| 165 |
+
|
| 166 |
+
self.start_trading_btn = widgets.Button(
|
| 167 |
+
description='Start Trading',
|
| 168 |
+
button_style='danger',
|
| 169 |
+
icon='rocket'
|
| 170 |
+
)
|
| 171 |
+
|
| 172 |
+
self.stop_trading_btn = widgets.Button(
|
| 173 |
+
description='Stop Trading',
|
| 174 |
+
button_style='danger',
|
| 175 |
+
icon='stop'
|
| 176 |
+
)
|
| 177 |
+
|
| 178 |
+
self.trading_output = widgets.Output()
|
| 179 |
+
|
| 180 |
+
# Backtesting widgets
|
| 181 |
+
self.run_backtest_btn = widgets.Button(
|
| 182 |
+
description='Run Backtest',
|
| 183 |
+
button_style='primary',
|
| 184 |
+
icon='chart-line'
|
| 185 |
+
)
|
| 186 |
+
|
| 187 |
+
self.backtest_output = widgets.Output()
|
| 188 |
+
|
| 189 |
+
# Chart widgets
|
| 190 |
+
self.chart_type = widgets.Dropdown(
|
| 191 |
+
options=['Candlestick', 'Line', 'Volume', 'Technical Indicators'],
|
| 192 |
+
value='Candlestick',
|
| 193 |
+
description='Chart Type:',
|
| 194 |
+
style={'description_width': '120px'}
|
| 195 |
+
)
|
| 196 |
+
|
| 197 |
+
self.chart_output = widgets.Output()
|
| 198 |
+
|
| 199 |
+
# Setup callbacks
|
| 200 |
+
self.load_config_btn.on_click(self.on_load_config)
|
| 201 |
+
self.load_data_btn.on_click(self.on_load_data)
|
| 202 |
+
self.connect_alpaca_btn.on_click(self.on_connect_alpaca)
|
| 203 |
+
self.start_training_btn.on_click(self.on_start_training)
|
| 204 |
+
self.start_trading_btn.on_click(self.on_start_trading)
|
| 205 |
+
self.stop_trading_btn.on_click(self.on_stop_trading)
|
| 206 |
+
self.run_backtest_btn.on_click(self.on_run_backtest)
|
| 207 |
+
self.chart_type.observe(self.on_chart_type_change, names='value')
|
| 208 |
+
|
| 209 |
+
def on_load_config(self, b):
|
| 210 |
+
"""Handle configuration loading"""
|
| 211 |
+
with self.config_output:
|
| 212 |
+
clear_output()
|
| 213 |
+
try:
|
| 214 |
+
self.config = load_config(self.config_file.value)
|
| 215 |
+
print(f"β
Configuration loaded from {self.config_file.value}")
|
| 216 |
+
print(f"Symbol: {self.config['trading']['symbol']}")
|
| 217 |
+
print(f"Capital: ${self.config['trading']['capital']:,}")
|
| 218 |
+
print(f"Timeframe: {self.config['trading']['timeframe']}")
|
| 219 |
+
print(f"Broker: {self.config['execution']['broker_api']}")
|
| 220 |
+
except Exception as e:
|
| 221 |
+
print(f"β Error loading configuration: {e}")
|
| 222 |
+
|
| 223 |
+
def on_load_data(self, b):
|
| 224 |
+
"""Handle data loading"""
|
| 225 |
+
with self.data_output:
|
| 226 |
+
clear_output()
|
| 227 |
+
try:
|
| 228 |
+
if self.config:
|
| 229 |
+
# Update config with widget values
|
| 230 |
+
self.config['data_source']['type'] = self.data_source.value
|
| 231 |
+
self.config['trading']['symbol'] = self.symbol_input.value
|
| 232 |
+
self.config['trading']['timeframe'] = self.timeframe_input.value
|
| 233 |
+
|
| 234 |
+
print(f"Loading data for {self.symbol_input.value}...")
|
| 235 |
+
self.data = load_data(self.config)
|
| 236 |
+
|
| 237 |
+
if self.data is not None and not self.data.empty:
|
| 238 |
+
print(f"β
Loaded {len(self.data)} data points")
|
| 239 |
+
print(f"Date range: {self.data['timestamp'].min()} to {self.data['timestamp'].max()}")
|
| 240 |
+
print(f"Price range: ${self.data['close'].min():.2f} - ${self.data['close'].max():.2f}")
|
| 241 |
+
|
| 242 |
+
# Add technical indicators
|
| 243 |
+
self.data = add_technical_indicators(self.data)
|
| 244 |
+
print(f"β
Added technical indicators")
|
| 245 |
+
|
| 246 |
+
# Update chart
|
| 247 |
+
self.update_chart()
|
| 248 |
+
else:
|
| 249 |
+
print("β Failed to load data")
|
| 250 |
+
else:
|
| 251 |
+
print("β οΈ Please load configuration first")
|
| 252 |
+
except Exception as e:
|
| 253 |
+
print(f"β Error loading data: {e}")
|
| 254 |
+
|
| 255 |
+
def on_connect_alpaca(self, b):
|
| 256 |
+
"""Handle Alpaca connection"""
|
| 257 |
+
with self.alpaca_output:
|
| 258 |
+
clear_output()
|
| 259 |
+
try:
|
| 260 |
+
if self.alpaca_api_key.value and self.alpaca_secret_key.value:
|
| 261 |
+
# Update config with API keys
|
| 262 |
+
if self.config:
|
| 263 |
+
self.config['alpaca']['api_key'] = self.alpaca_api_key.value
|
| 264 |
+
self.config['alpaca']['secret_key'] = self.alpaca_secret_key.value
|
| 265 |
+
self.config['execution']['broker_api'] = 'alpaca_paper'
|
| 266 |
+
|
| 267 |
+
print("Connecting to Alpaca...")
|
| 268 |
+
self.alpaca_broker = AlpacaBroker(self.config)
|
| 269 |
+
|
| 270 |
+
account_info = self.alpaca_broker.get_account_info()
|
| 271 |
+
if account_info:
|
| 272 |
+
print("β
Connected to Alpaca")
|
| 273 |
+
print(f"Account ID: {account_info['account_id']}")
|
| 274 |
+
print(f"Status: {account_info['status']}")
|
| 275 |
+
print(f"Buying Power: ${account_info['buying_power']:,.2f}")
|
| 276 |
+
print(f"Portfolio Value: ${account_info['portfolio_value']:,.2f}")
|
| 277 |
+
else:
|
| 278 |
+
print("β Failed to connect to Alpaca")
|
| 279 |
+
else:
|
| 280 |
+
print("β οΈ Please load configuration first")
|
| 281 |
+
else:
|
| 282 |
+
print("β οΈ Please enter Alpaca API credentials")
|
| 283 |
+
except Exception as e:
|
| 284 |
+
print(f"β Error connecting to Alpaca: {e}")
|
| 285 |
+
|
| 286 |
+
def on_start_training(self, b):
|
| 287 |
+
"""Handle FinRL training"""
|
| 288 |
+
with self.finrl_output:
|
| 289 |
+
clear_output()
|
| 290 |
+
try:
|
| 291 |
+
if self.data is not None:
|
| 292 |
+
print("Starting FinRL training...")
|
| 293 |
+
|
| 294 |
+
# Create FinRL config
|
| 295 |
+
finrl_config = FinRLConfig(
|
| 296 |
+
algorithm=self.finrl_algorithm.value,
|
| 297 |
+
learning_rate=self.learning_rate.value,
|
| 298 |
+
batch_size=self.batch_size.value,
|
| 299 |
+
buffer_size=1000000,
|
| 300 |
+
learning_starts=100,
|
| 301 |
+
gamma=0.99,
|
| 302 |
+
tau=0.005,
|
| 303 |
+
train_freq=1,
|
| 304 |
+
gradient_steps=1,
|
| 305 |
+
verbose=1,
|
| 306 |
+
tensorboard_log='logs/finrl_tensorboard'
|
| 307 |
+
)
|
| 308 |
+
|
| 309 |
+
# Initialize agent
|
| 310 |
+
self.finrl_agent = FinRLAgent(finrl_config)
|
| 311 |
+
|
| 312 |
+
# Train the agent
|
| 313 |
+
result = self.finrl_agent.train(
|
| 314 |
+
data=self.data,
|
| 315 |
+
config=self.config,
|
| 316 |
+
total_timesteps=self.training_steps.value,
|
| 317 |
+
use_real_broker=False
|
| 318 |
+
)
|
| 319 |
+
|
| 320 |
+
if result['success']:
|
| 321 |
+
print("β
Training completed successfully!")
|
| 322 |
+
print(f"Algorithm: {result['algorithm']}")
|
| 323 |
+
print(f"Timesteps: {result['total_timesteps']:,}")
|
| 324 |
+
print(f"Model saved: {result['model_path']}")
|
| 325 |
+
else:
|
| 326 |
+
print("β Training failed")
|
| 327 |
+
else:
|
| 328 |
+
print("β οΈ Please load data first")
|
| 329 |
+
except Exception as e:
|
| 330 |
+
print(f"β Error during training: {e}")
|
| 331 |
+
|
| 332 |
+
def on_start_trading(self, b):
|
| 333 |
+
"""Handle trading start"""
|
| 334 |
+
with self.trading_output:
|
| 335 |
+
clear_output()
|
| 336 |
+
try:
|
| 337 |
+
if self.config and self.alpaca_broker:
|
| 338 |
+
print("Starting live trading...")
|
| 339 |
+
self.trading_active = True
|
| 340 |
+
|
| 341 |
+
# Update config with widget values
|
| 342 |
+
self.config['trading']['capital'] = self.capital_input.value
|
| 343 |
+
self.config['execution']['order_size'] = self.order_size_input.value
|
| 344 |
+
|
| 345 |
+
# Start trading in background thread
|
| 346 |
+
def run_trading():
|
| 347 |
+
try:
|
| 348 |
+
run_live_trading(self.config, self.data)
|
| 349 |
+
except Exception as e:
|
| 350 |
+
print(f"Trading error: {e}")
|
| 351 |
+
|
| 352 |
+
trading_thread = threading.Thread(target=run_trading)
|
| 353 |
+
trading_thread.daemon = True
|
| 354 |
+
trading_thread.start()
|
| 355 |
+
|
| 356 |
+
print("β
Live trading started")
|
| 357 |
+
else:
|
| 358 |
+
print("β οΈ Please load configuration and connect to Alpaca first")
|
| 359 |
+
except Exception as e:
|
| 360 |
+
print(f"β Error starting trading: {e}")
|
| 361 |
+
|
| 362 |
+
def on_stop_trading(self, b):
|
| 363 |
+
"""Handle trading stop"""
|
| 364 |
+
with self.trading_output:
|
| 365 |
+
clear_output()
|
| 366 |
+
self.trading_active = False
|
| 367 |
+
print("β
Trading stopped")
|
| 368 |
+
|
| 369 |
+
def on_run_backtest(self, b):
|
| 370 |
+
"""Handle backtesting"""
|
| 371 |
+
with self.backtest_output:
|
| 372 |
+
clear_output()
|
| 373 |
+
try:
|
| 374 |
+
if self.config and self.data is not None:
|
| 375 |
+
print("Running backtest...")
|
| 376 |
+
|
| 377 |
+
# Update config with widget values
|
| 378 |
+
self.config['trading']['capital'] = self.capital_input.value
|
| 379 |
+
|
| 380 |
+
result = run_backtest(self.config, self.data)
|
| 381 |
+
|
| 382 |
+
if result['success']:
|
| 383 |
+
print("β
Backtest completed")
|
| 384 |
+
print(f"Total Return: {result['total_return']:.2%}")
|
| 385 |
+
print(f"Sharpe Ratio: {result['sharpe_ratio']:.2f}")
|
| 386 |
+
print(f"Max Drawdown: {result['max_drawdown']:.2%}")
|
| 387 |
+
print(f"Total Trades: {result['total_trades']}")
|
| 388 |
+
else:
|
| 389 |
+
print("β Backtest failed")
|
| 390 |
+
else:
|
| 391 |
+
print("β οΈ Please load configuration and data first")
|
| 392 |
+
except Exception as e:
|
| 393 |
+
print(f"β Error during backtest: {e}")
|
| 394 |
+
|
| 395 |
+
def on_chart_type_change(self, change):
|
| 396 |
+
"""Handle chart type change"""
|
| 397 |
+
if self.data is not None:
|
| 398 |
+
self.update_chart()
|
| 399 |
+
|
| 400 |
+
def update_chart(self):
|
| 401 |
+
"""Update the chart display"""
|
| 402 |
+
with self.chart_output:
|
| 403 |
+
clear_output()
|
| 404 |
+
|
| 405 |
+
if self.data is None:
|
| 406 |
+
return
|
| 407 |
+
|
| 408 |
+
if self.chart_type.value == "Candlestick":
|
| 409 |
+
fig = go.Figure(data=[go.Candlestick(
|
| 410 |
+
x=self.data['timestamp'],
|
| 411 |
+
open=self.data['open'],
|
| 412 |
+
high=self.data['high'],
|
| 413 |
+
low=self.data['low'],
|
| 414 |
+
close=self.data['close']
|
| 415 |
+
)])
|
| 416 |
+
fig.update_layout(
|
| 417 |
+
title=f"{self.config['trading']['symbol']} Candlestick Chart",
|
| 418 |
+
xaxis_title="Date",
|
| 419 |
+
yaxis_title="Price ($)",
|
| 420 |
+
height=500
|
| 421 |
+
)
|
| 422 |
+
display(fig)
|
| 423 |
+
|
| 424 |
+
elif self.chart_type.value == "Line":
|
| 425 |
+
fig = px.line(self.data, x='timestamp', y='close',
|
| 426 |
+
title=f"{self.config['trading']['symbol']} Price Chart")
|
| 427 |
+
fig.update_layout(height=500)
|
| 428 |
+
display(fig)
|
| 429 |
+
|
| 430 |
+
elif self.chart_type.value == "Volume":
|
| 431 |
+
fig = go.Figure()
|
| 432 |
+
fig.add_trace(go.Bar(
|
| 433 |
+
x=self.data['timestamp'],
|
| 434 |
+
y=self.data['volume'],
|
| 435 |
+
name='Volume'
|
| 436 |
+
))
|
| 437 |
+
fig.update_layout(
|
| 438 |
+
title=f"{self.config['trading']['symbol']} Volume Chart",
|
| 439 |
+
xaxis_title="Date",
|
| 440 |
+
yaxis_title="Volume",
|
| 441 |
+
height=500
|
| 442 |
+
)
|
| 443 |
+
display(fig)
|
| 444 |
+
|
| 445 |
+
elif self.chart_type.value == "Technical Indicators":
|
| 446 |
+
fig = go.Figure()
|
| 447 |
+
|
| 448 |
+
# Add price
|
| 449 |
+
fig.add_trace(go.Scatter(
|
| 450 |
+
x=self.data['timestamp'],
|
| 451 |
+
y=self.data['close'],
|
| 452 |
+
name='Close Price',
|
| 453 |
+
line=dict(color='blue')
|
| 454 |
+
))
|
| 455 |
+
|
| 456 |
+
# Add moving averages if available
|
| 457 |
+
if 'sma_20' in self.data.columns:
|
| 458 |
+
fig.add_trace(go.Scatter(
|
| 459 |
+
x=self.data['timestamp'],
|
| 460 |
+
y=self.data['sma_20'],
|
| 461 |
+
name='SMA 20',
|
| 462 |
+
line=dict(color='orange')
|
| 463 |
+
))
|
| 464 |
+
|
| 465 |
+
if 'sma_50' in self.data.columns:
|
| 466 |
+
fig.add_trace(go.Scatter(
|
| 467 |
+
x=self.data['timestamp'],
|
| 468 |
+
y=self.data['sma_50'],
|
| 469 |
+
name='SMA 50',
|
| 470 |
+
line=dict(color='red')
|
| 471 |
+
))
|
| 472 |
+
|
| 473 |
+
fig.update_layout(
|
| 474 |
+
title=f"{self.config['trading']['symbol']} Technical Indicators",
|
| 475 |
+
xaxis_title="Date",
|
| 476 |
+
yaxis_title="Price ($)",
|
| 477 |
+
height=500
|
| 478 |
+
)
|
| 479 |
+
display(fig)
|
| 480 |
+
|
| 481 |
+
def display_interface(self):
|
| 482 |
+
"""Display the complete Jupyter interface"""
|
| 483 |
+
|
| 484 |
+
# Header
|
| 485 |
+
display(HTML("""
|
| 486 |
+
<div style="text-align: center; margin-bottom: 20px;">
|
| 487 |
+
<h1>π€ Algorithmic Trading System</h1>
|
| 488 |
+
<p>Interactive Jupyter Interface for Trading Analysis</p>
|
| 489 |
+
</div>
|
| 490 |
+
"""))
|
| 491 |
+
|
| 492 |
+
# Configuration section
|
| 493 |
+
display(HTML("<h2>βοΈ Configuration</h2>"))
|
| 494 |
+
config_widgets = widgets.VBox([
|
| 495 |
+
widgets.HBox([self.config_file, self.load_config_btn]),
|
| 496 |
+
self.config_output
|
| 497 |
+
])
|
| 498 |
+
display(config_widgets)
|
| 499 |
+
|
| 500 |
+
# Data section
|
| 501 |
+
display(HTML("<h2>π₯ Data Management</h2>"))
|
| 502 |
+
data_widgets = widgets.VBox([
|
| 503 |
+
widgets.HBox([self.data_source, self.symbol_input, self.timeframe_input]),
|
| 504 |
+
widgets.HBox([self.load_data_btn]),
|
| 505 |
+
self.data_output
|
| 506 |
+
])
|
| 507 |
+
display(data_widgets)
|
| 508 |
+
|
| 509 |
+
# Alpaca section
|
| 510 |
+
display(HTML("<h2>π¦ Alpaca Integration</h2>"))
|
| 511 |
+
alpaca_widgets = widgets.VBox([
|
| 512 |
+
widgets.HBox([self.alpaca_api_key, self.alpaca_secret_key]),
|
| 513 |
+
widgets.HBox([self.connect_alpaca_btn]),
|
| 514 |
+
self.alpaca_output
|
| 515 |
+
])
|
| 516 |
+
display(alpaca_widgets)
|
| 517 |
+
|
| 518 |
+
# FinRL section
|
| 519 |
+
display(HTML("<h2>π§ FinRL Training</h2>"))
|
| 520 |
+
finrl_widgets = widgets.VBox([
|
| 521 |
+
widgets.HBox([self.finrl_algorithm, self.learning_rate]),
|
| 522 |
+
widgets.HBox([self.training_steps, self.batch_size]),
|
| 523 |
+
widgets.HBox([self.start_training_btn]),
|
| 524 |
+
self.finrl_output
|
| 525 |
+
])
|
| 526 |
+
display(finrl_widgets)
|
| 527 |
+
|
| 528 |
+
# Trading section
|
| 529 |
+
display(HTML("<h2>π― Trading Controls</h2>"))
|
| 530 |
+
trading_widgets = widgets.VBox([
|
| 531 |
+
widgets.HBox([self.capital_input, self.order_size_input]),
|
| 532 |
+
widgets.HBox([self.start_trading_btn, self.stop_trading_btn]),
|
| 533 |
+
self.trading_output
|
| 534 |
+
])
|
| 535 |
+
display(trading_widgets)
|
| 536 |
+
|
| 537 |
+
# Backtesting section
|
| 538 |
+
display(HTML("<h2>π Backtesting</h2>"))
|
| 539 |
+
backtest_widgets = widgets.VBox([
|
| 540 |
+
widgets.HBox([self.run_backtest_btn]),
|
| 541 |
+
self.backtest_output
|
| 542 |
+
])
|
| 543 |
+
display(backtest_widgets)
|
| 544 |
+
|
| 545 |
+
# Chart section
|
| 546 |
+
display(HTML("<h2>π Data Visualization</h2>"))
|
| 547 |
+
chart_widgets = widgets.VBox([
|
| 548 |
+
widgets.HBox([self.chart_type]),
|
| 549 |
+
self.chart_output
|
| 550 |
+
])
|
| 551 |
+
display(chart_widgets)
|
| 552 |
+
|
| 553 |
+
def create_jupyter_interface():
|
| 554 |
+
"""Create and return the Jupyter interface"""
|
| 555 |
+
ui = TradingJupyterUI()
|
| 556 |
+
return ui
|
ui/streamlit_app.py
ADDED
|
@@ -0,0 +1,679 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Streamlit UI for Algorithmic Trading System
|
| 3 |
+
|
| 4 |
+
A comprehensive web interface for:
|
| 5 |
+
- Real-time market data visualization
|
| 6 |
+
- Trading strategy configuration
|
| 7 |
+
- FinRL model training and evaluation
|
| 8 |
+
- Portfolio management
|
| 9 |
+
- Risk monitoring
|
| 10 |
+
"""
|
| 11 |
+
|
| 12 |
+
import streamlit as st
|
| 13 |
+
import plotly.graph_objects as go
|
| 14 |
+
import plotly.express as px
|
| 15 |
+
import pandas as pd
|
| 16 |
+
import numpy as np
|
| 17 |
+
import yaml
|
| 18 |
+
import os
|
| 19 |
+
import sys
|
| 20 |
+
from datetime import datetime, timedelta
|
| 21 |
+
from typing import Dict, Any, Optional
|
| 22 |
+
import asyncio
|
| 23 |
+
import threading
|
| 24 |
+
import time
|
| 25 |
+
|
| 26 |
+
# Add project root to path
|
| 27 |
+
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
| 28 |
+
|
| 29 |
+
from agentic_ai_system.main import load_config
|
| 30 |
+
from agentic_ai_system.data_ingestion import load_data, validate_data, add_technical_indicators
|
| 31 |
+
from agentic_ai_system.finrl_agent import FinRLAgent, FinRLConfig
|
| 32 |
+
from agentic_ai_system.alpaca_broker import AlpacaBroker
|
| 33 |
+
from agentic_ai_system.orchestrator import run_backtest, run_live_trading
|
| 34 |
+
|
| 35 |
+
# Page configuration
|
| 36 |
+
st.set_page_config(
|
| 37 |
+
page_title="Algorithmic Trading System",
|
| 38 |
+
page_icon="π",
|
| 39 |
+
layout="wide",
|
| 40 |
+
initial_sidebar_state="expanded"
|
| 41 |
+
)
|
| 42 |
+
|
| 43 |
+
# Custom CSS for better styling
|
| 44 |
+
st.markdown("""
|
| 45 |
+
<style>
|
| 46 |
+
.main-header {
|
| 47 |
+
font-size: 2.5rem;
|
| 48 |
+
font-weight: bold;
|
| 49 |
+
color: #1f77b4;
|
| 50 |
+
text-align: center;
|
| 51 |
+
margin-bottom: 2rem;
|
| 52 |
+
}
|
| 53 |
+
.metric-card {
|
| 54 |
+
background-color: #f0f2f6;
|
| 55 |
+
padding: 1rem;
|
| 56 |
+
border-radius: 0.5rem;
|
| 57 |
+
border-left: 4px solid #1f77b4;
|
| 58 |
+
}
|
| 59 |
+
.success-metric {
|
| 60 |
+
border-left-color: #28a745;
|
| 61 |
+
}
|
| 62 |
+
.warning-metric {
|
| 63 |
+
border-left-color: #ffc107;
|
| 64 |
+
}
|
| 65 |
+
.danger-metric {
|
| 66 |
+
border-left-color: #dc3545;
|
| 67 |
+
}
|
| 68 |
+
.sidebar .sidebar-content {
|
| 69 |
+
background-color: #f8f9fa;
|
| 70 |
+
}
|
| 71 |
+
</style>
|
| 72 |
+
""", unsafe_allow_html=True)
|
| 73 |
+
|
| 74 |
+
class TradingUI:
|
| 75 |
+
def __init__(self):
|
| 76 |
+
self.config = None
|
| 77 |
+
self.data = None
|
| 78 |
+
self.alpaca_broker = None
|
| 79 |
+
self.finrl_agent = None
|
| 80 |
+
self.session_state = st.session_state
|
| 81 |
+
|
| 82 |
+
# Initialize session state
|
| 83 |
+
if 'trading_active' not in self.session_state:
|
| 84 |
+
self.session_state.trading_active = False
|
| 85 |
+
if 'current_portfolio' not in self.session_state:
|
| 86 |
+
self.session_state.current_portfolio = {}
|
| 87 |
+
if 'trading_history' not in self.session_state:
|
| 88 |
+
self.session_state.trading_history = []
|
| 89 |
+
|
| 90 |
+
def load_configuration(self):
|
| 91 |
+
"""Load and display configuration"""
|
| 92 |
+
st.sidebar.header("βοΈ Configuration")
|
| 93 |
+
|
| 94 |
+
# Config file selector
|
| 95 |
+
config_files = [f for f in os.listdir('.') if f.endswith('.yaml') or f.endswith('.yml')]
|
| 96 |
+
selected_config = st.sidebar.selectbox(
|
| 97 |
+
"Select Configuration File",
|
| 98 |
+
config_files,
|
| 99 |
+
index=0 if 'config.yaml' in config_files else 0
|
| 100 |
+
)
|
| 101 |
+
|
| 102 |
+
if st.sidebar.button("Load Configuration"):
|
| 103 |
+
try:
|
| 104 |
+
self.config = load_config(selected_config)
|
| 105 |
+
st.sidebar.success(f"β
Configuration loaded: {selected_config}")
|
| 106 |
+
return True
|
| 107 |
+
except Exception as e:
|
| 108 |
+
st.sidebar.error(f"β Error loading config: {e}")
|
| 109 |
+
return False
|
| 110 |
+
|
| 111 |
+
return False
|
| 112 |
+
|
| 113 |
+
def display_system_status(self):
|
| 114 |
+
"""Display system status and metrics"""
|
| 115 |
+
st.header("π System Status")
|
| 116 |
+
|
| 117 |
+
col1, col2, col3, col4 = st.columns(4)
|
| 118 |
+
|
| 119 |
+
with col1:
|
| 120 |
+
st.metric(
|
| 121 |
+
label="Trading Status",
|
| 122 |
+
value="π’ Active" if self.session_state.trading_active else "π΄ Inactive",
|
| 123 |
+
delta="Running" if self.session_state.trading_active else "Stopped"
|
| 124 |
+
)
|
| 125 |
+
|
| 126 |
+
with col2:
|
| 127 |
+
if self.config:
|
| 128 |
+
st.metric(
|
| 129 |
+
label="Capital",
|
| 130 |
+
value=f"${self.config['trading']['capital']:,}",
|
| 131 |
+
delta="Available"
|
| 132 |
+
)
|
| 133 |
+
else:
|
| 134 |
+
st.metric(label="Capital", value="Not Loaded")
|
| 135 |
+
|
| 136 |
+
with col3:
|
| 137 |
+
if self.alpaca_broker:
|
| 138 |
+
try:
|
| 139 |
+
account_info = self.alpaca_broker.get_account_info()
|
| 140 |
+
if account_info:
|
| 141 |
+
st.metric(
|
| 142 |
+
label="Portfolio Value",
|
| 143 |
+
value=f"${float(account_info['portfolio_value']):,.2f}",
|
| 144 |
+
delta=f"{float(account_info['equity']) - float(account_info['portfolio_value']):,.2f}"
|
| 145 |
+
)
|
| 146 |
+
except:
|
| 147 |
+
st.metric(label="Portfolio Value", value="Not Connected")
|
| 148 |
+
else:
|
| 149 |
+
st.metric(label="Portfolio Value", value="Not Connected")
|
| 150 |
+
|
| 151 |
+
with col4:
|
| 152 |
+
if self.data is not None:
|
| 153 |
+
st.metric(
|
| 154 |
+
label="Data Points",
|
| 155 |
+
value=f"{len(self.data):,}",
|
| 156 |
+
delta=f"Latest: {self.data['timestamp'].max().strftime('%Y-%m-%d')}"
|
| 157 |
+
)
|
| 158 |
+
else:
|
| 159 |
+
st.metric(label="Data Points", value="Not Loaded")
|
| 160 |
+
|
| 161 |
+
def data_ingestion_panel(self):
|
| 162 |
+
"""Data ingestion and visualization panel"""
|
| 163 |
+
st.header("π₯ Data Ingestion")
|
| 164 |
+
|
| 165 |
+
col1, col2 = st.columns([2, 1])
|
| 166 |
+
|
| 167 |
+
with col1:
|
| 168 |
+
if self.config:
|
| 169 |
+
if st.button("Load Market Data"):
|
| 170 |
+
with st.spinner("Loading data..."):
|
| 171 |
+
try:
|
| 172 |
+
self.data = load_data(self.config)
|
| 173 |
+
if self.data is not None and not self.data.empty:
|
| 174 |
+
st.success(f"β
Loaded {len(self.data)} data points")
|
| 175 |
+
|
| 176 |
+
# Add technical indicators
|
| 177 |
+
self.data = add_technical_indicators(self.data)
|
| 178 |
+
st.info(f"β
Added technical indicators")
|
| 179 |
+
else:
|
| 180 |
+
st.error("β Failed to load data")
|
| 181 |
+
except Exception as e:
|
| 182 |
+
st.error(f"β Error loading data: {e}")
|
| 183 |
+
|
| 184 |
+
with col2:
|
| 185 |
+
if self.data is not None:
|
| 186 |
+
st.subheader("Data Summary")
|
| 187 |
+
st.write(f"**Symbol:** {self.config['trading']['symbol']}")
|
| 188 |
+
st.write(f"**Timeframe:** {self.config['trading']['timeframe']}")
|
| 189 |
+
st.write(f"**Date Range:** {self.data['timestamp'].min().strftime('%Y-%m-%d')} to {self.data['timestamp'].max().strftime('%Y-%m-%d')}")
|
| 190 |
+
st.write(f"**Price Range:** ${self.data['close'].min():.2f} - ${self.data['close'].max():.2f}")
|
| 191 |
+
|
| 192 |
+
# Data visualization
|
| 193 |
+
if self.data is not None:
|
| 194 |
+
st.subheader("π Market Data Visualization")
|
| 195 |
+
|
| 196 |
+
# Chart type selector
|
| 197 |
+
chart_type = st.selectbox(
|
| 198 |
+
"Chart Type",
|
| 199 |
+
["Candlestick", "Line", "OHLC", "Volume"]
|
| 200 |
+
)
|
| 201 |
+
|
| 202 |
+
if chart_type == "Candlestick":
|
| 203 |
+
fig = go.Figure(data=[go.Candlestick(
|
| 204 |
+
x=self.data['timestamp'],
|
| 205 |
+
open=self.data['open'],
|
| 206 |
+
high=self.data['high'],
|
| 207 |
+
low=self.data['low'],
|
| 208 |
+
close=self.data['close']
|
| 209 |
+
)])
|
| 210 |
+
fig.update_layout(
|
| 211 |
+
title=f"{self.config['trading']['symbol']} Candlestick Chart",
|
| 212 |
+
xaxis_title="Date",
|
| 213 |
+
yaxis_title="Price ($)",
|
| 214 |
+
height=500
|
| 215 |
+
)
|
| 216 |
+
st.plotly_chart(fig, use_container_width=True)
|
| 217 |
+
|
| 218 |
+
elif chart_type == "Line":
|
| 219 |
+
fig = px.line(self.data, x='timestamp', y='close',
|
| 220 |
+
title=f"{self.config['trading']['symbol']} Price Chart")
|
| 221 |
+
fig.update_layout(height=500)
|
| 222 |
+
st.plotly_chart(fig, use_container_width=True)
|
| 223 |
+
|
| 224 |
+
elif chart_type == "Volume":
|
| 225 |
+
fig = go.Figure()
|
| 226 |
+
fig.add_trace(go.Bar(
|
| 227 |
+
x=self.data['timestamp'],
|
| 228 |
+
y=self.data['volume'],
|
| 229 |
+
name='Volume'
|
| 230 |
+
))
|
| 231 |
+
fig.update_layout(
|
| 232 |
+
title=f"{self.config['trading']['symbol']} Volume Chart",
|
| 233 |
+
xaxis_title="Date",
|
| 234 |
+
yaxis_title="Volume",
|
| 235 |
+
height=500
|
| 236 |
+
)
|
| 237 |
+
st.plotly_chart(fig, use_container_width=True)
|
| 238 |
+
|
| 239 |
+
def alpaca_integration_panel(self):
|
| 240 |
+
"""Alpaca broker integration panel"""
|
| 241 |
+
st.header("π¦ Alpaca Integration")
|
| 242 |
+
|
| 243 |
+
col1, col2 = st.columns([1, 1])
|
| 244 |
+
|
| 245 |
+
with col1:
|
| 246 |
+
if st.button("Connect to Alpaca"):
|
| 247 |
+
if self.config and self.config['execution']['broker_api'] in ['alpaca_paper', 'alpaca_live']:
|
| 248 |
+
with st.spinner("Connecting to Alpaca..."):
|
| 249 |
+
try:
|
| 250 |
+
self.alpaca_broker = AlpacaBroker(self.config)
|
| 251 |
+
account_info = self.alpaca_broker.get_account_info()
|
| 252 |
+
if account_info:
|
| 253 |
+
st.success("β
Connected to Alpaca")
|
| 254 |
+
self.session_state.alpaca_connected = True
|
| 255 |
+
else:
|
| 256 |
+
st.error("β Failed to connect to Alpaca")
|
| 257 |
+
except Exception as e:
|
| 258 |
+
st.error(f"β Connection error: {e}")
|
| 259 |
+
else:
|
| 260 |
+
st.warning("β οΈ Alpaca not configured in settings")
|
| 261 |
+
|
| 262 |
+
with col2:
|
| 263 |
+
if st.button("Disconnect from Alpaca"):
|
| 264 |
+
self.alpaca_broker = None
|
| 265 |
+
self.session_state.alpaca_connected = False
|
| 266 |
+
st.success("β
Disconnected from Alpaca")
|
| 267 |
+
|
| 268 |
+
# Account information display
|
| 269 |
+
if self.alpaca_broker:
|
| 270 |
+
st.subheader("Account Information")
|
| 271 |
+
|
| 272 |
+
try:
|
| 273 |
+
account_info = self.alpaca_broker.get_account_info()
|
| 274 |
+
if account_info:
|
| 275 |
+
col1, col2, col3 = st.columns(3)
|
| 276 |
+
|
| 277 |
+
with col1:
|
| 278 |
+
st.metric(
|
| 279 |
+
label="Buying Power",
|
| 280 |
+
value=f"${float(account_info['buying_power']):,.2f}"
|
| 281 |
+
)
|
| 282 |
+
|
| 283 |
+
with col2:
|
| 284 |
+
st.metric(
|
| 285 |
+
label="Portfolio Value",
|
| 286 |
+
value=f"${float(account_info['portfolio_value']):,.2f}"
|
| 287 |
+
)
|
| 288 |
+
|
| 289 |
+
with col3:
|
| 290 |
+
st.metric(
|
| 291 |
+
label="Equity",
|
| 292 |
+
value=f"${float(account_info['equity']):,.2f}"
|
| 293 |
+
)
|
| 294 |
+
|
| 295 |
+
# Market hours
|
| 296 |
+
market_hours = self.alpaca_broker.get_market_hours()
|
| 297 |
+
if market_hours:
|
| 298 |
+
status_color = "π’" if market_hours['is_open'] else "π΄"
|
| 299 |
+
st.info(f"{status_color} Market Status: {'OPEN' if market_hours['is_open'] else 'CLOSED'}")
|
| 300 |
+
|
| 301 |
+
if market_hours['next_open']:
|
| 302 |
+
st.write(f"Next Open: {market_hours['next_open']}")
|
| 303 |
+
if market_hours['next_close']:
|
| 304 |
+
st.write(f"Next Close: {market_hours['next_close']}")
|
| 305 |
+
|
| 306 |
+
# Current positions
|
| 307 |
+
positions = self.alpaca_broker.get_positions()
|
| 308 |
+
if positions:
|
| 309 |
+
st.subheader("Current Positions")
|
| 310 |
+
positions_df = pd.DataFrame(positions)
|
| 311 |
+
st.dataframe(positions_df)
|
| 312 |
+
else:
|
| 313 |
+
st.info("No current positions")
|
| 314 |
+
|
| 315 |
+
except Exception as e:
|
| 316 |
+
st.error(f"Error fetching account info: {e}")
|
| 317 |
+
|
| 318 |
+
def finrl_training_panel(self):
|
| 319 |
+
"""FinRL model training panel"""
|
| 320 |
+
st.header("π§ FinRL Model Training")
|
| 321 |
+
|
| 322 |
+
if not self.data is not None:
|
| 323 |
+
st.warning("β οΈ Please load market data first")
|
| 324 |
+
return
|
| 325 |
+
|
| 326 |
+
col1, col2 = st.columns([2, 1])
|
| 327 |
+
|
| 328 |
+
with col1:
|
| 329 |
+
st.subheader("Training Configuration")
|
| 330 |
+
|
| 331 |
+
# Training parameters
|
| 332 |
+
algorithm = st.selectbox(
|
| 333 |
+
"Algorithm",
|
| 334 |
+
["PPO", "A2C", "DDPG", "TD3"],
|
| 335 |
+
index=0
|
| 336 |
+
)
|
| 337 |
+
|
| 338 |
+
learning_rate = st.slider(
|
| 339 |
+
"Learning Rate",
|
| 340 |
+
min_value=0.0001,
|
| 341 |
+
max_value=0.01,
|
| 342 |
+
value=0.0003,
|
| 343 |
+
step=0.0001,
|
| 344 |
+
format="%.4f"
|
| 345 |
+
)
|
| 346 |
+
|
| 347 |
+
total_timesteps = st.slider(
|
| 348 |
+
"Total Timesteps",
|
| 349 |
+
min_value=1000,
|
| 350 |
+
max_value=1000000,
|
| 351 |
+
value=100000,
|
| 352 |
+
step=1000
|
| 353 |
+
)
|
| 354 |
+
|
| 355 |
+
batch_size = st.selectbox(
|
| 356 |
+
"Batch Size",
|
| 357 |
+
[32, 64, 128, 256],
|
| 358 |
+
index=1
|
| 359 |
+
)
|
| 360 |
+
|
| 361 |
+
with col2:
|
| 362 |
+
st.subheader("Training Controls")
|
| 363 |
+
|
| 364 |
+
if st.button("Start Training", type="primary"):
|
| 365 |
+
if self.data is not None:
|
| 366 |
+
with st.spinner("Training FinRL model..."):
|
| 367 |
+
try:
|
| 368 |
+
# Create FinRL config
|
| 369 |
+
finrl_config = FinRLConfig(
|
| 370 |
+
algorithm=algorithm,
|
| 371 |
+
learning_rate=learning_rate,
|
| 372 |
+
batch_size=batch_size,
|
| 373 |
+
buffer_size=1000000,
|
| 374 |
+
learning_starts=100,
|
| 375 |
+
gamma=0.99,
|
| 376 |
+
tau=0.005,
|
| 377 |
+
train_freq=1,
|
| 378 |
+
gradient_steps=1,
|
| 379 |
+
verbose=1,
|
| 380 |
+
tensorboard_log='logs/finrl_tensorboard'
|
| 381 |
+
)
|
| 382 |
+
|
| 383 |
+
# Initialize agent
|
| 384 |
+
self.finrl_agent = FinRLAgent(finrl_config)
|
| 385 |
+
|
| 386 |
+
# Train the agent
|
| 387 |
+
result = self.finrl_agent.train(
|
| 388 |
+
data=self.data,
|
| 389 |
+
config=self.config,
|
| 390 |
+
total_timesteps=total_timesteps,
|
| 391 |
+
use_real_broker=False
|
| 392 |
+
)
|
| 393 |
+
|
| 394 |
+
if result['success']:
|
| 395 |
+
st.success("β
Training completed successfully!")
|
| 396 |
+
st.write(f"Model saved: {result['model_path']}")
|
| 397 |
+
self.session_state.model_trained = True
|
| 398 |
+
else:
|
| 399 |
+
st.error("β Training failed")
|
| 400 |
+
|
| 401 |
+
except Exception as e:
|
| 402 |
+
st.error(f"β Training error: {e}")
|
| 403 |
+
|
| 404 |
+
# Training progress and metrics
|
| 405 |
+
if hasattr(self.session_state, 'model_trained') and self.session_state.model_trained:
|
| 406 |
+
st.subheader("Model Performance")
|
| 407 |
+
|
| 408 |
+
if st.button("Evaluate Model"):
|
| 409 |
+
if self.finrl_agent:
|
| 410 |
+
with st.spinner("Evaluating model..."):
|
| 411 |
+
try:
|
| 412 |
+
# Use last 100 data points for evaluation
|
| 413 |
+
eval_data = self.data.tail(100)
|
| 414 |
+
prediction_result = self.finrl_agent.predict(
|
| 415 |
+
data=eval_data,
|
| 416 |
+
config=self.config,
|
| 417 |
+
use_real_broker=False
|
| 418 |
+
)
|
| 419 |
+
|
| 420 |
+
if prediction_result['success']:
|
| 421 |
+
col1, col2, col3 = st.columns(3)
|
| 422 |
+
|
| 423 |
+
with col1:
|
| 424 |
+
st.metric(
|
| 425 |
+
label="Initial Value",
|
| 426 |
+
value=f"${prediction_result['initial_value']:,.2f}"
|
| 427 |
+
)
|
| 428 |
+
|
| 429 |
+
with col2:
|
| 430 |
+
st.metric(
|
| 431 |
+
label="Final Value",
|
| 432 |
+
value=f"${prediction_result['final_value']:,.2f}"
|
| 433 |
+
)
|
| 434 |
+
|
| 435 |
+
with col3:
|
| 436 |
+
return_pct = prediction_result['total_return'] * 100
|
| 437 |
+
st.metric(
|
| 438 |
+
label="Total Return",
|
| 439 |
+
value=f"{return_pct:.2f}%",
|
| 440 |
+
delta=f"{return_pct:.2f}%"
|
| 441 |
+
)
|
| 442 |
+
|
| 443 |
+
st.write(f"Total Trades: {prediction_result['total_trades']}")
|
| 444 |
+
else:
|
| 445 |
+
st.error("β Model evaluation failed")
|
| 446 |
+
|
| 447 |
+
except Exception as e:
|
| 448 |
+
st.error(f"β Evaluation error: {e}")
|
| 449 |
+
|
| 450 |
+
def trading_controls_panel(self):
|
| 451 |
+
"""Trading controls and execution panel"""
|
| 452 |
+
st.header("π― Trading Controls")
|
| 453 |
+
|
| 454 |
+
col1, col2 = st.columns([1, 1])
|
| 455 |
+
|
| 456 |
+
with col1:
|
| 457 |
+
st.subheader("Backtesting")
|
| 458 |
+
|
| 459 |
+
if st.button("Run Backtest"):
|
| 460 |
+
if self.data is not None and self.config:
|
| 461 |
+
with st.spinner("Running backtest..."):
|
| 462 |
+
try:
|
| 463 |
+
result = run_backtest(self.config, self.data)
|
| 464 |
+
if result['success']:
|
| 465 |
+
st.success("β
Backtest completed")
|
| 466 |
+
|
| 467 |
+
# Display backtest results
|
| 468 |
+
col1, col2, col3 = st.columns(3)
|
| 469 |
+
|
| 470 |
+
with col1:
|
| 471 |
+
st.metric(
|
| 472 |
+
label="Total Return",
|
| 473 |
+
value=f"{result['total_return']:.2%}"
|
| 474 |
+
)
|
| 475 |
+
|
| 476 |
+
with col2:
|
| 477 |
+
st.metric(
|
| 478 |
+
label="Sharpe Ratio",
|
| 479 |
+
value=f"{result['sharpe_ratio']:.2f}"
|
| 480 |
+
)
|
| 481 |
+
|
| 482 |
+
with col3:
|
| 483 |
+
st.metric(
|
| 484 |
+
label="Max Drawdown",
|
| 485 |
+
value=f"{result['max_drawdown']:.2%}"
|
| 486 |
+
)
|
| 487 |
+
|
| 488 |
+
# Store results in session state
|
| 489 |
+
self.session_state.backtest_results = result
|
| 490 |
+
else:
|
| 491 |
+
st.error("β Backtest failed")
|
| 492 |
+
|
| 493 |
+
except Exception as e:
|
| 494 |
+
st.error(f"β Backtest error: {e}")
|
| 495 |
+
|
| 496 |
+
with col2:
|
| 497 |
+
st.subheader("Live Trading")
|
| 498 |
+
|
| 499 |
+
if st.button("Start Live Trading", type="primary"):
|
| 500 |
+
if self.config and self.alpaca_broker:
|
| 501 |
+
self.session_state.trading_active = True
|
| 502 |
+
st.success("β
Live trading started")
|
| 503 |
+
|
| 504 |
+
# Start trading in background thread
|
| 505 |
+
def run_trading():
|
| 506 |
+
try:
|
| 507 |
+
run_live_trading(self.config, self.data)
|
| 508 |
+
except Exception as e:
|
| 509 |
+
st.error(f"Trading error: {e}")
|
| 510 |
+
|
| 511 |
+
trading_thread = threading.Thread(target=run_trading)
|
| 512 |
+
trading_thread.daemon = True
|
| 513 |
+
trading_thread.start()
|
| 514 |
+
else:
|
| 515 |
+
st.warning("β οΈ Please configure Alpaca connection first")
|
| 516 |
+
|
| 517 |
+
if st.button("Stop Live Trading"):
|
| 518 |
+
self.session_state.trading_active = False
|
| 519 |
+
st.success("β
Live trading stopped")
|
| 520 |
+
|
| 521 |
+
def portfolio_monitoring_panel(self):
|
| 522 |
+
"""Portfolio monitoring and analytics panel"""
|
| 523 |
+
st.header("π Portfolio Monitoring")
|
| 524 |
+
|
| 525 |
+
if not self.alpaca_broker:
|
| 526 |
+
st.warning("β οΈ Connect to Alpaca to view portfolio")
|
| 527 |
+
return
|
| 528 |
+
|
| 529 |
+
try:
|
| 530 |
+
# Portfolio overview
|
| 531 |
+
account_info = self.alpaca_broker.get_account_info()
|
| 532 |
+
if account_info:
|
| 533 |
+
col1, col2, col3, col4 = st.columns(4)
|
| 534 |
+
|
| 535 |
+
with col1:
|
| 536 |
+
st.metric(
|
| 537 |
+
label="Total Value",
|
| 538 |
+
value=f"${float(account_info['portfolio_value']):,.2f}"
|
| 539 |
+
)
|
| 540 |
+
|
| 541 |
+
with col2:
|
| 542 |
+
st.metric(
|
| 543 |
+
label="Cash",
|
| 544 |
+
value=f"${float(account_info['cash']):,.2f}"
|
| 545 |
+
)
|
| 546 |
+
|
| 547 |
+
with col3:
|
| 548 |
+
st.metric(
|
| 549 |
+
label="Buying Power",
|
| 550 |
+
value=f"${float(account_info['buying_power']):,.2f}"
|
| 551 |
+
)
|
| 552 |
+
|
| 553 |
+
with col4:
|
| 554 |
+
equity = float(account_info['equity'])
|
| 555 |
+
portfolio_value = float(account_info['portfolio_value'])
|
| 556 |
+
pnl = equity - portfolio_value
|
| 557 |
+
st.metric(
|
| 558 |
+
label="P&L",
|
| 559 |
+
value=f"${pnl:,.2f}",
|
| 560 |
+
delta=f"{pnl:,.2f}"
|
| 561 |
+
)
|
| 562 |
+
|
| 563 |
+
# Positions table
|
| 564 |
+
positions = self.alpaca_broker.get_positions()
|
| 565 |
+
if positions:
|
| 566 |
+
st.subheader("Current Positions")
|
| 567 |
+
|
| 568 |
+
positions_df = pd.DataFrame(positions)
|
| 569 |
+
if not positions_df.empty:
|
| 570 |
+
# Calculate additional metrics
|
| 571 |
+
positions_df['market_value'] = positions_df['quantity'].astype(float) * positions_df['current_price'].astype(float)
|
| 572 |
+
positions_df['unrealized_pl'] = positions_df['unrealized_pl'].astype(float)
|
| 573 |
+
positions_df['unrealized_plpc'] = positions_df['unrealized_plpc'].astype(float)
|
| 574 |
+
|
| 575 |
+
# Display positions
|
| 576 |
+
st.dataframe(
|
| 577 |
+
positions_df[['symbol', 'quantity', 'current_price', 'market_value', 'unrealized_pl', 'unrealized_plpc']],
|
| 578 |
+
use_container_width=True
|
| 579 |
+
)
|
| 580 |
+
|
| 581 |
+
# Position chart
|
| 582 |
+
fig = px.pie(
|
| 583 |
+
positions_df,
|
| 584 |
+
values='market_value',
|
| 585 |
+
names='symbol',
|
| 586 |
+
title="Portfolio Allocation"
|
| 587 |
+
)
|
| 588 |
+
st.plotly_chart(fig, use_container_width=True)
|
| 589 |
+
else:
|
| 590 |
+
st.info("No positions found")
|
| 591 |
+
else:
|
| 592 |
+
st.info("No current positions")
|
| 593 |
+
|
| 594 |
+
except Exception as e:
|
| 595 |
+
st.error(f"Error fetching portfolio data: {e}")
|
| 596 |
+
|
| 597 |
+
def run(self):
|
| 598 |
+
"""Main UI application"""
|
| 599 |
+
# Header
|
| 600 |
+
st.markdown('<h1 class="main-header">π€ Algorithmic Trading System</h1>', unsafe_allow_html=True)
|
| 601 |
+
|
| 602 |
+
# Load configuration
|
| 603 |
+
if self.load_configuration():
|
| 604 |
+
self.config = load_config('config.yaml')
|
| 605 |
+
|
| 606 |
+
# Sidebar navigation
|
| 607 |
+
st.sidebar.title("Navigation")
|
| 608 |
+
page = st.sidebar.selectbox(
|
| 609 |
+
"Select Page",
|
| 610 |
+
["Dashboard", "Data Ingestion", "Alpaca Integration", "FinRL Training", "Trading Controls", "Portfolio Monitoring"]
|
| 611 |
+
)
|
| 612 |
+
|
| 613 |
+
# Display system status
|
| 614 |
+
self.display_system_status()
|
| 615 |
+
|
| 616 |
+
# Page routing
|
| 617 |
+
if page == "Dashboard":
|
| 618 |
+
st.header("π Dashboard")
|
| 619 |
+
|
| 620 |
+
if self.config:
|
| 621 |
+
st.subheader("System Configuration")
|
| 622 |
+
config_col1, config_col2 = st.columns(2)
|
| 623 |
+
|
| 624 |
+
with config_col1:
|
| 625 |
+
st.write(f"**Symbol:** {self.config['trading']['symbol']}")
|
| 626 |
+
st.write(f"**Capital:** ${self.config['trading']['capital']:,}")
|
| 627 |
+
st.write(f"**Timeframe:** {self.config['trading']['timeframe']}")
|
| 628 |
+
|
| 629 |
+
with config_col2:
|
| 630 |
+
st.write(f"**Broker:** {self.config['execution']['broker_api']}")
|
| 631 |
+
st.write(f"**FinRL Algorithm:** {self.config['finrl']['algorithm']}")
|
| 632 |
+
st.write(f"**Risk Max Drawdown:** {self.config['risk']['max_drawdown']:.1%}")
|
| 633 |
+
|
| 634 |
+
# Quick actions
|
| 635 |
+
st.subheader("Quick Actions")
|
| 636 |
+
col1, col2, col3 = st.columns(3)
|
| 637 |
+
|
| 638 |
+
with col1:
|
| 639 |
+
if st.button("Load Data", type="primary"):
|
| 640 |
+
if self.config:
|
| 641 |
+
with st.spinner("Loading data..."):
|
| 642 |
+
self.data = load_data(self.config)
|
| 643 |
+
if self.data is not None:
|
| 644 |
+
st.success("β
Data loaded successfully")
|
| 645 |
+
|
| 646 |
+
with col2:
|
| 647 |
+
if st.button("Connect Alpaca"):
|
| 648 |
+
if self.config and self.config['execution']['broker_api'] in ['alpaca_paper', 'alpaca_live']:
|
| 649 |
+
with st.spinner("Connecting..."):
|
| 650 |
+
self.alpaca_broker = AlpacaBroker(self.config)
|
| 651 |
+
st.success("β
Connected to Alpaca")
|
| 652 |
+
|
| 653 |
+
with col3:
|
| 654 |
+
if st.button("Start Training"):
|
| 655 |
+
if self.data is not None:
|
| 656 |
+
st.info("Navigate to FinRL Training page to configure and start training")
|
| 657 |
+
|
| 658 |
+
elif page == "Data Ingestion":
|
| 659 |
+
self.data_ingestion_panel()
|
| 660 |
+
|
| 661 |
+
elif page == "Alpaca Integration":
|
| 662 |
+
self.alpaca_integration_panel()
|
| 663 |
+
|
| 664 |
+
elif page == "FinRL Training":
|
| 665 |
+
self.finrl_training_panel()
|
| 666 |
+
|
| 667 |
+
elif page == "Trading Controls":
|
| 668 |
+
self.trading_controls_panel()
|
| 669 |
+
|
| 670 |
+
elif page == "Portfolio Monitoring":
|
| 671 |
+
self.portfolio_monitoring_panel()
|
| 672 |
+
|
| 673 |
+
def main():
|
| 674 |
+
"""Main application entry point"""
|
| 675 |
+
ui = TradingUI()
|
| 676 |
+
ui.run()
|
| 677 |
+
|
| 678 |
+
if __name__ == "__main__":
|
| 679 |
+
main()
|
ui/websocket_server.py
ADDED
|
@@ -0,0 +1,461 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
WebSocket Server for Real-time Trading Data
|
| 3 |
+
|
| 4 |
+
Provides real-time updates for:
|
| 5 |
+
- Market data streaming
|
| 6 |
+
- Trading signals
|
| 7 |
+
- Portfolio updates
|
| 8 |
+
- System alerts
|
| 9 |
+
"""
|
| 10 |
+
|
| 11 |
+
import asyncio
|
| 12 |
+
import websockets
|
| 13 |
+
import json
|
| 14 |
+
import logging
|
| 15 |
+
import threading
|
| 16 |
+
import time
|
| 17 |
+
from datetime import datetime, timedelta
|
| 18 |
+
from typing import Dict, Any, List, Optional
|
| 19 |
+
import pandas as pd
|
| 20 |
+
import os
|
| 21 |
+
import sys
|
| 22 |
+
|
| 23 |
+
# Add project root to path
|
| 24 |
+
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
| 25 |
+
|
| 26 |
+
from agentic_ai_system.main import load_config
|
| 27 |
+
from agentic_ai_system.data_ingestion import load_data, add_technical_indicators
|
| 28 |
+
from agentic_ai_system.alpaca_broker import AlpacaBroker
|
| 29 |
+
from agentic_ai_system.finrl_agent import FinRLAgent, FinRLConfig
|
| 30 |
+
|
| 31 |
+
class TradingWebSocketServer:
|
| 32 |
+
def __init__(self, host="localhost", port=8765):
|
| 33 |
+
self.host = host
|
| 34 |
+
self.port = port
|
| 35 |
+
self.clients = set()
|
| 36 |
+
self.config = None
|
| 37 |
+
self.alpaca_broker = None
|
| 38 |
+
self.finrl_agent = None
|
| 39 |
+
self.trading_active = False
|
| 40 |
+
self.market_data = None
|
| 41 |
+
self.portfolio_data = {}
|
| 42 |
+
|
| 43 |
+
# Setup logging
|
| 44 |
+
logging.basicConfig(level=logging.INFO)
|
| 45 |
+
self.logger = logging.getLogger(__name__)
|
| 46 |
+
|
| 47 |
+
async def register(self, websocket):
|
| 48 |
+
"""Register a new client"""
|
| 49 |
+
self.clients.add(websocket)
|
| 50 |
+
self.logger.info(f"Client connected. Total clients: {len(self.clients)}")
|
| 51 |
+
|
| 52 |
+
# Send initial data
|
| 53 |
+
await self.send_initial_data(websocket)
|
| 54 |
+
|
| 55 |
+
async def unregister(self, websocket):
|
| 56 |
+
"""Unregister a client"""
|
| 57 |
+
self.clients.remove(websocket)
|
| 58 |
+
self.logger.info(f"Client disconnected. Total clients: {len(self.clients)}")
|
| 59 |
+
|
| 60 |
+
async def send_initial_data(self, websocket):
|
| 61 |
+
"""Send initial data to new client"""
|
| 62 |
+
initial_data = {
|
| 63 |
+
"type": "initial_data",
|
| 64 |
+
"timestamp": datetime.now().isoformat(),
|
| 65 |
+
"config": self.config,
|
| 66 |
+
"portfolio": self.portfolio_data,
|
| 67 |
+
"trading_status": self.trading_active
|
| 68 |
+
}
|
| 69 |
+
await websocket.send(json.dumps(initial_data))
|
| 70 |
+
|
| 71 |
+
async def broadcast(self, message):
|
| 72 |
+
"""Broadcast message to all connected clients"""
|
| 73 |
+
if self.clients:
|
| 74 |
+
message_str = json.dumps(message)
|
| 75 |
+
await asyncio.gather(
|
| 76 |
+
*[client.send(message_str) for client in self.clients],
|
| 77 |
+
return_exceptions=True
|
| 78 |
+
)
|
| 79 |
+
|
| 80 |
+
async def handle_market_data(self):
|
| 81 |
+
"""Handle real-time market data updates"""
|
| 82 |
+
while True:
|
| 83 |
+
try:
|
| 84 |
+
if self.config and self.alpaca_broker:
|
| 85 |
+
# Get real-time market data
|
| 86 |
+
symbol = self.config['trading']['symbol']
|
| 87 |
+
|
| 88 |
+
# Get current price
|
| 89 |
+
current_price = await self.get_current_price(symbol)
|
| 90 |
+
|
| 91 |
+
if current_price:
|
| 92 |
+
market_update = {
|
| 93 |
+
"type": "market_data",
|
| 94 |
+
"timestamp": datetime.now().isoformat(),
|
| 95 |
+
"symbol": symbol,
|
| 96 |
+
"price": current_price,
|
| 97 |
+
"volume": await self.get_current_volume(symbol)
|
| 98 |
+
}
|
| 99 |
+
|
| 100 |
+
await self.broadcast(market_update)
|
| 101 |
+
self.logger.info(f"Broadcasted market data for {symbol}: ${current_price}")
|
| 102 |
+
|
| 103 |
+
await asyncio.sleep(1) # Update every second
|
| 104 |
+
|
| 105 |
+
except Exception as e:
|
| 106 |
+
self.logger.error(f"Error in market data handler: {e}")
|
| 107 |
+
await asyncio.sleep(5) # Wait before retrying
|
| 108 |
+
|
| 109 |
+
async def handle_portfolio_updates(self):
|
| 110 |
+
"""Handle portfolio updates"""
|
| 111 |
+
while True:
|
| 112 |
+
try:
|
| 113 |
+
if self.alpaca_broker:
|
| 114 |
+
# Get portfolio information
|
| 115 |
+
account_info = self.alpaca_broker.get_account_info()
|
| 116 |
+
positions = self.alpaca_broker.get_positions()
|
| 117 |
+
|
| 118 |
+
if account_info:
|
| 119 |
+
portfolio_update = {
|
| 120 |
+
"type": "portfolio_update",
|
| 121 |
+
"timestamp": datetime.now().isoformat(),
|
| 122 |
+
"account": {
|
| 123 |
+
"buying_power": float(account_info['buying_power']),
|
| 124 |
+
"portfolio_value": float(account_info['portfolio_value']),
|
| 125 |
+
"equity": float(account_info['equity']),
|
| 126 |
+
"cash": float(account_info['cash'])
|
| 127 |
+
},
|
| 128 |
+
"positions": positions if positions else []
|
| 129 |
+
}
|
| 130 |
+
|
| 131 |
+
await self.broadcast(portfolio_update)
|
| 132 |
+
self.portfolio_data = portfolio_update
|
| 133 |
+
|
| 134 |
+
await asyncio.sleep(5) # Update every 5 seconds
|
| 135 |
+
|
| 136 |
+
except Exception as e:
|
| 137 |
+
self.logger.error(f"Error in portfolio updates: {e}")
|
| 138 |
+
await asyncio.sleep(10) # Wait before retrying
|
| 139 |
+
|
| 140 |
+
async def handle_trading_signals(self):
|
| 141 |
+
"""Handle trading signals from FinRL agent"""
|
| 142 |
+
while True:
|
| 143 |
+
try:
|
| 144 |
+
if self.trading_active and self.finrl_agent and self.market_data is not None:
|
| 145 |
+
# Generate trading signals
|
| 146 |
+
signal = await self.generate_trading_signal()
|
| 147 |
+
|
| 148 |
+
if signal:
|
| 149 |
+
signal_update = {
|
| 150 |
+
"type": "trading_signal",
|
| 151 |
+
"timestamp": datetime.now().isoformat(),
|
| 152 |
+
"signal": signal
|
| 153 |
+
}
|
| 154 |
+
|
| 155 |
+
await self.broadcast(signal_update)
|
| 156 |
+
self.logger.info(f"Broadcasted trading signal: {signal}")
|
| 157 |
+
|
| 158 |
+
await asyncio.sleep(10) # Generate signals every 10 seconds
|
| 159 |
+
|
| 160 |
+
except Exception as e:
|
| 161 |
+
self.logger.error(f"Error in trading signals: {e}")
|
| 162 |
+
await asyncio.sleep(30) # Wait before retrying
|
| 163 |
+
|
| 164 |
+
async def get_current_price(self, symbol):
|
| 165 |
+
"""Get current price for symbol"""
|
| 166 |
+
try:
|
| 167 |
+
if self.alpaca_broker:
|
| 168 |
+
# Get latest price from Alpaca
|
| 169 |
+
latest_trade = self.alpaca_broker.get_latest_trade(symbol)
|
| 170 |
+
if latest_trade:
|
| 171 |
+
return float(latest_trade['p'])
|
| 172 |
+
return None
|
| 173 |
+
except Exception as e:
|
| 174 |
+
self.logger.error(f"Error getting current price: {e}")
|
| 175 |
+
return None
|
| 176 |
+
|
| 177 |
+
async def get_current_volume(self, symbol):
|
| 178 |
+
"""Get current volume for symbol"""
|
| 179 |
+
try:
|
| 180 |
+
if self.alpaca_broker:
|
| 181 |
+
# Get latest trade volume
|
| 182 |
+
latest_trade = self.alpaca_broker.get_latest_trade(symbol)
|
| 183 |
+
if latest_trade:
|
| 184 |
+
return int(latest_trade['s'])
|
| 185 |
+
return None
|
| 186 |
+
except Exception as e:
|
| 187 |
+
self.logger.error(f"Error getting current volume: {e}")
|
| 188 |
+
return None
|
| 189 |
+
|
| 190 |
+
async def generate_trading_signal(self):
|
| 191 |
+
"""Generate trading signal using FinRL agent"""
|
| 192 |
+
try:
|
| 193 |
+
if self.finrl_agent and self.market_data is not None:
|
| 194 |
+
# Use recent data for prediction
|
| 195 |
+
recent_data = self.market_data.tail(100)
|
| 196 |
+
|
| 197 |
+
prediction_result = self.finrl_agent.predict(
|
| 198 |
+
data=recent_data,
|
| 199 |
+
config=self.config,
|
| 200 |
+
use_real_broker=False
|
| 201 |
+
)
|
| 202 |
+
|
| 203 |
+
if prediction_result['success']:
|
| 204 |
+
# Generate signal based on prediction
|
| 205 |
+
current_price = await self.get_current_price(self.config['trading']['symbol'])
|
| 206 |
+
|
| 207 |
+
if current_price:
|
| 208 |
+
signal = {
|
| 209 |
+
"action": "HOLD", # Default action
|
| 210 |
+
"confidence": 0.5,
|
| 211 |
+
"price": current_price,
|
| 212 |
+
"reasoning": "Model prediction"
|
| 213 |
+
}
|
| 214 |
+
|
| 215 |
+
# Determine action based on prediction
|
| 216 |
+
if prediction_result['total_return'] > 0.02: # 2% positive return
|
| 217 |
+
signal["action"] = "BUY"
|
| 218 |
+
signal["confidence"] = min(0.9, 0.5 + abs(prediction_result['total_return']))
|
| 219 |
+
elif prediction_result['total_return'] < -0.02: # 2% negative return
|
| 220 |
+
signal["action"] = "SELL"
|
| 221 |
+
signal["confidence"] = min(0.9, 0.5 + abs(prediction_result['total_return']))
|
| 222 |
+
|
| 223 |
+
return signal
|
| 224 |
+
|
| 225 |
+
return None
|
| 226 |
+
except Exception as e:
|
| 227 |
+
self.logger.error(f"Error generating trading signal: {e}")
|
| 228 |
+
return None
|
| 229 |
+
|
| 230 |
+
async def handle_client_message(self, websocket, message):
|
| 231 |
+
"""Handle incoming client messages"""
|
| 232 |
+
try:
|
| 233 |
+
data = json.loads(message)
|
| 234 |
+
message_type = data.get("type")
|
| 235 |
+
|
| 236 |
+
if message_type == "load_config":
|
| 237 |
+
# Load configuration
|
| 238 |
+
config_file = data.get("config_file", "config.yaml")
|
| 239 |
+
self.config = load_config(config_file)
|
| 240 |
+
|
| 241 |
+
response = {
|
| 242 |
+
"type": "config_loaded",
|
| 243 |
+
"success": True,
|
| 244 |
+
"config": self.config
|
| 245 |
+
}
|
| 246 |
+
await websocket.send(json.dumps(response))
|
| 247 |
+
|
| 248 |
+
elif message_type == "connect_alpaca":
|
| 249 |
+
# Connect to Alpaca
|
| 250 |
+
api_key = data.get("api_key")
|
| 251 |
+
secret_key = data.get("secret_key")
|
| 252 |
+
|
| 253 |
+
if api_key and secret_key:
|
| 254 |
+
self.config['alpaca']['api_key'] = api_key
|
| 255 |
+
self.config['alpaca']['secret_key'] = secret_key
|
| 256 |
+
self.config['execution']['broker_api'] = 'alpaca_paper'
|
| 257 |
+
|
| 258 |
+
self.alpaca_broker = AlpacaBroker(self.config)
|
| 259 |
+
|
| 260 |
+
response = {
|
| 261 |
+
"type": "alpaca_connected",
|
| 262 |
+
"success": True
|
| 263 |
+
}
|
| 264 |
+
await websocket.send(json.dumps(response))
|
| 265 |
+
else:
|
| 266 |
+
response = {
|
| 267 |
+
"type": "alpaca_connected",
|
| 268 |
+
"success": False,
|
| 269 |
+
"error": "Missing API credentials"
|
| 270 |
+
}
|
| 271 |
+
await websocket.send(json.dumps(response))
|
| 272 |
+
|
| 273 |
+
elif message_type == "start_trading":
|
| 274 |
+
# Start trading
|
| 275 |
+
self.trading_active = True
|
| 276 |
+
|
| 277 |
+
response = {
|
| 278 |
+
"type": "trading_started",
|
| 279 |
+
"success": True
|
| 280 |
+
}
|
| 281 |
+
await websocket.send(json.dumps(response))
|
| 282 |
+
|
| 283 |
+
# Broadcast to all clients
|
| 284 |
+
await self.broadcast({
|
| 285 |
+
"type": "trading_status",
|
| 286 |
+
"active": True,
|
| 287 |
+
"timestamp": datetime.now().isoformat()
|
| 288 |
+
})
|
| 289 |
+
|
| 290 |
+
elif message_type == "stop_trading":
|
| 291 |
+
# Stop trading
|
| 292 |
+
self.trading_active = False
|
| 293 |
+
|
| 294 |
+
response = {
|
| 295 |
+
"type": "trading_stopped",
|
| 296 |
+
"success": True
|
| 297 |
+
}
|
| 298 |
+
await websocket.send(json.dumps(response))
|
| 299 |
+
|
| 300 |
+
# Broadcast to all clients
|
| 301 |
+
await self.broadcast({
|
| 302 |
+
"type": "trading_status",
|
| 303 |
+
"active": False,
|
| 304 |
+
"timestamp": datetime.now().isoformat()
|
| 305 |
+
})
|
| 306 |
+
|
| 307 |
+
elif message_type == "load_data":
|
| 308 |
+
# Load market data
|
| 309 |
+
if self.config:
|
| 310 |
+
self.market_data = load_data(self.config)
|
| 311 |
+
if self.market_data is not None:
|
| 312 |
+
self.market_data = add_technical_indicators(self.market_data)
|
| 313 |
+
|
| 314 |
+
response = {
|
| 315 |
+
"type": "data_loaded",
|
| 316 |
+
"success": True,
|
| 317 |
+
"data_points": len(self.market_data)
|
| 318 |
+
}
|
| 319 |
+
else:
|
| 320 |
+
response = {
|
| 321 |
+
"type": "data_loaded",
|
| 322 |
+
"success": False,
|
| 323 |
+
"error": "Failed to load data"
|
| 324 |
+
}
|
| 325 |
+
else:
|
| 326 |
+
response = {
|
| 327 |
+
"type": "data_loaded",
|
| 328 |
+
"success": False,
|
| 329 |
+
"error": "Configuration not loaded"
|
| 330 |
+
}
|
| 331 |
+
|
| 332 |
+
await websocket.send(json.dumps(response))
|
| 333 |
+
|
| 334 |
+
elif message_type == "train_model":
|
| 335 |
+
# Train FinRL model
|
| 336 |
+
if self.market_data is not None:
|
| 337 |
+
algorithm = data.get("algorithm", "PPO")
|
| 338 |
+
learning_rate = data.get("learning_rate", 0.0003)
|
| 339 |
+
training_steps = data.get("training_steps", 100000)
|
| 340 |
+
|
| 341 |
+
finrl_config = FinRLConfig(
|
| 342 |
+
algorithm=algorithm,
|
| 343 |
+
learning_rate=learning_rate,
|
| 344 |
+
batch_size=64,
|
| 345 |
+
buffer_size=1000000,
|
| 346 |
+
learning_starts=100,
|
| 347 |
+
gamma=0.99,
|
| 348 |
+
tau=0.005,
|
| 349 |
+
train_freq=1,
|
| 350 |
+
gradient_steps=1,
|
| 351 |
+
verbose=1,
|
| 352 |
+
tensorboard_log='logs/finrl_tensorboard'
|
| 353 |
+
)
|
| 354 |
+
|
| 355 |
+
self.finrl_agent = FinRLAgent(finrl_config)
|
| 356 |
+
|
| 357 |
+
# Train in background thread
|
| 358 |
+
def train_model():
|
| 359 |
+
try:
|
| 360 |
+
result = self.finrl_agent.train(
|
| 361 |
+
data=self.market_data,
|
| 362 |
+
config=self.config,
|
| 363 |
+
total_timesteps=training_steps,
|
| 364 |
+
use_real_broker=False
|
| 365 |
+
)
|
| 366 |
+
|
| 367 |
+
# Broadcast training completion
|
| 368 |
+
asyncio.create_task(self.broadcast({
|
| 369 |
+
"type": "training_completed",
|
| 370 |
+
"success": result['success'],
|
| 371 |
+
"result": result
|
| 372 |
+
}))
|
| 373 |
+
except Exception as e:
|
| 374 |
+
asyncio.create_task(self.broadcast({
|
| 375 |
+
"type": "training_completed",
|
| 376 |
+
"success": False,
|
| 377 |
+
"error": str(e)
|
| 378 |
+
}))
|
| 379 |
+
|
| 380 |
+
training_thread = threading.Thread(target=train_model)
|
| 381 |
+
training_thread.daemon = True
|
| 382 |
+
training_thread.start()
|
| 383 |
+
|
| 384 |
+
response = {
|
| 385 |
+
"type": "training_started",
|
| 386 |
+
"success": True
|
| 387 |
+
}
|
| 388 |
+
else:
|
| 389 |
+
response = {
|
| 390 |
+
"type": "training_started",
|
| 391 |
+
"success": False,
|
| 392 |
+
"error": "Market data not loaded"
|
| 393 |
+
}
|
| 394 |
+
|
| 395 |
+
await websocket.send(json.dumps(response))
|
| 396 |
+
|
| 397 |
+
else:
|
| 398 |
+
# Unknown message type
|
| 399 |
+
response = {
|
| 400 |
+
"type": "error",
|
| 401 |
+
"message": f"Unknown message type: {message_type}"
|
| 402 |
+
}
|
| 403 |
+
await websocket.send(json.dumps(response))
|
| 404 |
+
|
| 405 |
+
except json.JSONDecodeError:
|
| 406 |
+
response = {
|
| 407 |
+
"type": "error",
|
| 408 |
+
"message": "Invalid JSON message"
|
| 409 |
+
}
|
| 410 |
+
await websocket.send(json.dumps(response))
|
| 411 |
+
except Exception as e:
|
| 412 |
+
response = {
|
| 413 |
+
"type": "error",
|
| 414 |
+
"message": f"Server error: {str(e)}"
|
| 415 |
+
}
|
| 416 |
+
await websocket.send(json.dumps(response))
|
| 417 |
+
|
| 418 |
+
async def websocket_handler(self, websocket, path):
|
| 419 |
+
"""Main WebSocket handler"""
|
| 420 |
+
await self.register(websocket)
|
| 421 |
+
try:
|
| 422 |
+
async for message in websocket:
|
| 423 |
+
await self.handle_client_message(websocket, message)
|
| 424 |
+
except websockets.exceptions.ConnectionClosed:
|
| 425 |
+
pass
|
| 426 |
+
finally:
|
| 427 |
+
await self.unregister(websocket)
|
| 428 |
+
|
| 429 |
+
async def start_server(self):
|
| 430 |
+
"""Start the WebSocket server"""
|
| 431 |
+
# Start background tasks
|
| 432 |
+
asyncio.create_task(self.handle_market_data())
|
| 433 |
+
asyncio.create_task(self.handle_portfolio_updates())
|
| 434 |
+
asyncio.create_task(self.handle_trading_signals())
|
| 435 |
+
|
| 436 |
+
# Start WebSocket server
|
| 437 |
+
server = await websockets.serve(
|
| 438 |
+
self.websocket_handler,
|
| 439 |
+
self.host,
|
| 440 |
+
self.port
|
| 441 |
+
)
|
| 442 |
+
|
| 443 |
+
self.logger.info(f"WebSocket server started on ws://{self.host}:{self.port}")
|
| 444 |
+
|
| 445 |
+
# Keep server running
|
| 446 |
+
await server.wait_closed()
|
| 447 |
+
|
| 448 |
+
def run_server(self):
|
| 449 |
+
"""Run the server in a separate thread"""
|
| 450 |
+
def run():
|
| 451 |
+
asyncio.run(self.start_server())
|
| 452 |
+
|
| 453 |
+
server_thread = threading.Thread(target=run)
|
| 454 |
+
server_thread.daemon = True
|
| 455 |
+
server_thread.start()
|
| 456 |
+
|
| 457 |
+
return server_thread
|
| 458 |
+
|
| 459 |
+
def create_websocket_server(host="localhost", port=8765):
|
| 460 |
+
"""Create and return a WebSocket server instance"""
|
| 461 |
+
return TradingWebSocketServer(host=host, port=port)
|
ui_launcher.py
ADDED
|
@@ -0,0 +1,269 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
UI Launcher for Algorithmic Trading System
|
| 4 |
+
|
| 5 |
+
Provides multiple UI options:
|
| 6 |
+
- Streamlit: Quick prototyping and data science workflows
|
| 7 |
+
- Dash: Enterprise-grade interactive dashboards
|
| 8 |
+
- Jupyter: Notebook-based interfaces
|
| 9 |
+
- WebSocket: Real-time trading interfaces
|
| 10 |
+
"""
|
| 11 |
+
|
| 12 |
+
import argparse
|
| 13 |
+
import sys
|
| 14 |
+
import os
|
| 15 |
+
import subprocess
|
| 16 |
+
import webbrowser
|
| 17 |
+
import time
|
| 18 |
+
import threading
|
| 19 |
+
from typing import Optional
|
| 20 |
+
|
| 21 |
+
def check_dependencies():
|
| 22 |
+
"""Check if required UI dependencies are installed"""
|
| 23 |
+
required_packages = [
|
| 24 |
+
'streamlit',
|
| 25 |
+
'dash',
|
| 26 |
+
'plotly',
|
| 27 |
+
'ipywidgets'
|
| 28 |
+
]
|
| 29 |
+
|
| 30 |
+
missing_packages = []
|
| 31 |
+
for package in required_packages:
|
| 32 |
+
try:
|
| 33 |
+
__import__(package)
|
| 34 |
+
except ImportError:
|
| 35 |
+
missing_packages.append(package)
|
| 36 |
+
|
| 37 |
+
if missing_packages:
|
| 38 |
+
print(f"β Missing required packages: {', '.join(missing_packages)}")
|
| 39 |
+
print("Please install them using: pip install -r requirements.txt")
|
| 40 |
+
return False
|
| 41 |
+
|
| 42 |
+
return True
|
| 43 |
+
|
| 44 |
+
def launch_streamlit():
|
| 45 |
+
"""Launch Streamlit application"""
|
| 46 |
+
print("π Launching Streamlit UI...")
|
| 47 |
+
|
| 48 |
+
# Create streamlit app file if it doesn't exist
|
| 49 |
+
streamlit_app_path = "ui/streamlit_app.py"
|
| 50 |
+
if not os.path.exists(streamlit_app_path):
|
| 51 |
+
print(f"β Streamlit app not found at {streamlit_app_path}")
|
| 52 |
+
return False
|
| 53 |
+
|
| 54 |
+
try:
|
| 55 |
+
# Launch Streamlit
|
| 56 |
+
cmd = [
|
| 57 |
+
sys.executable, "-m", "streamlit", "run",
|
| 58 |
+
streamlit_app_path,
|
| 59 |
+
"--server.port", "8501",
|
| 60 |
+
"--server.address", "0.0.0.0",
|
| 61 |
+
"--browser.gatherUsageStats", "false"
|
| 62 |
+
]
|
| 63 |
+
|
| 64 |
+
print(f"Running: {' '.join(cmd)}")
|
| 65 |
+
subprocess.run(cmd)
|
| 66 |
+
return True
|
| 67 |
+
|
| 68 |
+
except Exception as e:
|
| 69 |
+
print(f"β Error launching Streamlit: {e}")
|
| 70 |
+
return False
|
| 71 |
+
|
| 72 |
+
def launch_dash():
|
| 73 |
+
"""Launch Dash application"""
|
| 74 |
+
print("π Launching Dash UI...")
|
| 75 |
+
|
| 76 |
+
# Create dash app file if it doesn't exist
|
| 77 |
+
dash_app_path = "ui/dash_app.py"
|
| 78 |
+
if not os.path.exists(dash_app_path):
|
| 79 |
+
print(f"β Dash app not found at {dash_app_path}")
|
| 80 |
+
return False
|
| 81 |
+
|
| 82 |
+
try:
|
| 83 |
+
# Launch Dash
|
| 84 |
+
cmd = [
|
| 85 |
+
sys.executable, dash_app_path
|
| 86 |
+
]
|
| 87 |
+
|
| 88 |
+
print(f"Running: {' '.join(cmd)}")
|
| 89 |
+
subprocess.run(cmd)
|
| 90 |
+
return True
|
| 91 |
+
|
| 92 |
+
except Exception as e:
|
| 93 |
+
print(f"β Error launching Dash: {e}")
|
| 94 |
+
return False
|
| 95 |
+
|
| 96 |
+
def launch_jupyter():
|
| 97 |
+
"""Launch Jupyter interface"""
|
| 98 |
+
print("π Launching Jupyter UI...")
|
| 99 |
+
|
| 100 |
+
try:
|
| 101 |
+
# Launch Jupyter Lab
|
| 102 |
+
cmd = [
|
| 103 |
+
sys.executable, "-m", "jupyter", "lab",
|
| 104 |
+
"--port", "8888",
|
| 105 |
+
"--ip", "0.0.0.0",
|
| 106 |
+
"--no-browser"
|
| 107 |
+
]
|
| 108 |
+
|
| 109 |
+
print(f"Running: {' '.join(cmd)}")
|
| 110 |
+
subprocess.run(cmd)
|
| 111 |
+
return True
|
| 112 |
+
|
| 113 |
+
except Exception as e:
|
| 114 |
+
print(f"β Error launching Jupyter: {e}")
|
| 115 |
+
return False
|
| 116 |
+
|
| 117 |
+
def launch_websocket_server():
|
| 118 |
+
"""Launch WebSocket server"""
|
| 119 |
+
print("π Launching WebSocket Server...")
|
| 120 |
+
|
| 121 |
+
try:
|
| 122 |
+
from ui.websocket_server import create_websocket_server
|
| 123 |
+
|
| 124 |
+
server = create_websocket_server(host="0.0.0.0", port=8765)
|
| 125 |
+
server_thread = server.run_server()
|
| 126 |
+
|
| 127 |
+
print("β
WebSocket server started on ws://0.0.0.0:8765")
|
| 128 |
+
print("Press Ctrl+C to stop the server")
|
| 129 |
+
|
| 130 |
+
# Keep the main thread alive
|
| 131 |
+
try:
|
| 132 |
+
while True:
|
| 133 |
+
time.sleep(1)
|
| 134 |
+
except KeyboardInterrupt:
|
| 135 |
+
print("\nπ Stopping WebSocket server...")
|
| 136 |
+
|
| 137 |
+
return True
|
| 138 |
+
|
| 139 |
+
except Exception as e:
|
| 140 |
+
print(f"β Error launching WebSocket server: {e}")
|
| 141 |
+
return False
|
| 142 |
+
|
| 143 |
+
def open_browser(url: str, delay: int = 2):
|
| 144 |
+
"""Open browser after delay"""
|
| 145 |
+
def open_url():
|
| 146 |
+
time.sleep(delay)
|
| 147 |
+
try:
|
| 148 |
+
webbrowser.open(url)
|
| 149 |
+
print(f"π Opened browser to: {url}")
|
| 150 |
+
except Exception as e:
|
| 151 |
+
print(f"β οΈ Could not open browser: {e}")
|
| 152 |
+
|
| 153 |
+
browser_thread = threading.Thread(target=open_url)
|
| 154 |
+
browser_thread.daemon = True
|
| 155 |
+
browser_thread.start()
|
| 156 |
+
|
| 157 |
+
def main():
|
| 158 |
+
"""Main launcher function"""
|
| 159 |
+
parser = argparse.ArgumentParser(
|
| 160 |
+
description="UI Launcher for Algorithmic Trading System",
|
| 161 |
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
| 162 |
+
epilog="""
|
| 163 |
+
Examples:
|
| 164 |
+
python ui_launcher.py streamlit # Launch Streamlit UI
|
| 165 |
+
python ui_launcher.py dash # Launch Dash UI
|
| 166 |
+
python ui_launcher.py jupyter # Launch Jupyter Lab
|
| 167 |
+
python ui_launcher.py websocket # Launch WebSocket server
|
| 168 |
+
python ui_launcher.py all # Launch all UIs
|
| 169 |
+
"""
|
| 170 |
+
)
|
| 171 |
+
|
| 172 |
+
parser.add_argument(
|
| 173 |
+
"ui_type",
|
| 174 |
+
choices=["streamlit", "dash", "jupyter", "websocket", "all"],
|
| 175 |
+
help="Type of UI to launch"
|
| 176 |
+
)
|
| 177 |
+
|
| 178 |
+
parser.add_argument(
|
| 179 |
+
"--no-browser",
|
| 180 |
+
action="store_true",
|
| 181 |
+
help="Don't automatically open browser"
|
| 182 |
+
)
|
| 183 |
+
|
| 184 |
+
parser.add_argument(
|
| 185 |
+
"--port",
|
| 186 |
+
type=int,
|
| 187 |
+
help="Custom port number (overrides default)"
|
| 188 |
+
)
|
| 189 |
+
|
| 190 |
+
args = parser.parse_args()
|
| 191 |
+
|
| 192 |
+
# Check dependencies
|
| 193 |
+
if not check_dependencies():
|
| 194 |
+
sys.exit(1)
|
| 195 |
+
|
| 196 |
+
print("π€ Algorithmic Trading System - UI Launcher")
|
| 197 |
+
print("=" * 50)
|
| 198 |
+
|
| 199 |
+
success = False
|
| 200 |
+
|
| 201 |
+
if args.ui_type == "streamlit":
|
| 202 |
+
success = launch_streamlit()
|
| 203 |
+
if success and not args.no_browser:
|
| 204 |
+
open_browser("http://localhost:8501")
|
| 205 |
+
|
| 206 |
+
elif args.ui_type == "dash":
|
| 207 |
+
success = launch_dash()
|
| 208 |
+
if success and not args.no_browser:
|
| 209 |
+
open_browser("http://localhost:8050")
|
| 210 |
+
|
| 211 |
+
elif args.ui_type == "jupyter":
|
| 212 |
+
success = launch_jupyter()
|
| 213 |
+
if success and not args.no_browser:
|
| 214 |
+
open_browser("http://localhost:8888")
|
| 215 |
+
|
| 216 |
+
elif args.ui_type == "websocket":
|
| 217 |
+
success = launch_websocket_server()
|
| 218 |
+
|
| 219 |
+
elif args.ui_type == "all":
|
| 220 |
+
print("π Launching all UI interfaces...")
|
| 221 |
+
|
| 222 |
+
# Launch WebSocket server in background
|
| 223 |
+
websocket_thread = threading.Thread(target=launch_websocket_server)
|
| 224 |
+
websocket_thread.daemon = True
|
| 225 |
+
websocket_thread.start()
|
| 226 |
+
|
| 227 |
+
# Launch Streamlit
|
| 228 |
+
streamlit_thread = threading.Thread(target=launch_streamlit)
|
| 229 |
+
streamlit_thread.daemon = True
|
| 230 |
+
streamlit_thread.start()
|
| 231 |
+
|
| 232 |
+
# Launch Dash
|
| 233 |
+
dash_thread = threading.Thread(target=launch_dash)
|
| 234 |
+
dash_thread.daemon = True
|
| 235 |
+
dash_thread.start()
|
| 236 |
+
|
| 237 |
+
# Launch Jupyter
|
| 238 |
+
jupyter_thread = threading.Thread(target=launch_jupyter)
|
| 239 |
+
jupyter_thread.daemon = True
|
| 240 |
+
jupyter_thread.start()
|
| 241 |
+
|
| 242 |
+
if not args.no_browser:
|
| 243 |
+
open_browser("http://localhost:8501", 3) # Streamlit
|
| 244 |
+
open_browser("http://localhost:8050", 5) # Dash
|
| 245 |
+
open_browser("http://localhost:8888", 7) # Jupyter
|
| 246 |
+
|
| 247 |
+
print("β
All UIs launched!")
|
| 248 |
+
print("π Streamlit: http://localhost:8501")
|
| 249 |
+
print("π Dash: http://localhost:8050")
|
| 250 |
+
print("π Jupyter: http://localhost:8888")
|
| 251 |
+
print("π WebSocket: ws://localhost:8765")
|
| 252 |
+
|
| 253 |
+
# Keep main thread alive
|
| 254 |
+
try:
|
| 255 |
+
while True:
|
| 256 |
+
time.sleep(1)
|
| 257 |
+
except KeyboardInterrupt:
|
| 258 |
+
print("\nπ Stopping all UIs...")
|
| 259 |
+
|
| 260 |
+
success = True
|
| 261 |
+
|
| 262 |
+
if success:
|
| 263 |
+
print("β
UI launched successfully!")
|
| 264 |
+
else:
|
| 265 |
+
print("β Failed to launch UI")
|
| 266 |
+
sys.exit(1)
|
| 267 |
+
|
| 268 |
+
if __name__ == "__main__":
|
| 269 |
+
main()
|