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()
|