Edwin Salguero
commited on
Commit
·
46590b0
1
Parent(s):
9fb1755
feat: Add comprehensive Alpaca integration with FinRL
Browse files- Add Alpaca broker module for real trading capabilities
- Integrate Alpaca with FinRL reinforcement learning agent
- Update execution agent to support real broker integration
- Enhance data ingestion with Alpaca market data support
- Add environment configuration for API credentials
- Update demo script to showcase Alpaca integration
- Comprehensive documentation updates
- Support for paper and live trading modes
- README.md +266 -382
- agentic_ai_system/alpaca_broker.py +406 -0
- agentic_ai_system/data_ingestion.py +231 -88
- agentic_ai_system/execution_agent.py +110 -24
- agentic_ai_system/finrl_agent.py +283 -212
- config.yaml +14 -1
- demo.py +283 -102
- env.example +28 -0
- requirements.txt +4 -0
README.md
CHANGED
@@ -25,224 +25,85 @@ library_name: algorithmic-trading
|
|
25 |
paperswithcode_id: null
|
26 |
---
|
27 |
|
28 |
-
# Algorithmic Trading System
|
29 |
|
30 |
-
A
|
31 |
|
32 |
-
## Features
|
33 |
|
34 |
### Core Trading System
|
35 |
-
- **
|
36 |
-
- **Technical Analysis**:
|
37 |
-
- **Risk Management**: Position sizing and
|
38 |
-
- **
|
39 |
|
40 |
### FinRL Reinforcement Learning
|
41 |
-
- **Multiple
|
42 |
- **Custom Trading Environment**: Gymnasium-compatible environment for RL training
|
43 |
-
- **
|
44 |
-
- **
|
45 |
-
- **Model Persistence**: Save and load trained models for inference
|
46 |
-
- **TensorBoard Integration**: Training progress visualization and monitoring
|
47 |
-
- **Comprehensive Evaluation**: Performance metrics including Sharpe ratio and total returns
|
48 |
-
|
49 |
-
### Docker Integration
|
50 |
-
- **Multi-Environment Support**: Development, production, and testing environments
|
51 |
-
- **Container Orchestration**: Docker Compose for easy service management
|
52 |
-
- **Monitoring Stack**: Prometheus and Grafana for system monitoring
|
53 |
-
- **Development Tools**: Jupyter Lab integration for interactive development
|
54 |
-
- **Automated Testing**: Containerized test execution with coverage reporting
|
55 |
-
- **Resource Management**: CPU and memory limits for production deployment
|
56 |
-
- **Health Checks**: Built-in health monitoring for all services
|
57 |
-
- **Backup Services**: Automated backup and data persistence
|
58 |
-
|
59 |
-
### Synthetic Data Generation
|
60 |
-
- **Realistic Market Data**: Generate OHLCV data using geometric Brownian motion
|
61 |
-
- **Multiple Frequencies**: Support for 1min, 5min, 1H, and 1D data
|
62 |
-
- **Market Scenarios**: Normal, volatile, trending, and crash market conditions
|
63 |
-
- **Tick Data**: High-frequency tick data generation for testing
|
64 |
-
- **Configurable Parameters**: Volatility, trend, noise levels, and base prices
|
65 |
-
|
66 |
-
### Comprehensive Logging
|
67 |
-
- **Multi-level Logging**: Console and file-based logging
|
68 |
-
- **Rotating Log Files**: Automatic log rotation with size limits
|
69 |
-
- **Specialized Loggers**: Separate loggers for trading, performance, and errors
|
70 |
-
- **Structured Logging**: Detailed log messages with timestamps and context
|
71 |
-
|
72 |
-
### Testing Framework
|
73 |
-
- **Unit Tests**: Comprehensive tests for all components
|
74 |
-
- **Integration Tests**: End-to-end workflow testing
|
75 |
-
- **Test Coverage**: Code coverage reporting with HTML and XML outputs
|
76 |
-
- **Mock Testing**: Isolated testing with mocked dependencies
|
77 |
-
|
78 |
-
## Installation
|
79 |
-
|
80 |
-
### Option 1: Docker (Recommended)
|
81 |
-
|
82 |
-
1. Clone the repository:
|
83 |
-
```bash
|
84 |
-
git clone https://huggingface.co/ParallelLLC/algorithmic_trading
|
85 |
-
cd algorithmic_trading
|
86 |
-
```
|
87 |
-
|
88 |
-
2. Build and run with Docker:
|
89 |
-
```bash
|
90 |
-
# Build the image
|
91 |
-
docker build -t algorithmic-trading .
|
92 |
-
|
93 |
-
# Run the trading system
|
94 |
-
docker run -p 8000:8000 algorithmic-trading
|
95 |
-
```
|
96 |
|
97 |
-
###
|
|
|
|
|
|
|
|
|
|
|
98 |
|
99 |
-
|
100 |
-
|
101 |
-
|
102 |
-
|
103 |
-
|
|
|
104 |
|
105 |
-
|
106 |
-
```bash
|
107 |
-
pip install -r requirements.txt
|
108 |
-
```
|
109 |
|
110 |
-
|
|
|
|
|
111 |
|
112 |
-
|
113 |
|
|
|
114 |
```bash
|
115 |
-
|
116 |
-
|
117 |
-
|
118 |
-
# Build and start production environment
|
119 |
-
./scripts/docker-build.sh prod
|
120 |
-
|
121 |
-
# Run tests in Docker
|
122 |
-
./scripts/docker-build.sh test
|
123 |
-
|
124 |
-
# Stop all containers
|
125 |
-
./scripts/docker-build.sh stop
|
126 |
```
|
127 |
|
128 |
-
###
|
129 |
-
|
130 |
```bash
|
131 |
-
|
132 |
-
docker-compose -f docker-compose.dev.yml up -d
|
133 |
-
|
134 |
-
# Access services:
|
135 |
-
# - Jupyter Lab: http://localhost:8888
|
136 |
-
# - Trading System: http://localhost:8000
|
137 |
-
# - TensorBoard: http://localhost:6006
|
138 |
```
|
139 |
|
140 |
-
###
|
141 |
-
|
142 |
```bash
|
143 |
-
|
144 |
-
docker-compose -f docker-compose.prod.yml up -d
|
145 |
-
|
146 |
-
# Access services:
|
147 |
-
# - Trading System: http://localhost:8000
|
148 |
-
# - Grafana: http://localhost:3000 (admin/admin)
|
149 |
-
# - Prometheus: http://localhost:9090
|
150 |
```
|
151 |
|
152 |
-
|
153 |
-
|
154 |
-
|
155 |
-
|
156 |
-
|
157 |
-
|
158 |
-
# Run FinRL training
|
159 |
-
./scripts/docker-build.sh run 'python finrl_demo.py'
|
160 |
|
161 |
-
#
|
162 |
-
|
163 |
-
|
164 |
-
# Show logs
|
165 |
-
./scripts/docker-build.sh logs trading-system
|
166 |
```
|
167 |
|
168 |
-
###
|
169 |
-
|
170 |
-
#### Development (`docker-compose.dev.yml`)
|
171 |
-
- **trading-dev**: Jupyter Lab environment with hot reload
|
172 |
-
- **finrl-training-dev**: FinRL training with TensorBoard
|
173 |
-
- **testing**: Automated test execution
|
174 |
-
- **linting**: Code quality checks
|
175 |
-
|
176 |
-
#### Production (`docker-compose.prod.yml`)
|
177 |
-
- **trading-system**: Main trading system with resource limits
|
178 |
-
- **monitoring**: Prometheus metrics collection
|
179 |
-
- **grafana**: Data visualization dashboard
|
180 |
-
- **backup**: Automated backup service
|
181 |
-
|
182 |
-
#### Standard (`docker-compose.yml`)
|
183 |
-
- **trading-system**: Basic trading system
|
184 |
-
- **finrl-training**: FinRL training service
|
185 |
-
- **backtesting**: Backtesting service
|
186 |
-
- **development**: Development environment
|
187 |
-
|
188 |
-
### Docker Features
|
189 |
-
|
190 |
-
#### Health Checks
|
191 |
-
All services include health checks to ensure system reliability:
|
192 |
-
```yaml
|
193 |
-
healthcheck:
|
194 |
-
test: ["CMD", "python", "-c", "import sys; sys.exit(0)"]
|
195 |
-
interval: 30s
|
196 |
-
timeout: 10s
|
197 |
-
retries: 3
|
198 |
-
start_period: 40s
|
199 |
-
```
|
200 |
-
|
201 |
-
#### Resource Management
|
202 |
-
Production services include resource limits:
|
203 |
-
```yaml
|
204 |
-
deploy:
|
205 |
-
resources:
|
206 |
-
limits:
|
207 |
-
memory: 2G
|
208 |
-
cpus: '1.0'
|
209 |
-
reservations:
|
210 |
-
memory: 512M
|
211 |
-
cpus: '0.5'
|
212 |
-
```
|
213 |
-
|
214 |
-
#### Volume Management
|
215 |
-
Persistent data storage with named volumes:
|
216 |
-
- `trading_data`: Market data and configuration
|
217 |
-
- `trading_logs`: System logs
|
218 |
-
- `trading_models`: Trained models
|
219 |
-
- `prometheus_data`: Monitoring metrics
|
220 |
-
- `grafana_data`: Dashboard configurations
|
221 |
-
|
222 |
-
#### Logging
|
223 |
-
Structured logging with rotation:
|
224 |
-
```yaml
|
225 |
-
logging:
|
226 |
-
driver: "json-file"
|
227 |
-
options:
|
228 |
-
max-size: "10m"
|
229 |
-
max-file: "3"
|
230 |
-
```
|
231 |
-
|
232 |
-
## Configuration
|
233 |
-
|
234 |
-
The system is configured via `config.yaml`:
|
235 |
-
|
236 |
```yaml
|
237 |
# Data source configuration
|
238 |
data_source:
|
239 |
-
type: '
|
240 |
-
path: 'data/market_data.csv'
|
241 |
|
242 |
# Trading parameters
|
243 |
trading:
|
244 |
symbol: 'AAPL'
|
245 |
-
timeframe: '
|
246 |
capital: 100000
|
247 |
|
248 |
# Risk management
|
@@ -250,286 +111,309 @@ risk:
|
|
250 |
max_position: 100
|
251 |
max_drawdown: 0.05
|
252 |
|
253 |
-
#
|
254 |
execution:
|
255 |
-
broker_api: 'paper'
|
256 |
order_size: 10
|
257 |
-
delay_ms: 100
|
258 |
-
success_rate: 0.95
|
259 |
-
|
260 |
-
# Synthetic data generation
|
261 |
-
synthetic_data:
|
262 |
-
base_price: 150.0
|
263 |
-
volatility: 0.02
|
264 |
-
trend: 0.001
|
265 |
-
noise_level: 0.005
|
266 |
-
generate_data: true
|
267 |
-
data_path: 'data/synthetic_market_data.csv'
|
268 |
-
|
269 |
-
# Logging configuration
|
270 |
-
logging:
|
271 |
-
log_level: 'INFO'
|
272 |
-
log_dir: 'logs'
|
273 |
-
enable_console: true
|
274 |
-
enable_file: true
|
275 |
-
max_file_size_mb: 10
|
276 |
-
backup_count: 5
|
277 |
|
278 |
# FinRL configuration
|
279 |
finrl:
|
280 |
algorithm: 'PPO'
|
281 |
learning_rate: 0.0003
|
282 |
-
batch_size: 64
|
283 |
-
buffer_size: 1000000
|
284 |
-
gamma: 0.99
|
285 |
-
tensorboard_log: 'logs/finrl_tensorboard'
|
286 |
training:
|
287 |
total_timesteps: 100000
|
288 |
-
eval_freq: 10000
|
289 |
save_best_model: true
|
290 |
-
model_save_path: 'models/finrl_best/'
|
291 |
-
inference:
|
292 |
-
use_trained_model: false
|
293 |
-
model_path: 'models/finrl_best/best_model'
|
294 |
```
|
295 |
|
296 |
-
##
|
297 |
|
298 |
-
###
|
299 |
```bash
|
300 |
-
python
|
301 |
```
|
302 |
|
303 |
-
|
304 |
-
|
305 |
-
|
306 |
-
|
|
|
307 |
|
308 |
-
###
|
309 |
```bash
|
310 |
python -m agentic_ai_system.main --mode live --duration 60
|
311 |
```
|
312 |
|
313 |
-
###
|
314 |
```bash
|
315 |
-
python -m agentic_ai_system.main --
|
316 |
```
|
317 |
|
318 |
-
##
|
319 |
|
320 |
-
###
|
321 |
-
```
|
322 |
-
|
323 |
-
|
324 |
|
325 |
-
|
326 |
-
|
327 |
-
pytest -m unit
|
328 |
-
```
|
329 |
|
330 |
-
|
331 |
-
|
332 |
-
|
333 |
```
|
334 |
|
335 |
-
###
|
336 |
-
```
|
337 |
-
|
338 |
-
|
339 |
|
340 |
-
|
341 |
-
|
342 |
-
|
343 |
-
```
|
344 |
|
345 |
-
|
346 |
-
|
347 |
-
|
348 |
-
./scripts/docker-build.sh test
|
349 |
|
350 |
-
#
|
351 |
-
|
352 |
-
|
|
|
|
|
|
|
|
|
353 |
|
354 |
-
|
|
|
355 |
|
356 |
-
###
|
|
|
|
|
357 |
|
358 |
-
|
359 |
-
|
360 |
-
|
361 |
-
4. **ExecutionAgent**: Executes trading orders with broker simulation
|
362 |
-
5. **Orchestrator**: Coordinates the entire trading workflow
|
363 |
-
6. **LoggerConfig**: Manages comprehensive logging throughout the system
|
364 |
-
7. **FinRLAgent**: Reinforcement learning agent for advanced trading strategies
|
365 |
|
366 |
-
|
|
|
|
|
367 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
368 |
```
|
369 |
-
|
370 |
-
|
371 |
-
|
372 |
-
|
373 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
374 |
```
|
375 |
|
376 |
-
|
|
|
|
|
377 |
|
378 |
```
|
379 |
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
380 |
-
│
|
381 |
-
│
|
382 |
-
|
383 |
-
│ •
|
384 |
-
│ •
|
385 |
-
|
386 |
-
│
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
387 |
└─────────────────┘ └─────────────────┘ └─────────────────┘
|
388 |
```
|
389 |
|
390 |
-
|
391 |
-
|
392 |
-
### Prometheus Metrics
|
393 |
-
- Trading performance metrics
|
394 |
-
- System resource usage
|
395 |
-
- Error rates and response times
|
396 |
-
- Custom business metrics
|
397 |
|
398 |
-
|
399 |
-
|
400 |
-
|
401 |
-
|
402 |
-
-
|
|
|
403 |
|
404 |
-
|
405 |
-
- Service availability monitoring
|
406 |
-
- Dependency health verification
|
407 |
-
- Automatic restart on failure
|
408 |
-
- Performance degradation detection
|
409 |
|
410 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
411 |
|
412 |
-
###
|
413 |
-
```
|
414 |
-
|
415 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
416 |
|
417 |
-
|
418 |
-
|
|
|
|
|
|
|
|
|
|
|
419 |
```
|
420 |
|
421 |
-
|
422 |
-
```bash
|
423 |
-
# Deploy to production
|
424 |
-
./scripts/docker-build.sh prod
|
425 |
|
426 |
-
|
427 |
-
|
428 |
-
|
429 |
-
|
|
|
|
|
430 |
|
431 |
-
###
|
432 |
-
|
433 |
-
-
|
434 |
-
-
|
435 |
-
-
|
436 |
-
-
|
437 |
|
438 |
-
###
|
439 |
-
```
|
440 |
-
#
|
441 |
-
|
442 |
-
|
443 |
|
444 |
-
#
|
445 |
-
|
446 |
-
|
447 |
-
|
448 |
|
449 |
-
|
|
|
|
|
|
|
450 |
|
451 |
-
|
452 |
|
453 |
-
|
454 |
```bash
|
455 |
-
#
|
456 |
-
docker
|
|
|
|
|
|
|
457 |
|
458 |
-
#
|
459 |
-
docker
|
460 |
```
|
461 |
|
462 |
-
|
463 |
```bash
|
464 |
-
#
|
465 |
-
docker
|
466 |
|
467 |
-
#
|
468 |
-
docker
|
469 |
```
|
470 |
|
471 |
-
|
472 |
-
```bash
|
473 |
-
# Check volume permissions
|
474 |
-
docker run --rm -v $(pwd):/app algorithmic-trading:latest ls -la /app
|
475 |
|
476 |
-
|
477 |
-
|
|
|
478 |
```
|
479 |
|
480 |
-
###
|
|
|
|
|
|
|
|
|
|
|
|
|
481 |
|
482 |
-
|
483 |
-
|
484 |
-
# Adjust resource limits in docker-compose.prod.yml
|
485 |
-
deploy:
|
486 |
-
resources:
|
487 |
-
limits:
|
488 |
-
memory: 4G # Increase for heavy workloads
|
489 |
-
cpus: '2.0' # Increase for CPU-intensive tasks
|
490 |
```
|
491 |
|
492 |
-
|
493 |
-
```yaml
|
494 |
-
# Reduce log verbosity in production
|
495 |
-
logging:
|
496 |
-
driver: "json-file"
|
497 |
-
options:
|
498 |
-
max-size: "5m" # Smaller log files
|
499 |
-
max-file: "2" # Fewer log files
|
500 |
-
```
|
501 |
|
502 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
503 |
|
504 |
1. Fork the repository
|
505 |
2. Create a feature branch
|
506 |
-
3.
|
507 |
-
4.
|
508 |
5. Submit a pull request
|
509 |
|
510 |
-
|
511 |
-
```bash
|
512 |
-
# Start development environment
|
513 |
-
./scripts/docker-build.sh dev
|
514 |
|
515 |
-
|
516 |
-
./scripts/docker-build.sh test
|
517 |
|
518 |
-
|
519 |
-
docker-compose -f docker-compose.dev.yml run linting
|
520 |
|
521 |
-
|
522 |
-
|
523 |
-
|
524 |
-
|
525 |
-
```
|
526 |
-
|
527 |
-
## License
|
528 |
|
529 |
-
|
530 |
|
531 |
-
|
|
|
|
|
|
|
532 |
|
533 |
-
|
534 |
|
535 |
-
|
|
|
25 |
paperswithcode_id: null
|
26 |
---
|
27 |
|
28 |
+
# Algorithmic Trading System with FinRL and Alpaca Integration
|
29 |
|
30 |
+
A sophisticated algorithmic trading system that combines reinforcement learning (FinRL) with real-time market data and order execution through Alpaca Markets. This system supports both paper trading and live trading with advanced risk management and technical analysis.
|
31 |
|
32 |
+
## 🚀 Features
|
33 |
|
34 |
### Core Trading System
|
35 |
+
- **Multi-source Data Ingestion**: CSV files, Alpaca Markets API, and synthetic data generation
|
36 |
+
- **Technical Analysis**: 20+ technical indicators including RSI, MACD, Bollinger Bands, and more
|
37 |
+
- **Risk Management**: Position sizing, drawdown limits, and portfolio protection
|
38 |
+
- **Real-time Execution**: Live order placement and portfolio monitoring
|
39 |
|
40 |
### FinRL Reinforcement Learning
|
41 |
+
- **Multiple Algorithms**: PPO, A2C, DDPG, and TD3 support
|
42 |
- **Custom Trading Environment**: Gymnasium-compatible environment for RL training
|
43 |
+
- **Real-time Integration**: Can execute real trades during training and inference
|
44 |
+
- **Model Persistence**: Save and load trained models for consistent performance
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
45 |
|
46 |
+
### Alpaca Broker Integration
|
47 |
+
- **Paper Trading**: Risk-free testing with virtual money
|
48 |
+
- **Live Trading**: Real market execution (use with caution!)
|
49 |
+
- **Market Data**: Real-time and historical data from Alpaca
|
50 |
+
- **Account Management**: Portfolio monitoring and position tracking
|
51 |
+
- **Order Types**: Market orders, limit orders, and order cancellation
|
52 |
|
53 |
+
### Advanced Features
|
54 |
+
- **Docker Support**: Containerized deployment for consistency
|
55 |
+
- **Comprehensive Logging**: Detailed logs for debugging and performance analysis
|
56 |
+
- **Backtesting Engine**: Historical performance evaluation
|
57 |
+
- **Live Trading Simulation**: Real-time trading with configurable duration
|
58 |
+
- **Performance Metrics**: Returns, Sharpe ratio, drawdown analysis
|
59 |
|
60 |
+
## 📋 Prerequisites
|
|
|
|
|
|
|
61 |
|
62 |
+
- Python 3.8+
|
63 |
+
- Alpaca Markets account (free paper trading available)
|
64 |
+
- Docker (optional, for containerized deployment)
|
65 |
|
66 |
+
## 🛠️ Installation
|
67 |
|
68 |
+
### 1. Clone the Repository
|
69 |
```bash
|
70 |
+
git clone <repository-url>
|
71 |
+
cd algorithmic_trading
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
72 |
```
|
73 |
|
74 |
+
### 2. Install Dependencies
|
|
|
75 |
```bash
|
76 |
+
pip install -r requirements.txt
|
|
|
|
|
|
|
|
|
|
|
|
|
77 |
```
|
78 |
|
79 |
+
### 3. Set Up Alpaca API Credentials
|
80 |
+
Create a `.env` file in the project root:
|
81 |
```bash
|
82 |
+
cp env.example .env
|
|
|
|
|
|
|
|
|
|
|
|
|
83 |
```
|
84 |
|
85 |
+
Edit `.env` with your Alpaca credentials:
|
86 |
+
```env
|
87 |
+
# Get these from https://app.alpaca.markets/paper/dashboard/overview
|
88 |
+
ALPACA_API_KEY=your_paper_api_key_here
|
89 |
+
ALPACA_SECRET_KEY=your_paper_secret_key_here
|
|
|
|
|
|
|
90 |
|
91 |
+
# For live trading (use with caution!)
|
92 |
+
# ALPACA_API_KEY=your_live_api_key_here
|
93 |
+
# ALPACA_SECRET_KEY=your_live_secret_key_here
|
|
|
|
|
94 |
```
|
95 |
|
96 |
+
### 4. Configure Trading Parameters
|
97 |
+
Edit `config.yaml` to customize your trading strategy:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
98 |
```yaml
|
99 |
# Data source configuration
|
100 |
data_source:
|
101 |
+
type: 'alpaca' # Options: 'alpaca', 'csv', 'synthetic'
|
|
|
102 |
|
103 |
# Trading parameters
|
104 |
trading:
|
105 |
symbol: 'AAPL'
|
106 |
+
timeframe: '1m'
|
107 |
capital: 100000
|
108 |
|
109 |
# Risk management
|
|
|
111 |
max_position: 100
|
112 |
max_drawdown: 0.05
|
113 |
|
114 |
+
# Execution settings
|
115 |
execution:
|
116 |
+
broker_api: 'alpaca_paper' # Options: 'paper', 'alpaca_paper', 'alpaca_live'
|
117 |
order_size: 10
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
118 |
|
119 |
# FinRL configuration
|
120 |
finrl:
|
121 |
algorithm: 'PPO'
|
122 |
learning_rate: 0.0003
|
|
|
|
|
|
|
|
|
123 |
training:
|
124 |
total_timesteps: 100000
|
|
|
125 |
save_best_model: true
|
|
|
|
|
|
|
|
|
126 |
```
|
127 |
|
128 |
+
## 🚀 Quick Start
|
129 |
|
130 |
+
### 1. Run the Demo
|
131 |
```bash
|
132 |
+
python demo.py
|
133 |
```
|
134 |
|
135 |
+
This will:
|
136 |
+
- Test data ingestion from Alpaca
|
137 |
+
- Demonstrate FinRL training
|
138 |
+
- Show trading workflow execution
|
139 |
+
- Run backtesting on historical data
|
140 |
|
141 |
+
### 2. Start Paper Trading
|
142 |
```bash
|
143 |
python -m agentic_ai_system.main --mode live --duration 60
|
144 |
```
|
145 |
|
146 |
+
### 3. Run Backtesting
|
147 |
```bash
|
148 |
+
python -m agentic_ai_system.main --mode backtest --start-date 2024-01-01 --end-date 2024-01-31
|
149 |
```
|
150 |
|
151 |
+
## 📊 Usage Examples
|
152 |
|
153 |
+
### Basic Trading Workflow
|
154 |
+
```python
|
155 |
+
from agentic_ai_system.main import load_config
|
156 |
+
from agentic_ai_system.orchestrator import run
|
157 |
|
158 |
+
# Load configuration
|
159 |
+
config = load_config()
|
|
|
|
|
160 |
|
161 |
+
# Run single trading cycle
|
162 |
+
result = run(config)
|
163 |
+
print(f"Trading result: {result}")
|
164 |
```
|
165 |
|
166 |
+
### FinRL Training
|
167 |
+
```python
|
168 |
+
from agentic_ai_system.finrl_agent import FinRLAgent, FinRLConfig
|
169 |
+
from agentic_ai_system.data_ingestion import load_data
|
170 |
|
171 |
+
# Load data and configuration
|
172 |
+
config = load_config()
|
173 |
+
data = load_data(config)
|
|
|
174 |
|
175 |
+
# Initialize FinRL agent
|
176 |
+
finrl_config = FinRLConfig(algorithm='PPO', learning_rate=0.0003)
|
177 |
+
agent = FinRLAgent(finrl_config)
|
|
|
178 |
|
179 |
+
# Train the agent
|
180 |
+
result = agent.train(
|
181 |
+
data=data,
|
182 |
+
config=config,
|
183 |
+
total_timesteps=100000,
|
184 |
+
use_real_broker=False # Use simulation for training
|
185 |
+
)
|
186 |
|
187 |
+
print(f"Training completed: {result}")
|
188 |
+
```
|
189 |
|
190 |
+
### Alpaca Integration
|
191 |
+
```python
|
192 |
+
from agentic_ai_system.alpaca_broker import AlpacaBroker
|
193 |
|
194 |
+
# Initialize Alpaca broker
|
195 |
+
config = load_config()
|
196 |
+
broker = AlpacaBroker(config)
|
|
|
|
|
|
|
|
|
197 |
|
198 |
+
# Get account information
|
199 |
+
account_info = broker.get_account_info()
|
200 |
+
print(f"Account balance: ${account_info['buying_power']:,.2f}")
|
201 |
|
202 |
+
# Place a market order
|
203 |
+
result = broker.place_market_order(
|
204 |
+
symbol='AAPL',
|
205 |
+
quantity=10,
|
206 |
+
side='buy'
|
207 |
+
)
|
208 |
+
print(f"Order result: {result}")
|
209 |
```
|
210 |
+
|
211 |
+
### Real-time Trading with FinRL
|
212 |
+
```python
|
213 |
+
from agentic_ai_system.finrl_agent import FinRLAgent
|
214 |
+
|
215 |
+
# Load trained model
|
216 |
+
agent = FinRLAgent(FinRLConfig())
|
217 |
+
agent.model = agent._load_model('models/finrl_best/best_model', config)
|
218 |
+
|
219 |
+
# Make predictions with real execution
|
220 |
+
result = agent.predict(
|
221 |
+
data=recent_data,
|
222 |
+
config=config,
|
223 |
+
use_real_broker=True # Execute real trades!
|
224 |
+
)
|
225 |
```
|
226 |
|
227 |
+
## 🏗️ Architecture
|
228 |
+
|
229 |
+
### System Components
|
230 |
|
231 |
```
|
232 |
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
233 |
+
│ Data Sources │ │ Strategy Agent │ │ Execution Agent │
|
234 |
+
│ │ │ │ │ │
|
235 |
+
│ • Alpaca API │───▶│ • Technical │───▶│ • Alpaca Broker │
|
236 |
+
│ • CSV Files │ │ Indicators │ │ • Order Mgmt │
|
237 |
+
│ • Synthetic │ │ • Signal Gen │ │ • Risk Control │
|
238 |
+
└─────────────────┘ └─────────────────┘ └─────────────────┘
|
239 |
+
│ │ │
|
240 |
+
▼ ▼ ▼
|
241 |
+
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
242 |
+
│ Data Ingestion │ │ FinRL Agent │ │ Portfolio │
|
243 |
+
│ │ │ │ │ Management │
|
244 |
+
│ • Validation │ │ • PPO/A2C/DDPG │ │ • Positions │
|
245 |
+
│ • Indicators │ │ • Training │ │ • P&L Tracking │
|
246 |
+
│ • Preprocessing │ │ • Prediction │ │ • Risk Metrics │
|
247 |
└─────────────────┘ └─────────────────┘ └─────────────────┘
|
248 |
```
|
249 |
|
250 |
+
### Data Flow
|
|
|
|
|
|
|
|
|
|
|
|
|
251 |
|
252 |
+
1. **Data Ingestion**: Market data from Alpaca, CSV, or synthetic sources
|
253 |
+
2. **Preprocessing**: Technical indicators, data validation, and feature engineering
|
254 |
+
3. **Strategy Generation**: Traditional technical analysis or FinRL predictions
|
255 |
+
4. **Risk Management**: Position sizing and portfolio protection
|
256 |
+
5. **Order Execution**: Real-time order placement through Alpaca
|
257 |
+
6. **Performance Tracking**: Continuous monitoring and logging
|
258 |
|
259 |
+
## 🔧 Configuration
|
|
|
|
|
|
|
|
|
260 |
|
261 |
+
### Alpaca Settings
|
262 |
+
```yaml
|
263 |
+
alpaca:
|
264 |
+
api_key: '' # Set via environment variable
|
265 |
+
secret_key: '' # Set via environment variable
|
266 |
+
paper_trading: true
|
267 |
+
base_url: 'https://paper-api.alpaca.markets'
|
268 |
+
live_url: 'https://api.alpaca.markets'
|
269 |
+
data_url: 'https://data.alpaca.markets'
|
270 |
+
account_type: 'paper' # 'paper' or 'live'
|
271 |
+
```
|
272 |
|
273 |
+
### FinRL Settings
|
274 |
+
```yaml
|
275 |
+
finrl:
|
276 |
+
algorithm: 'PPO' # PPO, A2C, DDPG, TD3
|
277 |
+
learning_rate: 0.0003
|
278 |
+
batch_size: 64
|
279 |
+
buffer_size: 1000000
|
280 |
+
training:
|
281 |
+
total_timesteps: 100000
|
282 |
+
eval_freq: 10000
|
283 |
+
save_best_model: true
|
284 |
+
model_save_path: 'models/finrl_best/'
|
285 |
+
inference:
|
286 |
+
use_trained_model: false
|
287 |
+
model_path: 'models/finrl_best/best_model'
|
288 |
+
```
|
289 |
|
290 |
+
### Risk Management
|
291 |
+
```yaml
|
292 |
+
risk:
|
293 |
+
max_position: 100
|
294 |
+
max_drawdown: 0.05
|
295 |
+
stop_loss: 0.02
|
296 |
+
take_profit: 0.05
|
297 |
```
|
298 |
|
299 |
+
## 📈 Performance Monitoring
|
|
|
|
|
|
|
300 |
|
301 |
+
### Logging
|
302 |
+
The system provides comprehensive logging:
|
303 |
+
- `logs/trading_system.log`: Main system logs
|
304 |
+
- `logs/trading.log`: Trading-specific events
|
305 |
+
- `logs/performance.log`: Performance metrics
|
306 |
+
- `logs/finrl_tensorboard/`: FinRL training logs
|
307 |
|
308 |
+
### Metrics Tracked
|
309 |
+
- Portfolio value and returns
|
310 |
+
- Trade execution statistics
|
311 |
+
- Risk metrics (Sharpe ratio, drawdown)
|
312 |
+
- FinRL training progress
|
313 |
+
- Alpaca account status
|
314 |
|
315 |
+
### Real-time Monitoring
|
316 |
+
```python
|
317 |
+
# Get account information
|
318 |
+
account_info = broker.get_account_info()
|
319 |
+
print(f"Portfolio Value: ${account_info['portfolio_value']:,.2f}")
|
320 |
|
321 |
+
# Get current positions
|
322 |
+
positions = broker.get_positions()
|
323 |
+
for pos in positions:
|
324 |
+
print(f"{pos['symbol']}: {pos['quantity']} shares")
|
325 |
|
326 |
+
# Check market status
|
327 |
+
market_open = broker.is_market_open()
|
328 |
+
print(f"Market: {'OPEN' if market_open else 'CLOSED'}")
|
329 |
+
```
|
330 |
|
331 |
+
## 🐳 Docker Deployment
|
332 |
|
333 |
+
### Build and Run
|
334 |
```bash
|
335 |
+
# Build the image
|
336 |
+
docker build -t algorithmic-trading .
|
337 |
+
|
338 |
+
# Run with environment variables
|
339 |
+
docker run -it --env-file .env algorithmic-trading
|
340 |
|
341 |
+
# Run with Jupyter Lab for development
|
342 |
+
docker-compose -f docker-compose.dev.yml up
|
343 |
```
|
344 |
|
345 |
+
### Production Deployment
|
346 |
```bash
|
347 |
+
# Use production compose file
|
348 |
+
docker-compose -f docker-compose.prod.yml up -d
|
349 |
|
350 |
+
# Monitor logs
|
351 |
+
docker-compose -f docker-compose.prod.yml logs -f
|
352 |
```
|
353 |
|
354 |
+
## 🧪 Testing
|
|
|
|
|
|
|
355 |
|
356 |
+
### Run All Tests
|
357 |
+
```bash
|
358 |
+
pytest tests/ -v
|
359 |
```
|
360 |
|
361 |
+
### Test Specific Components
|
362 |
+
```bash
|
363 |
+
# Test Alpaca integration
|
364 |
+
pytest tests/test_alpaca_integration.py -v
|
365 |
+
|
366 |
+
# Test FinRL agent
|
367 |
+
pytest tests/test_finrl_agent.py -v
|
368 |
|
369 |
+
# Test trading workflow
|
370 |
+
pytest tests/test_integration.py -v
|
|
|
|
|
|
|
|
|
|
|
|
|
371 |
```
|
372 |
|
373 |
+
## ⚠️ Important Notes
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
374 |
|
375 |
+
### Paper Trading vs Live Trading
|
376 |
+
- **Paper Trading**: Uses virtual money, safe for testing
|
377 |
+
- **Live Trading**: Uses real money, use with extreme caution
|
378 |
+
- Always test strategies thoroughly in paper trading before going live
|
379 |
+
|
380 |
+
### Risk Management
|
381 |
+
- Set appropriate position limits and drawdown thresholds
|
382 |
+
- Monitor your portfolio regularly
|
383 |
+
- Use stop-loss orders to limit potential losses
|
384 |
+
- Never risk more than you can afford to lose
|
385 |
+
|
386 |
+
### API Rate Limits
|
387 |
+
- Alpaca has rate limits on API calls
|
388 |
+
- The system includes built-in delays to respect these limits
|
389 |
+
- Monitor your API usage in the Alpaca dashboard
|
390 |
+
|
391 |
+
## 🤝 Contributing
|
392 |
|
393 |
1. Fork the repository
|
394 |
2. Create a feature branch
|
395 |
+
3. Make your changes
|
396 |
+
4. Add tests for new functionality
|
397 |
5. Submit a pull request
|
398 |
|
399 |
+
## 📄 License
|
|
|
|
|
|
|
400 |
|
401 |
+
This project is licensed under the MIT License - see the LICENSE file for details.
|
|
|
402 |
|
403 |
+
## 🆘 Support
|
|
|
404 |
|
405 |
+
- **Documentation**: Check the logs and configuration files
|
406 |
+
- **Issues**: Report bugs and feature requests on GitHub
|
407 |
+
- **Alpaca Support**: Contact Alpaca for API-related issues
|
408 |
+
- **Community**: Join our Discord/Telegram for discussions
|
|
|
|
|
|
|
409 |
|
410 |
+
## 🔗 Useful Links
|
411 |
|
412 |
+
- [Alpaca Markets Documentation](https://alpaca.markets/docs/)
|
413 |
+
- [FinRL Documentation](https://finrl.readthedocs.io/)
|
414 |
+
- [Stable Baselines3 Documentation](https://stable-baselines3.readthedocs.io/)
|
415 |
+
- [Gymnasium Documentation](https://gymnasium.farama.org/)
|
416 |
|
417 |
+
---
|
418 |
|
419 |
+
**Disclaimer**: This software is for educational and research purposes. Trading involves substantial risk of loss and is not suitable for all investors. Past performance does not guarantee future results. Always consult with a financial advisor before making investment decisions.
|
agentic_ai_system/alpaca_broker.py
ADDED
@@ -0,0 +1,406 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""
|
2 |
+
Alpaca Broker Integration for Algorithmic Trading
|
3 |
+
|
4 |
+
This module provides integration with Alpaca Markets for real trading capabilities,
|
5 |
+
including paper trading and live trading support.
|
6 |
+
"""
|
7 |
+
|
8 |
+
import os
|
9 |
+
import logging
|
10 |
+
import time
|
11 |
+
from typing import Dict, Any, Optional, List
|
12 |
+
from datetime import datetime, timedelta
|
13 |
+
import pandas as pd
|
14 |
+
import numpy as np
|
15 |
+
|
16 |
+
try:
|
17 |
+
from alpaca.trading.client import TradingClient
|
18 |
+
from alpaca.trading.requests import MarketOrderRequest, LimitOrderRequest
|
19 |
+
from alpaca.trading.enums import OrderSide, TimeInForce
|
20 |
+
from alpaca.data.historical import StockHistoricalDataClient
|
21 |
+
from alpaca.data.requests import StockBarsRequest
|
22 |
+
from alpaca.data.timeframe import TimeFrame
|
23 |
+
from alpaca.data.enums import Adjustment
|
24 |
+
from alpaca.account import Account
|
25 |
+
ALPACA_AVAILABLE = True
|
26 |
+
except ImportError:
|
27 |
+
ALPACA_AVAILABLE = False
|
28 |
+
logging.warning("Alpaca SDK not available. Install with: pip install alpaca-py")
|
29 |
+
|
30 |
+
logger = logging.getLogger(__name__)
|
31 |
+
|
32 |
+
|
33 |
+
class AlpacaBroker:
|
34 |
+
"""
|
35 |
+
Alpaca broker integration for algorithmic trading
|
36 |
+
"""
|
37 |
+
|
38 |
+
def __init__(self, config: Dict[str, Any]):
|
39 |
+
"""
|
40 |
+
Initialize Alpaca broker connection
|
41 |
+
|
42 |
+
Args:
|
43 |
+
config: Configuration dictionary containing Alpaca settings
|
44 |
+
"""
|
45 |
+
self.config = config
|
46 |
+
self.alpaca_config = config.get('alpaca', {})
|
47 |
+
|
48 |
+
# Get API credentials from environment or config
|
49 |
+
self.api_key = os.getenv('ALPACA_API_KEY') or self.alpaca_config.get('api_key', '')
|
50 |
+
self.secret_key = os.getenv('ALPACA_SECRET_KEY') or self.alpaca_config.get('secret_key', '')
|
51 |
+
|
52 |
+
# Determine if using paper or live trading
|
53 |
+
self.paper_trading = self.alpaca_config.get('paper_trading', True)
|
54 |
+
self.account_type = self.alpaca_config.get('account_type', 'paper')
|
55 |
+
|
56 |
+
# Set URLs based on account type
|
57 |
+
if self.account_type == 'live':
|
58 |
+
self.base_url = self.alpaca_config.get('live_url', 'https://api.alpaca.markets')
|
59 |
+
self.data_url = self.alpaca_config.get('data_url', 'https://data.alpaca.markets')
|
60 |
+
else:
|
61 |
+
self.base_url = self.alpaca_config.get('base_url', 'https://paper-api.alpaca.markets')
|
62 |
+
self.data_url = self.alpaca_config.get('data_url', 'https://data.alpaca.markets')
|
63 |
+
|
64 |
+
# Initialize clients
|
65 |
+
self.trading_client = None
|
66 |
+
self.data_client = None
|
67 |
+
self.account = None
|
68 |
+
|
69 |
+
# Initialize connection
|
70 |
+
self._initialize_connection()
|
71 |
+
|
72 |
+
logger.info(f"Alpaca broker initialized for {self.account_type} trading")
|
73 |
+
|
74 |
+
def _initialize_connection(self):
|
75 |
+
"""Initialize Alpaca API connections"""
|
76 |
+
if not ALPACA_AVAILABLE:
|
77 |
+
logger.error("Alpaca SDK not available")
|
78 |
+
return False
|
79 |
+
|
80 |
+
if not self.api_key or not self.secret_key:
|
81 |
+
logger.error("Alpaca API credentials not provided")
|
82 |
+
return False
|
83 |
+
|
84 |
+
try:
|
85 |
+
# Initialize trading client
|
86 |
+
self.trading_client = TradingClient(
|
87 |
+
api_key=self.api_key,
|
88 |
+
secret_key=self.secret_key,
|
89 |
+
paper=self.paper_trading
|
90 |
+
)
|
91 |
+
|
92 |
+
# Initialize data client
|
93 |
+
self.data_client = StockHistoricalDataClient(
|
94 |
+
api_key=self.api_key,
|
95 |
+
secret_key=self.secret_key
|
96 |
+
)
|
97 |
+
|
98 |
+
# Get account information
|
99 |
+
self.account = self.trading_client.get_account()
|
100 |
+
|
101 |
+
logger.info(f"Connected to Alpaca {self.account_type} account: {self.account.id}")
|
102 |
+
logger.info(f"Account status: {self.account.status}")
|
103 |
+
logger.info(f"Buying power: ${self.account.buying_power}")
|
104 |
+
|
105 |
+
return True
|
106 |
+
|
107 |
+
except Exception as e:
|
108 |
+
logger.error(f"Failed to initialize Alpaca connection: {e}")
|
109 |
+
return False
|
110 |
+
|
111 |
+
def get_account_info(self) -> Dict[str, Any]:
|
112 |
+
"""Get account information"""
|
113 |
+
if not self.account:
|
114 |
+
return {}
|
115 |
+
|
116 |
+
return {
|
117 |
+
'account_id': self.account.id,
|
118 |
+
'status': self.account.status,
|
119 |
+
'buying_power': float(self.account.buying_power),
|
120 |
+
'cash': float(self.account.cash),
|
121 |
+
'portfolio_value': float(self.account.portfolio_value),
|
122 |
+
'equity': float(self.account.equity),
|
123 |
+
'daytrade_count': self.account.daytrade_count,
|
124 |
+
'trading_blocked': self.account.trading_blocked,
|
125 |
+
'transfers_blocked': self.account.transfers_blocked,
|
126 |
+
'account_blocked': self.account.account_blocked
|
127 |
+
}
|
128 |
+
|
129 |
+
def get_positions(self) -> List[Dict[str, Any]]:
|
130 |
+
"""Get current positions"""
|
131 |
+
if not self.trading_client:
|
132 |
+
return []
|
133 |
+
|
134 |
+
try:
|
135 |
+
positions = self.trading_client.get_all_positions()
|
136 |
+
return [
|
137 |
+
{
|
138 |
+
'symbol': pos.symbol,
|
139 |
+
'quantity': int(pos.qty),
|
140 |
+
'market_value': float(pos.market_value),
|
141 |
+
'unrealized_pl': float(pos.unrealized_pl),
|
142 |
+
'current_price': float(pos.current_price)
|
143 |
+
}
|
144 |
+
for pos in positions
|
145 |
+
]
|
146 |
+
except Exception as e:
|
147 |
+
logger.error(f"Error getting positions: {e}")
|
148 |
+
return []
|
149 |
+
|
150 |
+
def get_market_data(self, symbol: str, timeframe: str = '1Min',
|
151 |
+
start_date: Optional[str] = None,
|
152 |
+
end_date: Optional[str] = None,
|
153 |
+
limit: int = 1000) -> Optional[pd.DataFrame]:
|
154 |
+
"""
|
155 |
+
Get historical market data from Alpaca
|
156 |
+
|
157 |
+
Args:
|
158 |
+
symbol: Stock symbol
|
159 |
+
timeframe: Timeframe for data (1Min, 5Min, 15Min, 1Hour, 1Day)
|
160 |
+
start_date: Start date (ISO format)
|
161 |
+
end_date: End date (ISO format)
|
162 |
+
limit: Maximum number of bars to return
|
163 |
+
|
164 |
+
Returns:
|
165 |
+
DataFrame with OHLCV data or None if error
|
166 |
+
"""
|
167 |
+
if not self.data_client:
|
168 |
+
logger.error("Data client not initialized")
|
169 |
+
return None
|
170 |
+
|
171 |
+
try:
|
172 |
+
# Convert timeframe string to TimeFrame enum
|
173 |
+
tf_map = {
|
174 |
+
'1Min': TimeFrame.Minute,
|
175 |
+
'5Min': TimeFrame.Minute_5,
|
176 |
+
'15Min': TimeFrame.Minute_15,
|
177 |
+
'1Hour': TimeFrame.Hour,
|
178 |
+
'1Day': TimeFrame.Day
|
179 |
+
}
|
180 |
+
|
181 |
+
time_frame = tf_map.get(timeframe, TimeFrame.Minute)
|
182 |
+
|
183 |
+
# Set default dates if not provided
|
184 |
+
if not end_date:
|
185 |
+
end_date = datetime.now().isoformat()
|
186 |
+
if not start_date:
|
187 |
+
start_date = (datetime.now() - timedelta(days=30)).isoformat()
|
188 |
+
|
189 |
+
# Create request
|
190 |
+
request = StockBarsRequest(
|
191 |
+
symbol_or_symbols=symbol,
|
192 |
+
timeframe=time_frame,
|
193 |
+
start=start_date,
|
194 |
+
end=end_date,
|
195 |
+
adjustment=Adjustment.ALL,
|
196 |
+
limit=limit
|
197 |
+
)
|
198 |
+
|
199 |
+
# Get data
|
200 |
+
bars = self.data_client.get_stock_bars(request)
|
201 |
+
|
202 |
+
if bars and symbol in bars:
|
203 |
+
# Convert to DataFrame
|
204 |
+
df = bars[symbol].df
|
205 |
+
df = df.reset_index()
|
206 |
+
df.columns = ['timestamp', 'open', 'high', 'low', 'close', 'volume', 'vwap', 'trade_count']
|
207 |
+
|
208 |
+
# Select relevant columns
|
209 |
+
df = df[['timestamp', 'open', 'high', 'low', 'close', 'volume']]
|
210 |
+
|
211 |
+
logger.info(f"Retrieved {len(df)} bars for {symbol}")
|
212 |
+
return df
|
213 |
+
else:
|
214 |
+
logger.warning(f"No data returned for {symbol}")
|
215 |
+
return None
|
216 |
+
|
217 |
+
except Exception as e:
|
218 |
+
logger.error(f"Error getting market data for {symbol}: {e}")
|
219 |
+
return None
|
220 |
+
|
221 |
+
def place_market_order(self, symbol: str, quantity: int, side: str) -> Dict[str, Any]:
|
222 |
+
"""
|
223 |
+
Place a market order
|
224 |
+
|
225 |
+
Args:
|
226 |
+
symbol: Stock symbol
|
227 |
+
quantity: Number of shares
|
228 |
+
side: 'buy' or 'sell'
|
229 |
+
|
230 |
+
Returns:
|
231 |
+
Order result dictionary
|
232 |
+
"""
|
233 |
+
if not self.trading_client:
|
234 |
+
return {'success': False, 'error': 'Trading client not initialized'}
|
235 |
+
|
236 |
+
try:
|
237 |
+
# Convert side to Alpaca enum
|
238 |
+
order_side = OrderSide.BUY if side.lower() == 'buy' else OrderSide.SELL
|
239 |
+
|
240 |
+
# Create order request
|
241 |
+
order_request = MarketOrderRequest(
|
242 |
+
symbol=symbol,
|
243 |
+
qty=quantity,
|
244 |
+
side=order_side,
|
245 |
+
time_in_force=TimeInForce.DAY
|
246 |
+
)
|
247 |
+
|
248 |
+
# Place order
|
249 |
+
order = self.trading_client.submit_order(order_request)
|
250 |
+
|
251 |
+
# Wait for order to be processed
|
252 |
+
time.sleep(1)
|
253 |
+
|
254 |
+
# Get updated order status
|
255 |
+
order = self.trading_client.get_order_by_id(order.id)
|
256 |
+
|
257 |
+
result = {
|
258 |
+
'success': order.status == 'filled',
|
259 |
+
'order_id': order.id,
|
260 |
+
'status': order.status,
|
261 |
+
'symbol': order.symbol,
|
262 |
+
'quantity': int(order.qty),
|
263 |
+
'side': order.side.value,
|
264 |
+
'filled_quantity': int(order.filled_qty) if order.filled_qty else 0,
|
265 |
+
'filled_avg_price': float(order.filled_avg_price) if order.filled_avg_price else 0,
|
266 |
+
'submitted_at': order.submitted_at.isoformat() if order.submitted_at else None,
|
267 |
+
'filled_at': order.filled_at.isoformat() if order.filled_at else None,
|
268 |
+
'error': None
|
269 |
+
}
|
270 |
+
|
271 |
+
if order.status == 'rejected':
|
272 |
+
result['error'] = 'Order rejected'
|
273 |
+
|
274 |
+
logger.info(f"Market order placed: {result}")
|
275 |
+
return result
|
276 |
+
|
277 |
+
except Exception as e:
|
278 |
+
logger.error(f"Error placing market order: {e}")
|
279 |
+
return {
|
280 |
+
'success': False,
|
281 |
+
'order_id': None,
|
282 |
+
'status': 'error',
|
283 |
+
'error': str(e)
|
284 |
+
}
|
285 |
+
|
286 |
+
def place_limit_order(self, symbol: str, quantity: int, side: str,
|
287 |
+
limit_price: float) -> Dict[str, Any]:
|
288 |
+
"""
|
289 |
+
Place a limit order
|
290 |
+
|
291 |
+
Args:
|
292 |
+
symbol: Stock symbol
|
293 |
+
quantity: Number of shares
|
294 |
+
side: 'buy' or 'sell'
|
295 |
+
limit_price: Limit price for the order
|
296 |
+
|
297 |
+
Returns:
|
298 |
+
Order result dictionary
|
299 |
+
"""
|
300 |
+
if not self.trading_client:
|
301 |
+
return {'success': False, 'error': 'Trading client not initialized'}
|
302 |
+
|
303 |
+
try:
|
304 |
+
# Convert side to Alpaca enum
|
305 |
+
order_side = OrderSide.BUY if side.lower() == 'buy' else OrderSide.SELL
|
306 |
+
|
307 |
+
# Create order request
|
308 |
+
order_request = LimitOrderRequest(
|
309 |
+
symbol=symbol,
|
310 |
+
qty=quantity,
|
311 |
+
side=order_side,
|
312 |
+
time_in_force=TimeInForce.DAY,
|
313 |
+
limit_price=limit_price
|
314 |
+
)
|
315 |
+
|
316 |
+
# Place order
|
317 |
+
order = self.trading_client.submit_order(order_request)
|
318 |
+
|
319 |
+
result = {
|
320 |
+
'success': True,
|
321 |
+
'order_id': order.id,
|
322 |
+
'status': order.status,
|
323 |
+
'symbol': order.symbol,
|
324 |
+
'quantity': int(order.qty),
|
325 |
+
'side': order.side.value,
|
326 |
+
'limit_price': float(order.limit_price),
|
327 |
+
'submitted_at': order.submitted_at.isoformat() if order.submitted_at else None,
|
328 |
+
'error': None
|
329 |
+
}
|
330 |
+
|
331 |
+
logger.info(f"Limit order placed: {result}")
|
332 |
+
return result
|
333 |
+
|
334 |
+
except Exception as e:
|
335 |
+
logger.error(f"Error placing limit order: {e}")
|
336 |
+
return {
|
337 |
+
'success': False,
|
338 |
+
'order_id': None,
|
339 |
+
'status': 'error',
|
340 |
+
'error': str(e)
|
341 |
+
}
|
342 |
+
|
343 |
+
def cancel_order(self, order_id: str) -> Dict[str, Any]:
|
344 |
+
"""Cancel an existing order"""
|
345 |
+
if not self.trading_client:
|
346 |
+
return {'success': False, 'error': 'Trading client not initialized'}
|
347 |
+
|
348 |
+
try:
|
349 |
+
self.trading_client.cancel_order_by_id(order_id)
|
350 |
+
return {'success': True, 'order_id': order_id, 'status': 'cancelled'}
|
351 |
+
except Exception as e:
|
352 |
+
logger.error(f"Error cancelling order {order_id}: {e}")
|
353 |
+
return {'success': False, 'order_id': order_id, 'error': str(e)}
|
354 |
+
|
355 |
+
def get_orders(self, status: str = 'all') -> List[Dict[str, Any]]:
|
356 |
+
"""Get order history"""
|
357 |
+
if not self.trading_client:
|
358 |
+
return []
|
359 |
+
|
360 |
+
try:
|
361 |
+
orders = self.trading_client.get_orders(status=status)
|
362 |
+
return [
|
363 |
+
{
|
364 |
+
'order_id': order.id,
|
365 |
+
'symbol': order.symbol,
|
366 |
+
'quantity': int(order.qty),
|
367 |
+
'side': order.side.value,
|
368 |
+
'status': order.status,
|
369 |
+
'order_type': order.order_type.value,
|
370 |
+
'submitted_at': order.submitted_at.isoformat() if order.submitted_at else None,
|
371 |
+
'filled_at': order.filled_at.isoformat() if order.filled_at else None
|
372 |
+
}
|
373 |
+
for order in orders
|
374 |
+
]
|
375 |
+
except Exception as e:
|
376 |
+
logger.error(f"Error getting orders: {e}")
|
377 |
+
return []
|
378 |
+
|
379 |
+
def is_market_open(self) -> bool:
|
380 |
+
"""Check if market is currently open"""
|
381 |
+
if not self.trading_client:
|
382 |
+
return False
|
383 |
+
|
384 |
+
try:
|
385 |
+
clock = self.trading_client.get_clock()
|
386 |
+
return clock.is_open
|
387 |
+
except Exception as e:
|
388 |
+
logger.error(f"Error checking market status: {e}")
|
389 |
+
return False
|
390 |
+
|
391 |
+
def get_market_hours(self) -> Dict[str, Any]:
|
392 |
+
"""Get market hours information"""
|
393 |
+
if not self.trading_client:
|
394 |
+
return {}
|
395 |
+
|
396 |
+
try:
|
397 |
+
clock = self.trading_client.get_clock()
|
398 |
+
return {
|
399 |
+
'is_open': clock.is_open,
|
400 |
+
'next_open': clock.next_open.isoformat() if clock.next_open else None,
|
401 |
+
'next_close': clock.next_close.isoformat() if clock.next_close else None,
|
402 |
+
'timestamp': clock.timestamp.isoformat() if clock.timestamp else None
|
403 |
+
}
|
404 |
+
except Exception as e:
|
405 |
+
logger.error(f"Error getting market hours: {e}")
|
406 |
+
return {}
|
agentic_ai_system/data_ingestion.py
CHANGED
@@ -1,149 +1,292 @@
|
|
1 |
import pandas as pd
|
|
|
2 |
import logging
|
3 |
import os
|
4 |
-
from typing import Dict, Any
|
5 |
-
from
|
6 |
|
7 |
logger = logging.getLogger(__name__)
|
8 |
|
9 |
-
def load_data(config: Dict[str, Any]) -> pd.DataFrame:
|
10 |
"""
|
11 |
-
Load market data
|
12 |
|
13 |
Args:
|
14 |
config: Configuration dictionary
|
15 |
|
16 |
Returns:
|
17 |
-
DataFrame with market data
|
18 |
"""
|
19 |
-
logger.info("Starting data ingestion process")
|
20 |
-
|
21 |
try:
|
22 |
-
data_source = config['data_source']
|
23 |
-
|
24 |
|
25 |
-
if
|
|
|
|
|
26 |
return _load_csv_data(config)
|
27 |
-
elif
|
28 |
-
return
|
29 |
else:
|
30 |
-
|
|
|
31 |
|
32 |
except Exception as e:
|
33 |
-
logger.error(f"Error
|
34 |
-
|
35 |
-
|
36 |
-
def _load_csv_data(config: Dict[str, Any]) -> pd.DataFrame:
|
37 |
-
"""Load data from CSV file"""
|
38 |
-
path = config['data_source']['path']
|
39 |
-
|
40 |
-
if not os.path.exists(path):
|
41 |
-
logger.warning(f"CSV file not found at {path}, generating synthetic data instead")
|
42 |
-
return _generate_synthetic_data(config)
|
43 |
-
|
44 |
-
logger.info(f"Loading data from CSV: {path}")
|
45 |
-
df = pd.read_csv(path)
|
46 |
-
|
47 |
-
# Validate data
|
48 |
-
required_columns = ['timestamp', 'open', 'high', 'low', 'close', 'volume']
|
49 |
-
missing_columns = [col for col in required_columns if col not in df.columns]
|
50 |
-
|
51 |
-
if missing_columns:
|
52 |
-
logger.warning(f"Missing columns in CSV: {missing_columns}")
|
53 |
-
logger.info("Generating synthetic data instead")
|
54 |
-
return _generate_synthetic_data(config)
|
55 |
-
|
56 |
-
# Convert timestamp to datetime
|
57 |
-
df['timestamp'] = pd.to_datetime(df['timestamp'])
|
58 |
-
|
59 |
-
logger.info(f"Successfully loaded {len(df)} data points from CSV")
|
60 |
-
return df
|
61 |
|
62 |
-
def
|
63 |
-
"""
|
64 |
-
logger.info("Generating synthetic market data")
|
65 |
-
|
66 |
try:
|
67 |
-
|
68 |
-
data_path = config['synthetic_data']['data_path']
|
69 |
-
os.makedirs(os.path.dirname(data_path), exist_ok=True)
|
70 |
|
71 |
-
# Initialize
|
72 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
73 |
|
74 |
-
#
|
75 |
-
|
76 |
-
symbol=
|
77 |
-
|
78 |
-
|
79 |
-
frequency=config['trading']['timeframe']
|
80 |
)
|
81 |
|
82 |
-
|
83 |
-
|
84 |
-
|
85 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
86 |
|
87 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
88 |
|
89 |
except Exception as e:
|
90 |
-
logger.error(f"Error
|
91 |
-
|
92 |
|
93 |
-
def
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
94 |
"""
|
95 |
-
Validate
|
96 |
|
97 |
Args:
|
98 |
-
|
99 |
|
100 |
Returns:
|
101 |
True if data is valid, False otherwise
|
102 |
"""
|
103 |
-
logger.info("Validating data quality")
|
104 |
-
|
105 |
try:
|
106 |
-
|
|
|
|
|
|
|
|
|
107 |
required_columns = ['timestamp', 'open', 'high', 'low', 'close', 'volume']
|
108 |
-
missing_columns = [col for col in required_columns if col not in
|
109 |
|
110 |
if missing_columns:
|
111 |
logger.error(f"Missing required columns: {missing_columns}")
|
112 |
return False
|
113 |
|
114 |
-
# Check for
|
115 |
-
|
116 |
-
if
|
117 |
-
logger.warning(f"Found
|
|
|
|
|
|
|
118 |
|
119 |
# Check for negative prices
|
120 |
price_columns = ['open', 'high', 'low', 'close']
|
121 |
-
negative_prices =
|
122 |
-
if negative_prices:
|
123 |
logger.error("Found negative prices in data")
|
124 |
return False
|
125 |
|
126 |
-
# Check for
|
127 |
-
|
128 |
-
|
129 |
-
|
130 |
|
131 |
# Check OHLC consistency
|
132 |
invalid_ohlc = (
|
133 |
-
(
|
134 |
-
(
|
135 |
-
(
|
136 |
-
(
|
137 |
-
(
|
138 |
)
|
139 |
|
140 |
if invalid_ohlc.any():
|
141 |
-
logger.error(
|
142 |
return False
|
143 |
|
144 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
145 |
return True
|
146 |
|
147 |
except Exception as e:
|
148 |
-
logger.error(f"Error
|
149 |
return False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
import pandas as pd
|
2 |
+
import numpy as np
|
3 |
import logging
|
4 |
import os
|
5 |
+
from typing import Dict, Any, Optional
|
6 |
+
from datetime import datetime, timedelta
|
7 |
|
8 |
logger = logging.getLogger(__name__)
|
9 |
|
10 |
+
def load_data(config: Dict[str, Any]) -> Optional[pd.DataFrame]:
|
11 |
"""
|
12 |
+
Load market data based on configuration.
|
13 |
|
14 |
Args:
|
15 |
config: Configuration dictionary
|
16 |
|
17 |
Returns:
|
18 |
+
DataFrame with market data or None if error
|
19 |
"""
|
|
|
|
|
20 |
try:
|
21 |
+
data_source = config['data_source']['type']
|
22 |
+
logger.info(f"Loading data from source: {data_source}")
|
23 |
|
24 |
+
if data_source == 'alpaca':
|
25 |
+
return _load_alpaca_data(config)
|
26 |
+
elif data_source == 'csv':
|
27 |
return _load_csv_data(config)
|
28 |
+
elif data_source == 'synthetic':
|
29 |
+
return _load_synthetic_data(config)
|
30 |
else:
|
31 |
+
logger.error(f"Unsupported data source: {data_source}")
|
32 |
+
return None
|
33 |
|
34 |
except Exception as e:
|
35 |
+
logger.error(f"Error loading data: {e}")
|
36 |
+
return None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
37 |
|
38 |
+
def _load_alpaca_data(config: Dict[str, Any]) -> Optional[pd.DataFrame]:
|
39 |
+
"""Load market data from Alpaca"""
|
|
|
|
|
40 |
try:
|
41 |
+
from .alpaca_broker import AlpacaBroker
|
|
|
|
|
42 |
|
43 |
+
# Initialize Alpaca broker
|
44 |
+
alpaca_broker = AlpacaBroker(config)
|
45 |
+
|
46 |
+
# Get symbol and timeframe from config
|
47 |
+
symbol = config['trading']['symbol']
|
48 |
+
timeframe = config['trading']['timeframe']
|
49 |
+
|
50 |
+
# Convert timeframe to Alpaca format
|
51 |
+
tf_map = {
|
52 |
+
'1m': '1Min',
|
53 |
+
'5m': '5Min',
|
54 |
+
'15m': '15Min',
|
55 |
+
'1h': '1Hour',
|
56 |
+
'1d': '1Day'
|
57 |
+
}
|
58 |
+
alpaca_timeframe = tf_map.get(timeframe, '1Min')
|
59 |
|
60 |
+
# Get market data
|
61 |
+
data = alpaca_broker.get_market_data(
|
62 |
+
symbol=symbol,
|
63 |
+
timeframe=alpaca_timeframe,
|
64 |
+
limit=1000
|
|
|
65 |
)
|
66 |
|
67 |
+
if data is not None and not data.empty:
|
68 |
+
logger.info(f"Loaded {len(data)} data points from Alpaca for {symbol}")
|
69 |
+
return data
|
70 |
+
else:
|
71 |
+
logger.error(f"No data returned from Alpaca for {symbol}")
|
72 |
+
return None
|
73 |
+
|
74 |
+
except Exception as e:
|
75 |
+
logger.error(f"Error loading Alpaca data: {e}")
|
76 |
+
return None
|
77 |
+
|
78 |
+
def _load_csv_data(config: Dict[str, Any]) -> Optional[pd.DataFrame]:
|
79 |
+
"""Load market data from CSV file"""
|
80 |
+
try:
|
81 |
+
file_path = config['data_source']['path']
|
82 |
|
83 |
+
if not os.path.exists(file_path):
|
84 |
+
logger.error(f"CSV file not found: {file_path}")
|
85 |
+
return None
|
86 |
+
|
87 |
+
# Load CSV data
|
88 |
+
data = pd.read_csv(file_path)
|
89 |
+
|
90 |
+
# Ensure required columns exist
|
91 |
+
required_columns = ['timestamp', 'open', 'high', 'low', 'close', 'volume']
|
92 |
+
missing_columns = [col for col in required_columns if col not in data.columns]
|
93 |
+
|
94 |
+
if missing_columns:
|
95 |
+
logger.error(f"Missing required columns: {missing_columns}")
|
96 |
+
return None
|
97 |
+
|
98 |
+
# Convert timestamp to datetime
|
99 |
+
data['timestamp'] = pd.to_datetime(data['timestamp'])
|
100 |
+
|
101 |
+
# Sort by timestamp
|
102 |
+
data = data.sort_values('timestamp').reset_index(drop=True)
|
103 |
+
|
104 |
+
logger.info(f"Loaded {len(data)} data points from CSV: {file_path}")
|
105 |
+
return data
|
106 |
|
107 |
except Exception as e:
|
108 |
+
logger.error(f"Error loading CSV data: {e}")
|
109 |
+
return None
|
110 |
|
111 |
+
def _load_synthetic_data(config: Dict[str, Any]) -> Optional[pd.DataFrame]:
|
112 |
+
"""Load or generate synthetic market data"""
|
113 |
+
try:
|
114 |
+
synthetic_config = config.get('synthetic_data', {})
|
115 |
+
data_path = synthetic_config.get('data_path', 'data/synthetic_market_data.csv')
|
116 |
+
|
117 |
+
# Check if synthetic data file exists
|
118 |
+
if os.path.exists(data_path):
|
119 |
+
logger.info(f"Loading existing synthetic data from: {data_path}")
|
120 |
+
return _load_csv_data({'data_source': {'path': data_path}})
|
121 |
+
|
122 |
+
# Generate new synthetic data
|
123 |
+
logger.info("Generating new synthetic market data")
|
124 |
+
from .synthetic_data_generator import SyntheticDataGenerator
|
125 |
+
|
126 |
+
generator = SyntheticDataGenerator(config)
|
127 |
+
data = generator.generate_data()
|
128 |
+
|
129 |
+
# Save generated data
|
130 |
+
os.makedirs(os.path.dirname(data_path), exist_ok=True)
|
131 |
+
data.to_csv(data_path, index=False)
|
132 |
+
logger.info(f"Saved synthetic data to: {data_path}")
|
133 |
+
|
134 |
+
return data
|
135 |
+
|
136 |
+
except Exception as e:
|
137 |
+
logger.error(f"Error loading synthetic data: {e}")
|
138 |
+
return None
|
139 |
+
|
140 |
+
def validate_data(data: pd.DataFrame) -> bool:
|
141 |
"""
|
142 |
+
Validate market data quality.
|
143 |
|
144 |
Args:
|
145 |
+
data: DataFrame with market data
|
146 |
|
147 |
Returns:
|
148 |
True if data is valid, False otherwise
|
149 |
"""
|
|
|
|
|
150 |
try:
|
151 |
+
if data is None or data.empty:
|
152 |
+
logger.error("Data is None or empty")
|
153 |
+
return False
|
154 |
+
|
155 |
+
# Check required columns
|
156 |
required_columns = ['timestamp', 'open', 'high', 'low', 'close', 'volume']
|
157 |
+
missing_columns = [col for col in required_columns if col not in data.columns]
|
158 |
|
159 |
if missing_columns:
|
160 |
logger.error(f"Missing required columns: {missing_columns}")
|
161 |
return False
|
162 |
|
163 |
+
# Check for NaN values
|
164 |
+
nan_counts = data[required_columns].isna().sum()
|
165 |
+
if nan_counts.sum() > 0:
|
166 |
+
logger.warning(f"Found NaN values: {nan_counts.to_dict()}")
|
167 |
+
# Remove rows with NaN values
|
168 |
+
data.dropna(subset=required_columns, inplace=True)
|
169 |
+
logger.info(f"Removed {nan_counts.sum()} rows with NaN values")
|
170 |
|
171 |
# Check for negative prices
|
172 |
price_columns = ['open', 'high', 'low', 'close']
|
173 |
+
negative_prices = data[price_columns] < 0
|
174 |
+
if negative_prices.any().any():
|
175 |
logger.error("Found negative prices in data")
|
176 |
return False
|
177 |
|
178 |
+
# Check for zero volumes
|
179 |
+
zero_volumes = data['volume'] == 0
|
180 |
+
if zero_volumes.sum() > len(data) * 0.5: # More than 50% zero volumes
|
181 |
+
logger.warning("High percentage of zero volumes detected")
|
182 |
|
183 |
# Check OHLC consistency
|
184 |
invalid_ohlc = (
|
185 |
+
(data['high'] < data['low']) |
|
186 |
+
(data['open'] > data['high']) |
|
187 |
+
(data['close'] > data['high']) |
|
188 |
+
(data['open'] < data['low']) |
|
189 |
+
(data['close'] < data['low'])
|
190 |
)
|
191 |
|
192 |
if invalid_ohlc.any():
|
193 |
+
logger.error("Found invalid OHLC relationships")
|
194 |
return False
|
195 |
|
196 |
+
# Check timestamp consistency
|
197 |
+
if 'timestamp' in data.columns:
|
198 |
+
timestamps = pd.to_datetime(data['timestamp'])
|
199 |
+
if not timestamps.is_monotonic_increasing:
|
200 |
+
logger.warning("Timestamps are not in ascending order")
|
201 |
+
data = data.sort_values('timestamp').reset_index(drop=True)
|
202 |
+
|
203 |
+
logger.info(f"Data validation passed: {len(data)} valid records")
|
204 |
return True
|
205 |
|
206 |
except Exception as e:
|
207 |
+
logger.error(f"Error validating data: {e}")
|
208 |
return False
|
209 |
+
|
210 |
+
def add_technical_indicators(data: pd.DataFrame) -> pd.DataFrame:
|
211 |
+
"""
|
212 |
+
Add technical indicators to market data.
|
213 |
+
|
214 |
+
Args:
|
215 |
+
data: DataFrame with OHLCV data
|
216 |
+
|
217 |
+
Returns:
|
218 |
+
DataFrame with technical indicators added
|
219 |
+
"""
|
220 |
+
try:
|
221 |
+
df = data.copy()
|
222 |
+
|
223 |
+
# Simple Moving Averages
|
224 |
+
df['sma_20'] = df['close'].rolling(window=20).mean()
|
225 |
+
df['sma_50'] = df['close'].rolling(window=50).mean()
|
226 |
+
df['sma_200'] = df['close'].rolling(window=200).mean()
|
227 |
+
|
228 |
+
# Exponential Moving Averages
|
229 |
+
df['ema_12'] = df['close'].ewm(span=12).mean()
|
230 |
+
df['ema_26'] = df['close'].ewm(span=26).mean()
|
231 |
+
|
232 |
+
# MACD
|
233 |
+
df['macd'] = df['ema_12'] - df['ema_26']
|
234 |
+
df['macd_signal'] = df['macd'].ewm(span=9).mean()
|
235 |
+
df['macd_histogram'] = df['macd'] - df['macd_signal']
|
236 |
+
|
237 |
+
# RSI
|
238 |
+
delta = df['close'].diff()
|
239 |
+
gain = (delta.where(delta > 0, 0)).rolling(window=14).mean()
|
240 |
+
loss = (-delta.where(delta < 0, 0)).rolling(window=14).mean()
|
241 |
+
rs = gain / loss
|
242 |
+
df['rsi'] = 100 - (100 / (1 + rs))
|
243 |
+
|
244 |
+
# Bollinger Bands
|
245 |
+
df['bb_middle'] = df['close'].rolling(window=20).mean()
|
246 |
+
bb_std = df['close'].rolling(window=20).std()
|
247 |
+
df['bb_upper'] = df['bb_middle'] + (bb_std * 2)
|
248 |
+
df['bb_lower'] = df['bb_middle'] - (bb_std * 2)
|
249 |
+
|
250 |
+
# Average True Range (ATR)
|
251 |
+
high_low = df['high'] - df['low']
|
252 |
+
high_close = np.abs(df['high'] - df['close'].shift())
|
253 |
+
low_close = np.abs(df['low'] - df['close'].shift())
|
254 |
+
true_range = np.maximum(high_low, np.maximum(high_close, low_close))
|
255 |
+
df['atr'] = true_range.rolling(window=14).mean()
|
256 |
+
|
257 |
+
# Volume indicators
|
258 |
+
df['volume_sma'] = df['volume'].rolling(window=20).mean()
|
259 |
+
df['volume_ratio'] = df['volume'] / df['volume_sma']
|
260 |
+
|
261 |
+
# Price momentum
|
262 |
+
df['price_change'] = df['close'].pct_change()
|
263 |
+
df['price_change_5'] = df['close'].pct_change(periods=5)
|
264 |
+
df['price_change_20'] = df['close'].pct_change(periods=20)
|
265 |
+
|
266 |
+
logger.info("Technical indicators added successfully")
|
267 |
+
return df
|
268 |
+
|
269 |
+
except Exception as e:
|
270 |
+
logger.error(f"Error adding technical indicators: {e}")
|
271 |
+
return data
|
272 |
+
|
273 |
+
def get_latest_data(data: pd.DataFrame, n_periods: int = 100) -> pd.DataFrame:
|
274 |
+
"""
|
275 |
+
Get the latest n periods of data.
|
276 |
+
|
277 |
+
Args:
|
278 |
+
data: DataFrame with market data
|
279 |
+
n_periods: Number of periods to return
|
280 |
+
|
281 |
+
Returns:
|
282 |
+
DataFrame with latest n periods
|
283 |
+
"""
|
284 |
+
try:
|
285 |
+
if len(data) <= n_periods:
|
286 |
+
return data
|
287 |
+
|
288 |
+
return data.tail(n_periods).reset_index(drop=True)
|
289 |
+
|
290 |
+
except Exception as e:
|
291 |
+
logger.error(f"Error getting latest data: {e}")
|
292 |
+
return data
|
agentic_ai_system/execution_agent.py
CHANGED
@@ -10,6 +10,18 @@ class ExecutionAgent(Agent):
|
|
10 |
self.order_size = config['execution']['order_size']
|
11 |
self.execution_delay = config.get('execution', {}).get('delay_ms', 100)
|
12 |
self.success_rate = config.get('execution', {}).get('success_rate', 0.95)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
13 |
self.logger.info(f"Execution agent initialized with {self.broker_api} broker")
|
14 |
|
15 |
def act(self, signal: Dict[str, Any]) -> Dict[str, Any]:
|
@@ -30,8 +42,11 @@ class ExecutionAgent(Agent):
|
|
30 |
self.logger.warning("Invalid signal received, skipping execution")
|
31 |
return self._generate_execution_result(signal, success=False, error="Invalid signal")
|
32 |
|
33 |
-
#
|
34 |
-
|
|
|
|
|
|
|
35 |
|
36 |
# Log execution result
|
37 |
self.log_action(execution_result)
|
@@ -42,6 +57,99 @@ class ExecutionAgent(Agent):
|
|
42 |
self.log_error(e, "Error in order execution")
|
43 |
return self._generate_execution_result(signal, success=False, error=str(e))
|
44 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
45 |
def _validate_signal(self, signal: Dict[str, Any]) -> bool:
|
46 |
"""Validate trading signal"""
|
47 |
try:
|
@@ -74,28 +182,6 @@ class ExecutionAgent(Agent):
|
|
74 |
self.log_error(e, "Error validating signal")
|
75 |
return False
|
76 |
|
77 |
-
def _execute_order(self, signal: Dict[str, Any]) -> Dict[str, Any]:
|
78 |
-
"""Execute order with broker simulation"""
|
79 |
-
try:
|
80 |
-
# Simulate execution delay
|
81 |
-
time.sleep(self.execution_delay / 1000.0)
|
82 |
-
|
83 |
-
# Simulate execution success/failure
|
84 |
-
import random
|
85 |
-
success = random.random() < self.success_rate
|
86 |
-
|
87 |
-
if signal['action'] == 'hold':
|
88 |
-
success = True # Hold actions always succeed
|
89 |
-
|
90 |
-
if success:
|
91 |
-
return self._simulate_successful_execution(signal)
|
92 |
-
else:
|
93 |
-
return self._simulate_failed_execution(signal)
|
94 |
-
|
95 |
-
except Exception as e:
|
96 |
-
self.log_error(e, "Error in order execution simulation")
|
97 |
-
return self._generate_execution_result(signal, success=False, error=str(e))
|
98 |
-
|
99 |
def _simulate_successful_execution(self, signal: Dict[str, Any]) -> Dict[str, Any]:
|
100 |
"""Simulate successful order execution"""
|
101 |
try:
|
|
|
10 |
self.order_size = config['execution']['order_size']
|
11 |
self.execution_delay = config.get('execution', {}).get('delay_ms', 100)
|
12 |
self.success_rate = config.get('execution', {}).get('success_rate', 0.95)
|
13 |
+
|
14 |
+
# Initialize Alpaca broker if configured
|
15 |
+
self.alpaca_broker = None
|
16 |
+
if self.broker_api in ['alpaca_paper', 'alpaca_live']:
|
17 |
+
try:
|
18 |
+
from .alpaca_broker import AlpacaBroker
|
19 |
+
self.alpaca_broker = AlpacaBroker(config)
|
20 |
+
self.logger.info(f"Alpaca broker initialized for {self.broker_api}")
|
21 |
+
except Exception as e:
|
22 |
+
self.logger.error(f"Failed to initialize Alpaca broker: {e}")
|
23 |
+
self.broker_api = 'paper' # Fallback to simulation
|
24 |
+
|
25 |
self.logger.info(f"Execution agent initialized with {self.broker_api} broker")
|
26 |
|
27 |
def act(self, signal: Dict[str, Any]) -> Dict[str, Any]:
|
|
|
42 |
self.logger.warning("Invalid signal received, skipping execution")
|
43 |
return self._generate_execution_result(signal, success=False, error="Invalid signal")
|
44 |
|
45 |
+
# Execute order based on broker type
|
46 |
+
if self.broker_api in ['alpaca_paper', 'alpaca_live'] and self.alpaca_broker:
|
47 |
+
execution_result = self._execute_alpaca_order(signal)
|
48 |
+
else:
|
49 |
+
execution_result = self._execute_simulated_order(signal)
|
50 |
|
51 |
# Log execution result
|
52 |
self.log_action(execution_result)
|
|
|
57 |
self.log_error(e, "Error in order execution")
|
58 |
return self._generate_execution_result(signal, success=False, error=str(e))
|
59 |
|
60 |
+
def _execute_alpaca_order(self, signal: Dict[str, Any]) -> Dict[str, Any]:
|
61 |
+
"""Execute order using Alpaca broker"""
|
62 |
+
try:
|
63 |
+
if signal['action'] == 'hold':
|
64 |
+
return self._generate_execution_result(signal, success=True, error=None)
|
65 |
+
|
66 |
+
# Place market order with Alpaca
|
67 |
+
result = self.alpaca_broker.place_market_order(
|
68 |
+
symbol=signal['symbol'],
|
69 |
+
quantity=signal['quantity'],
|
70 |
+
side=signal['action']
|
71 |
+
)
|
72 |
+
|
73 |
+
# Convert Alpaca result to our format
|
74 |
+
execution_result = {
|
75 |
+
'order_id': result.get('order_id'),
|
76 |
+
'status': result.get('status', 'unknown'),
|
77 |
+
'action': signal['action'],
|
78 |
+
'symbol': signal['symbol'],
|
79 |
+
'quantity': signal['quantity'],
|
80 |
+
'price': result.get('filled_avg_price', signal.get('price', 0)),
|
81 |
+
'execution_time': time.time(),
|
82 |
+
'commission': self._calculate_commission(signal),
|
83 |
+
'total_value': result.get('filled_avg_price', 0) * signal['quantity'] if result.get('filled_avg_price') else 0,
|
84 |
+
'success': result.get('success', False),
|
85 |
+
'error': result.get('error')
|
86 |
+
}
|
87 |
+
|
88 |
+
if execution_result['success']:
|
89 |
+
self.logger.info(f"Alpaca order executed successfully: {execution_result['order_id']}")
|
90 |
+
else:
|
91 |
+
self.logger.error(f"Alpaca order failed: {execution_result['error']}")
|
92 |
+
|
93 |
+
return execution_result
|
94 |
+
|
95 |
+
except Exception as e:
|
96 |
+
self.log_error(e, "Error in Alpaca order execution")
|
97 |
+
return self._generate_execution_result(signal, success=False, error=str(e))
|
98 |
+
|
99 |
+
def _execute_simulated_order(self, signal: Dict[str, Any]) -> Dict[str, Any]:
|
100 |
+
"""Execute order with broker simulation"""
|
101 |
+
try:
|
102 |
+
# Simulate execution delay
|
103 |
+
time.sleep(self.execution_delay / 1000.0)
|
104 |
+
|
105 |
+
# Simulate execution success/failure
|
106 |
+
import random
|
107 |
+
success = random.random() < self.success_rate
|
108 |
+
|
109 |
+
if signal['action'] == 'hold':
|
110 |
+
success = True # Hold actions always succeed
|
111 |
+
|
112 |
+
if success:
|
113 |
+
return self._simulate_successful_execution(signal)
|
114 |
+
else:
|
115 |
+
return self._simulate_failed_execution(signal)
|
116 |
+
|
117 |
+
except Exception as e:
|
118 |
+
self.log_error(e, "Error in order execution simulation")
|
119 |
+
return self._generate_execution_result(signal, success=False, error=str(e))
|
120 |
+
|
121 |
+
def get_account_info(self) -> Dict[str, Any]:
|
122 |
+
"""Get account information"""
|
123 |
+
if self.alpaca_broker:
|
124 |
+
return self.alpaca_broker.get_account_info()
|
125 |
+
else:
|
126 |
+
# Return simulated account info
|
127 |
+
return {
|
128 |
+
'account_id': 'SIM_ACCOUNT',
|
129 |
+
'status': 'ACTIVE',
|
130 |
+
'buying_power': 100000.0,
|
131 |
+
'cash': 100000.0,
|
132 |
+
'portfolio_value': 100000.0,
|
133 |
+
'equity': 100000.0,
|
134 |
+
'trading_blocked': False
|
135 |
+
}
|
136 |
+
|
137 |
+
def get_positions(self) -> list:
|
138 |
+
"""Get current positions"""
|
139 |
+
if self.alpaca_broker:
|
140 |
+
return self.alpaca_broker.get_positions()
|
141 |
+
else:
|
142 |
+
# Return simulated positions
|
143 |
+
return []
|
144 |
+
|
145 |
+
def is_market_open(self) -> bool:
|
146 |
+
"""Check if market is open"""
|
147 |
+
if self.alpaca_broker:
|
148 |
+
return self.alpaca_broker.is_market_open()
|
149 |
+
else:
|
150 |
+
# Assume market is always open for simulation
|
151 |
+
return True
|
152 |
+
|
153 |
def _validate_signal(self, signal: Dict[str, Any]) -> bool:
|
154 |
"""Validate trading signal"""
|
155 |
try:
|
|
|
182 |
self.log_error(e, "Error validating signal")
|
183 |
return False
|
184 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
185 |
def _simulate_successful_execution(self, signal: Dict[str, Any]) -> Dict[str, Any]:
|
186 |
"""Simulate successful order execution"""
|
187 |
try:
|
agentic_ai_system/finrl_agent.py
CHANGED
@@ -3,7 +3,7 @@ FinRL Agent for Algorithmic Trading
|
|
3 |
|
4 |
This module provides a FinRL-based reinforcement learning agent that can be integrated
|
5 |
with the existing algorithmic trading system. It supports various RL algorithms
|
6 |
-
including PPO, A2C, DDPG, and TD3.
|
7 |
"""
|
8 |
|
9 |
import numpy as np
|
@@ -51,16 +51,31 @@ class TradingEnvironment(gym.Env):
|
|
51 |
- Buy, sell, or hold positions
|
52 |
- Use technical indicators for decision making
|
53 |
- Manage portfolio value and risk
|
|
|
54 |
"""
|
55 |
|
56 |
-
def __init__(self, data: pd.DataFrame,
|
57 |
-
|
|
|
58 |
super().__init__()
|
59 |
|
60 |
self.data = data
|
|
|
61 |
self.initial_balance = initial_balance
|
62 |
self.transaction_fee = transaction_fee
|
63 |
self.max_position = max_position
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
64 |
|
65 |
# Reset state
|
66 |
self.reset()
|
@@ -128,47 +143,84 @@ class TradingEnvironment(gym.Env):
|
|
128 |
if action == 0: # Sell
|
129 |
if self.position > 0:
|
130 |
shares_to_sell = min(self.position, self.max_position)
|
131 |
-
|
132 |
-
self.
|
133 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
134 |
elif action == 2: # Buy
|
135 |
if self.balance > 0:
|
136 |
max_shares = min(
|
137 |
int(self.balance / current_price),
|
138 |
self.max_position - self.position
|
139 |
)
|
|
|
140 |
if max_shares > 0:
|
141 |
-
|
142 |
-
|
143 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
144 |
|
145 |
# Update portfolio value
|
146 |
self.previous_portfolio_value = self.portfolio_value
|
147 |
self.portfolio_value = self._calculate_portfolio_value()
|
148 |
self.total_return = (self.portfolio_value - self.initial_balance) / self.initial_balance
|
149 |
|
150 |
-
# Calculate reward
|
151 |
-
reward = self._calculate_reward()
|
152 |
-
|
153 |
# Move to next step
|
154 |
self.current_step += 1
|
155 |
|
156 |
# Check if episode is done
|
157 |
done = self.current_step >= len(self.data) - 1
|
158 |
|
159 |
-
# Get observation
|
160 |
if not done:
|
161 |
observation = self._get_features(self.data.iloc[self.current_step])
|
162 |
else:
|
163 |
-
# Use last available data for final observation
|
164 |
observation = self._get_features(self.data.iloc[-1])
|
165 |
|
|
|
|
|
|
|
|
|
166 |
info = {
|
167 |
-
'balance': self.balance,
|
168 |
-
'position': self.position,
|
169 |
'portfolio_value': self.portfolio_value,
|
170 |
'total_return': self.total_return,
|
171 |
-
'
|
|
|
|
|
172 |
}
|
173 |
|
174 |
return observation, reward, done, False, info
|
@@ -184,15 +236,10 @@ class TradingEnvironment(gym.Env):
|
|
184 |
self.previous_portfolio_value = self.initial_balance
|
185 |
self.total_return = 0.0
|
186 |
|
187 |
-
|
188 |
-
|
189 |
-
'balance': self.balance,
|
190 |
-
'position': self.position,
|
191 |
-
'portfolio_value': self.portfolio_value,
|
192 |
-
'total_return': self.total_return
|
193 |
-
}
|
194 |
|
195 |
-
return observation,
|
196 |
|
197 |
|
198 |
class FinRLAgent:
|
@@ -209,13 +256,16 @@ class FinRLAgent:
|
|
209 |
|
210 |
logger.info(f"Initializing FinRL agent with algorithm: {config.algorithm}")
|
211 |
|
212 |
-
def create_environment(self, data: pd.DataFrame,
|
|
|
213 |
"""Create trading environment from market data"""
|
214 |
return TradingEnvironment(
|
215 |
data=data,
|
|
|
216 |
initial_balance=initial_balance,
|
217 |
transaction_fee=0.001,
|
218 |
-
max_position=100
|
|
|
219 |
)
|
220 |
|
221 |
def prepare_data(self, data: pd.DataFrame) -> pd.DataFrame:
|
@@ -241,14 +291,216 @@ class FinRLAgent:
|
|
241 |
|
242 |
return df
|
243 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
244 |
def _calculate_rsi(self, prices: pd.Series, period: int = 14) -> pd.Series:
|
245 |
"""Calculate RSI indicator"""
|
246 |
delta = prices.diff()
|
247 |
gain = (delta.where(delta > 0, 0)).rolling(window=period).mean()
|
248 |
loss = (-delta.where(delta < 0, 0)).rolling(window=period).mean()
|
249 |
rs = gain / loss
|
250 |
-
|
251 |
-
return rsi
|
252 |
|
253 |
def _calculate_bollinger_bands(self, prices: pd.Series, period: int = 20, std_dev: int = 2) -> Tuple[pd.Series, pd.Series]:
|
254 |
"""Calculate Bollinger Bands"""
|
@@ -262,186 +514,5 @@ class FinRLAgent:
|
|
262 |
"""Calculate MACD indicator"""
|
263 |
ema_fast = prices.ewm(span=fast).mean()
|
264 |
ema_slow = prices.ewm(span=slow).mean()
|
265 |
-
|
266 |
-
return
|
267 |
-
|
268 |
-
def train(self, data: pd.DataFrame, total_timesteps: int = 100000,
|
269 |
-
eval_freq: int = 10000, eval_data: Optional[pd.DataFrame] = None) -> Dict[str, Any]:
|
270 |
-
"""Train the FinRL agent"""
|
271 |
-
|
272 |
-
logger.info("Starting FinRL agent training")
|
273 |
-
|
274 |
-
# Prepare data
|
275 |
-
train_data = self.prepare_data(data)
|
276 |
-
|
277 |
-
# Create training environment
|
278 |
-
self.env = DummyVecEnv([lambda: self.create_environment(train_data)])
|
279 |
-
|
280 |
-
# Create evaluation environment if provided
|
281 |
-
if eval_data is not None:
|
282 |
-
eval_data = self.prepare_data(eval_data)
|
283 |
-
self.eval_env = DummyVecEnv([lambda: self.create_environment(eval_data)])
|
284 |
-
self.callback = EvalCallback(
|
285 |
-
self.eval_env,
|
286 |
-
best_model_save_path="models/finrl_best/",
|
287 |
-
log_path="logs/finrl_eval/",
|
288 |
-
eval_freq=eval_freq,
|
289 |
-
deterministic=True,
|
290 |
-
render=False
|
291 |
-
)
|
292 |
-
|
293 |
-
# Initialize model based on algorithm
|
294 |
-
if self.config.algorithm == "PPO":
|
295 |
-
self.model = PPO(
|
296 |
-
"MlpPolicy",
|
297 |
-
self.env,
|
298 |
-
learning_rate=self.config.learning_rate,
|
299 |
-
batch_size=self.config.batch_size,
|
300 |
-
gamma=self.config.gamma,
|
301 |
-
verbose=self.config.verbose,
|
302 |
-
tensorboard_log=self.config.tensorboard_log
|
303 |
-
)
|
304 |
-
elif self.config.algorithm == "A2C":
|
305 |
-
self.model = A2C(
|
306 |
-
"MlpPolicy",
|
307 |
-
self.env,
|
308 |
-
learning_rate=self.config.learning_rate,
|
309 |
-
gamma=self.config.gamma,
|
310 |
-
verbose=self.config.verbose,
|
311 |
-
tensorboard_log=self.config.tensorboard_log
|
312 |
-
)
|
313 |
-
elif self.config.algorithm == "DDPG":
|
314 |
-
self.model = DDPG(
|
315 |
-
"MlpPolicy",
|
316 |
-
self.env,
|
317 |
-
learning_rate=self.config.learning_rate,
|
318 |
-
buffer_size=self.config.buffer_size,
|
319 |
-
learning_starts=self.config.learning_starts,
|
320 |
-
gamma=self.config.gamma,
|
321 |
-
tau=self.config.tau,
|
322 |
-
train_freq=self.config.train_freq,
|
323 |
-
gradient_steps=self.config.gradient_steps,
|
324 |
-
verbose=self.config.verbose,
|
325 |
-
tensorboard_log=self.config.tensorboard_log
|
326 |
-
)
|
327 |
-
elif self.config.algorithm == "TD3":
|
328 |
-
self.model = TD3(
|
329 |
-
"MlpPolicy",
|
330 |
-
self.env,
|
331 |
-
learning_rate=self.config.learning_rate,
|
332 |
-
buffer_size=self.config.buffer_size,
|
333 |
-
learning_starts=self.config.learning_starts,
|
334 |
-
gamma=self.config.gamma,
|
335 |
-
tau=self.config.tau,
|
336 |
-
train_freq=self.config.train_freq,
|
337 |
-
gradient_steps=self.config.gradient_steps,
|
338 |
-
target_update_interval=self.config.target_update_interval,
|
339 |
-
verbose=self.config.verbose,
|
340 |
-
tensorboard_log=self.config.tensorboard_log
|
341 |
-
)
|
342 |
-
else:
|
343 |
-
raise ValueError(f"Unsupported algorithm: {self.config.algorithm}")
|
344 |
-
|
345 |
-
# Train the model
|
346 |
-
callbacks = [self.callback] if self.callback else None
|
347 |
-
self.model.learn(
|
348 |
-
total_timesteps=total_timesteps,
|
349 |
-
callback=callbacks
|
350 |
-
)
|
351 |
-
|
352 |
-
logger.info("FinRL agent training completed")
|
353 |
-
|
354 |
-
return {
|
355 |
-
'algorithm': self.config.algorithm,
|
356 |
-
'total_timesteps': total_timesteps,
|
357 |
-
'model_path': f"models/finrl_{self.config.algorithm.lower()}"
|
358 |
-
}
|
359 |
-
|
360 |
-
def predict(self, data: pd.DataFrame) -> List[int]:
|
361 |
-
"""Generate trading predictions using the trained model"""
|
362 |
-
if self.model is None:
|
363 |
-
raise ValueError("Model not trained. Call train() first.")
|
364 |
-
|
365 |
-
# Prepare data
|
366 |
-
test_data = self.prepare_data(data)
|
367 |
-
|
368 |
-
# Create test environment
|
369 |
-
test_env = self.create_environment(test_data)
|
370 |
-
|
371 |
-
predictions = []
|
372 |
-
obs, _ = test_env.reset()
|
373 |
-
|
374 |
-
done = False
|
375 |
-
while not done:
|
376 |
-
action, _ = self.model.predict(obs, deterministic=True)
|
377 |
-
predictions.append(action)
|
378 |
-
obs, _, done, _, _ = test_env.step(action)
|
379 |
-
|
380 |
-
return predictions
|
381 |
-
|
382 |
-
def evaluate(self, data: pd.DataFrame) -> Dict[str, float]:
|
383 |
-
"""Evaluate the trained model on test data"""
|
384 |
-
if self.model is None:
|
385 |
-
raise ValueError("Model not trained. Call train() first.")
|
386 |
-
|
387 |
-
# Prepare data
|
388 |
-
test_data = self.prepare_data(data)
|
389 |
-
|
390 |
-
# Create test environment
|
391 |
-
test_env = self.create_environment(test_data)
|
392 |
-
|
393 |
-
obs, _ = test_env.reset()
|
394 |
-
done = False
|
395 |
-
total_reward = 0
|
396 |
-
steps = 0
|
397 |
-
|
398 |
-
while not done:
|
399 |
-
action, _ = self.model.predict(obs, deterministic=True)
|
400 |
-
obs, reward, done, _, info = test_env.step(action)
|
401 |
-
total_reward += reward
|
402 |
-
steps += 1
|
403 |
-
|
404 |
-
# Calculate metrics
|
405 |
-
final_portfolio_value = info['portfolio_value']
|
406 |
-
initial_balance = test_env.initial_balance
|
407 |
-
total_return = (final_portfolio_value - initial_balance) / initial_balance
|
408 |
-
|
409 |
-
return {
|
410 |
-
'total_reward': total_reward,
|
411 |
-
'total_return': total_return,
|
412 |
-
'final_portfolio_value': final_portfolio_value,
|
413 |
-
'steps': steps,
|
414 |
-
'sharpe_ratio': total_reward / steps if steps > 0 else 0
|
415 |
-
}
|
416 |
-
|
417 |
-
def save_model(self, path: str):
|
418 |
-
"""Save the trained model"""
|
419 |
-
if self.model is None:
|
420 |
-
raise ValueError("No model to save. Train the model first.")
|
421 |
-
|
422 |
-
self.model.save(path)
|
423 |
-
logger.info(f"Model saved to {path}")
|
424 |
-
|
425 |
-
def load_model(self, path: str):
|
426 |
-
"""Load a trained model"""
|
427 |
-
if self.config.algorithm == "PPO":
|
428 |
-
self.model = PPO.load(path)
|
429 |
-
elif self.config.algorithm == "A2C":
|
430 |
-
self.model = A2C.load(path)
|
431 |
-
elif self.config.algorithm == "DDPG":
|
432 |
-
self.model = DDPG.load(path)
|
433 |
-
elif self.config.algorithm == "TD3":
|
434 |
-
self.model = TD3.load(path)
|
435 |
-
else:
|
436 |
-
raise ValueError(f"Unsupported algorithm: {self.config.algorithm}")
|
437 |
-
|
438 |
-
logger.info(f"Model loaded from {path}")
|
439 |
-
|
440 |
-
|
441 |
-
def create_finrl_agent_from_config(config_path: str) -> FinRLAgent:
|
442 |
-
"""Create FinRL agent from configuration file"""
|
443 |
-
with open(config_path, 'r') as file:
|
444 |
-
config_data = yaml.safe_load(file)
|
445 |
-
|
446 |
-
finrl_config = FinRLConfig(**config_data.get('finrl', {}))
|
447 |
-
return FinRLAgent(finrl_config)
|
|
|
3 |
|
4 |
This module provides a FinRL-based reinforcement learning agent that can be integrated
|
5 |
with the existing algorithmic trading system. It supports various RL algorithms
|
6 |
+
including PPO, A2C, DDPG, and TD3, and can work with Alpaca broker for real trading.
|
7 |
"""
|
8 |
|
9 |
import numpy as np
|
|
|
51 |
- Buy, sell, or hold positions
|
52 |
- Use technical indicators for decision making
|
53 |
- Manage portfolio value and risk
|
54 |
+
- Integrate with Alpaca broker for real trading
|
55 |
"""
|
56 |
|
57 |
+
def __init__(self, data: pd.DataFrame, config: Dict[str, Any],
|
58 |
+
initial_balance: float = 100000, transaction_fee: float = 0.001,
|
59 |
+
max_position: int = 100, use_real_broker: bool = False):
|
60 |
super().__init__()
|
61 |
|
62 |
self.data = data
|
63 |
+
self.config = config
|
64 |
self.initial_balance = initial_balance
|
65 |
self.transaction_fee = transaction_fee
|
66 |
self.max_position = max_position
|
67 |
+
self.use_real_broker = use_real_broker
|
68 |
+
|
69 |
+
# Initialize Alpaca broker if using real trading
|
70 |
+
self.alpaca_broker = None
|
71 |
+
if use_real_broker:
|
72 |
+
try:
|
73 |
+
from .alpaca_broker import AlpacaBroker
|
74 |
+
self.alpaca_broker = AlpacaBroker(config)
|
75 |
+
logger.info("Alpaca broker initialized for FinRL environment")
|
76 |
+
except Exception as e:
|
77 |
+
logger.error(f"Failed to initialize Alpaca broker: {e}")
|
78 |
+
self.use_real_broker = False
|
79 |
|
80 |
# Reset state
|
81 |
self.reset()
|
|
|
143 |
if action == 0: # Sell
|
144 |
if self.position > 0:
|
145 |
shares_to_sell = min(self.position, self.max_position)
|
146 |
+
|
147 |
+
if self.use_real_broker and self.alpaca_broker:
|
148 |
+
# Execute real order with Alpaca
|
149 |
+
result = self.alpaca_broker.place_market_order(
|
150 |
+
symbol=self.config['trading']['symbol'],
|
151 |
+
quantity=shares_to_sell,
|
152 |
+
side='sell'
|
153 |
+
)
|
154 |
+
|
155 |
+
if result['success']:
|
156 |
+
sell_value = result['filled_avg_price'] * shares_to_sell * (1 - self.transaction_fee)
|
157 |
+
self.balance += sell_value
|
158 |
+
self.position -= shares_to_sell
|
159 |
+
logger.info(f"Real sell order executed: {result['order_id']}")
|
160 |
+
else:
|
161 |
+
logger.warning(f"Real sell order failed: {result['error']}")
|
162 |
+
else:
|
163 |
+
# Simulate order execution
|
164 |
+
sell_value = shares_to_sell * current_price * (1 - self.transaction_fee)
|
165 |
+
self.balance += sell_value
|
166 |
+
self.position -= shares_to_sell
|
167 |
+
|
168 |
elif action == 2: # Buy
|
169 |
if self.balance > 0:
|
170 |
max_shares = min(
|
171 |
int(self.balance / current_price),
|
172 |
self.max_position - self.position
|
173 |
)
|
174 |
+
|
175 |
if max_shares > 0:
|
176 |
+
if self.use_real_broker and self.alpaca_broker:
|
177 |
+
# Execute real order with Alpaca
|
178 |
+
result = self.alpaca_broker.place_market_order(
|
179 |
+
symbol=self.config['trading']['symbol'],
|
180 |
+
quantity=max_shares,
|
181 |
+
side='buy'
|
182 |
+
)
|
183 |
+
|
184 |
+
if result['success']:
|
185 |
+
buy_value = result['filled_avg_price'] * max_shares * (1 + self.transaction_fee)
|
186 |
+
self.balance -= buy_value
|
187 |
+
self.position += max_shares
|
188 |
+
logger.info(f"Real buy order executed: {result['order_id']}")
|
189 |
+
else:
|
190 |
+
logger.warning(f"Real buy order failed: {result['error']}")
|
191 |
+
else:
|
192 |
+
# Simulate order execution
|
193 |
+
buy_value = max_shares * current_price * (1 + self.transaction_fee)
|
194 |
+
self.balance -= buy_value
|
195 |
+
self.position += max_shares
|
196 |
|
197 |
# Update portfolio value
|
198 |
self.previous_portfolio_value = self.portfolio_value
|
199 |
self.portfolio_value = self._calculate_portfolio_value()
|
200 |
self.total_return = (self.portfolio_value - self.initial_balance) / self.initial_balance
|
201 |
|
|
|
|
|
|
|
202 |
# Move to next step
|
203 |
self.current_step += 1
|
204 |
|
205 |
# Check if episode is done
|
206 |
done = self.current_step >= len(self.data) - 1
|
207 |
|
208 |
+
# Get observation for next step
|
209 |
if not done:
|
210 |
observation = self._get_features(self.data.iloc[self.current_step])
|
211 |
else:
|
|
|
212 |
observation = self._get_features(self.data.iloc[-1])
|
213 |
|
214 |
+
# Calculate reward
|
215 |
+
reward = self._calculate_reward()
|
216 |
+
|
217 |
+
# Additional info
|
218 |
info = {
|
|
|
|
|
219 |
'portfolio_value': self.portfolio_value,
|
220 |
'total_return': self.total_return,
|
221 |
+
'position': self.position,
|
222 |
+
'balance': self.balance,
|
223 |
+
'step': self.current_step
|
224 |
}
|
225 |
|
226 |
return observation, reward, done, False, info
|
|
|
236 |
self.previous_portfolio_value = self.initial_balance
|
237 |
self.total_return = 0.0
|
238 |
|
239 |
+
# Get initial observation
|
240 |
+
observation = self._get_features(self.data.iloc[0])
|
|
|
|
|
|
|
|
|
|
|
241 |
|
242 |
+
return observation, {}
|
243 |
|
244 |
|
245 |
class FinRLAgent:
|
|
|
256 |
|
257 |
logger.info(f"Initializing FinRL agent with algorithm: {config.algorithm}")
|
258 |
|
259 |
+
def create_environment(self, data: pd.DataFrame, config: Dict[str, Any],
|
260 |
+
initial_balance: float = 100000, use_real_broker: bool = False) -> TradingEnvironment:
|
261 |
"""Create trading environment from market data"""
|
262 |
return TradingEnvironment(
|
263 |
data=data,
|
264 |
+
config=config,
|
265 |
initial_balance=initial_balance,
|
266 |
transaction_fee=0.001,
|
267 |
+
max_position=100,
|
268 |
+
use_real_broker=use_real_broker
|
269 |
)
|
270 |
|
271 |
def prepare_data(self, data: pd.DataFrame) -> pd.DataFrame:
|
|
|
291 |
|
292 |
return df
|
293 |
|
294 |
+
def train(self, data: pd.DataFrame, config: Dict[str, Any],
|
295 |
+
total_timesteps: int = 100000, use_real_broker: bool = False) -> Dict[str, Any]:
|
296 |
+
"""
|
297 |
+
Train the FinRL agent
|
298 |
+
|
299 |
+
Args:
|
300 |
+
data: Market data for training
|
301 |
+
config: Configuration dictionary
|
302 |
+
total_timesteps: Number of timesteps for training
|
303 |
+
use_real_broker: Whether to use real Alpaca broker during training
|
304 |
+
|
305 |
+
Returns:
|
306 |
+
Training results dictionary
|
307 |
+
"""
|
308 |
+
try:
|
309 |
+
# Prepare data
|
310 |
+
prepared_data = self.prepare_data(data)
|
311 |
+
|
312 |
+
# Create environment
|
313 |
+
self.env = self.create_environment(prepared_data, config, use_real_broker=use_real_broker)
|
314 |
+
|
315 |
+
# Create evaluation environment (without real broker)
|
316 |
+
eval_data = prepared_data.copy()
|
317 |
+
self.eval_env = self.create_environment(eval_data, config, use_real_broker=False)
|
318 |
+
|
319 |
+
# Create callback for evaluation
|
320 |
+
self.callback = EvalCallback(
|
321 |
+
self.eval_env,
|
322 |
+
best_model_save_path=config['finrl']['training']['model_save_path'],
|
323 |
+
log_path=config['finrl']['tensorboard_log'],
|
324 |
+
eval_freq=config['finrl']['training']['eval_freq'],
|
325 |
+
deterministic=True,
|
326 |
+
render=False
|
327 |
+
)
|
328 |
+
|
329 |
+
# Initialize model based on algorithm
|
330 |
+
if self.config.algorithm == "PPO":
|
331 |
+
self.model = PPO(
|
332 |
+
"MlpPolicy",
|
333 |
+
self.env,
|
334 |
+
learning_rate=self.config.learning_rate,
|
335 |
+
batch_size=self.config.batch_size,
|
336 |
+
buffer_size=self.config.buffer_size,
|
337 |
+
learning_starts=self.config.learning_starts,
|
338 |
+
gamma=self.config.gamma,
|
339 |
+
train_freq=self.config.train_freq,
|
340 |
+
gradient_steps=self.config.gradient_steps,
|
341 |
+
verbose=self.config.verbose,
|
342 |
+
tensorboard_log=self.config.tensorboard_log
|
343 |
+
)
|
344 |
+
elif self.config.algorithm == "A2C":
|
345 |
+
self.model = A2C(
|
346 |
+
"MlpPolicy",
|
347 |
+
self.env,
|
348 |
+
learning_rate=self.config.learning_rate,
|
349 |
+
gamma=self.config.gamma,
|
350 |
+
verbose=self.config.verbose,
|
351 |
+
tensorboard_log=self.config.tensorboard_log
|
352 |
+
)
|
353 |
+
elif self.config.algorithm == "DDPG":
|
354 |
+
self.model = DDPG(
|
355 |
+
"MlpPolicy",
|
356 |
+
self.env,
|
357 |
+
learning_rate=self.config.learning_rate,
|
358 |
+
buffer_size=self.config.buffer_size,
|
359 |
+
learning_starts=self.config.learning_starts,
|
360 |
+
gamma=self.config.gamma,
|
361 |
+
tau=self.config.tau,
|
362 |
+
train_freq=self.config.train_freq,
|
363 |
+
gradient_steps=self.config.gradient_steps,
|
364 |
+
verbose=self.config.verbose,
|
365 |
+
tensorboard_log=self.config.tensorboard_log
|
366 |
+
)
|
367 |
+
elif self.config.algorithm == "TD3":
|
368 |
+
self.model = TD3(
|
369 |
+
"MlpPolicy",
|
370 |
+
self.env,
|
371 |
+
learning_rate=self.config.learning_rate,
|
372 |
+
buffer_size=self.config.buffer_size,
|
373 |
+
learning_starts=self.config.learning_starts,
|
374 |
+
gamma=self.config.gamma,
|
375 |
+
tau=self.config.tau,
|
376 |
+
train_freq=self.config.train_freq,
|
377 |
+
gradient_steps=self.config.gradient_steps,
|
378 |
+
verbose=self.config.verbose,
|
379 |
+
tensorboard_log=self.config.tensorboard_log
|
380 |
+
)
|
381 |
+
else:
|
382 |
+
raise ValueError(f"Unsupported algorithm: {self.config.algorithm}")
|
383 |
+
|
384 |
+
# Train the model
|
385 |
+
logger.info(f"Starting training with {total_timesteps} timesteps")
|
386 |
+
self.model.learn(
|
387 |
+
total_timesteps=total_timesteps,
|
388 |
+
callback=self.callback,
|
389 |
+
progress_bar=True
|
390 |
+
)
|
391 |
+
|
392 |
+
# Save the final model
|
393 |
+
model_path = f"{config['finrl']['training']['model_save_path']}/final_model"
|
394 |
+
self.model.save(model_path)
|
395 |
+
logger.info(f"Training completed. Model saved to {model_path}")
|
396 |
+
|
397 |
+
return {
|
398 |
+
'success': True,
|
399 |
+
'algorithm': self.config.algorithm,
|
400 |
+
'total_timesteps': total_timesteps,
|
401 |
+
'model_path': model_path
|
402 |
+
}
|
403 |
+
|
404 |
+
except Exception as e:
|
405 |
+
logger.error(f"Error during training: {e}")
|
406 |
+
return {
|
407 |
+
'success': False,
|
408 |
+
'error': str(e)
|
409 |
+
}
|
410 |
+
|
411 |
+
def predict(self, data: pd.DataFrame, config: Dict[str, Any],
|
412 |
+
use_real_broker: bool = False) -> Dict[str, Any]:
|
413 |
+
"""
|
414 |
+
Make predictions using the trained model
|
415 |
+
|
416 |
+
Args:
|
417 |
+
data: Market data for prediction
|
418 |
+
config: Configuration dictionary
|
419 |
+
use_real_broker: Whether to use real Alpaca broker for execution
|
420 |
+
|
421 |
+
Returns:
|
422 |
+
Prediction results dictionary
|
423 |
+
"""
|
424 |
+
try:
|
425 |
+
if self.model is None:
|
426 |
+
# Try to load model
|
427 |
+
model_path = config['finrl']['inference']['model_path']
|
428 |
+
if config['finrl']['inference']['use_trained_model']:
|
429 |
+
self.model = self._load_model(model_path, config)
|
430 |
+
if self.model is None:
|
431 |
+
return {'success': False, 'error': 'No trained model available'}
|
432 |
+
else:
|
433 |
+
return {'success': False, 'error': 'No model available for prediction'}
|
434 |
+
|
435 |
+
# Prepare data
|
436 |
+
prepared_data = self.prepare_data(data)
|
437 |
+
|
438 |
+
# Create environment
|
439 |
+
env = self.create_environment(prepared_data, config, use_real_broker=use_real_broker)
|
440 |
+
|
441 |
+
# Run prediction
|
442 |
+
obs, _ = env.reset()
|
443 |
+
done = False
|
444 |
+
actions = []
|
445 |
+
rewards = []
|
446 |
+
portfolio_values = []
|
447 |
+
|
448 |
+
while not done:
|
449 |
+
action, _ = self.model.predict(obs, deterministic=True)
|
450 |
+
obs, reward, done, _, info = env.step(action)
|
451 |
+
|
452 |
+
actions.append(action)
|
453 |
+
rewards.append(reward)
|
454 |
+
portfolio_values.append(info['portfolio_value'])
|
455 |
+
|
456 |
+
# Calculate final metrics
|
457 |
+
initial_value = config['trading']['capital']
|
458 |
+
final_value = portfolio_values[-1] if portfolio_values else initial_value
|
459 |
+
total_return = (final_value - initial_value) / initial_value
|
460 |
+
|
461 |
+
return {
|
462 |
+
'success': True,
|
463 |
+
'actions': actions,
|
464 |
+
'rewards': rewards,
|
465 |
+
'portfolio_values': portfolio_values,
|
466 |
+
'initial_value': initial_value,
|
467 |
+
'final_value': final_value,
|
468 |
+
'total_return': total_return,
|
469 |
+
'total_trades': len([a for a in actions if a != 1]) # Count non-hold actions
|
470 |
+
}
|
471 |
+
|
472 |
+
except Exception as e:
|
473 |
+
logger.error(f"Error during prediction: {e}")
|
474 |
+
return {
|
475 |
+
'success': False,
|
476 |
+
'error': str(e)
|
477 |
+
}
|
478 |
+
|
479 |
+
def _load_model(self, model_path: str, config: Dict[str, Any]):
|
480 |
+
"""Load a trained model"""
|
481 |
+
try:
|
482 |
+
if config['finrl']['algorithm'] == "PPO":
|
483 |
+
return PPO.load(model_path)
|
484 |
+
elif config['finrl']['algorithm'] == "A2C":
|
485 |
+
return A2C.load(model_path)
|
486 |
+
elif config['finrl']['algorithm'] == "DDPG":
|
487 |
+
return DDPG.load(model_path)
|
488 |
+
elif config['finrl']['algorithm'] == "TD3":
|
489 |
+
return TD3.load(model_path)
|
490 |
+
else:
|
491 |
+
logger.error(f"Unsupported algorithm for model loading: {config['finrl']['algorithm']}")
|
492 |
+
return None
|
493 |
+
except Exception as e:
|
494 |
+
logger.error(f"Error loading model: {e}")
|
495 |
+
return None
|
496 |
+
|
497 |
def _calculate_rsi(self, prices: pd.Series, period: int = 14) -> pd.Series:
|
498 |
"""Calculate RSI indicator"""
|
499 |
delta = prices.diff()
|
500 |
gain = (delta.where(delta > 0, 0)).rolling(window=period).mean()
|
501 |
loss = (-delta.where(delta < 0, 0)).rolling(window=period).mean()
|
502 |
rs = gain / loss
|
503 |
+
return 100 - (100 / (1 + rs))
|
|
|
504 |
|
505 |
def _calculate_bollinger_bands(self, prices: pd.Series, period: int = 20, std_dev: int = 2) -> Tuple[pd.Series, pd.Series]:
|
506 |
"""Calculate Bollinger Bands"""
|
|
|
514 |
"""Calculate MACD indicator"""
|
515 |
ema_fast = prices.ewm(span=fast).mean()
|
516 |
ema_slow = prices.ewm(span=slow).mean()
|
517 |
+
macd = ema_fast - ema_slow
|
518 |
+
return macd
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
config.yaml
CHANGED
@@ -13,8 +13,21 @@ risk:
|
|
13 |
max_drawdown: 0.05
|
14 |
|
15 |
execution:
|
16 |
-
broker_api: 'paper'
|
17 |
order_size: 10
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
18 |
|
19 |
# Synthetic data generation settings
|
20 |
synthetic_data:
|
|
|
13 |
max_drawdown: 0.05
|
14 |
|
15 |
execution:
|
16 |
+
broker_api: 'paper' # Options: 'paper', 'alpaca_paper', 'alpaca_live'
|
17 |
order_size: 10
|
18 |
+
delay_ms: 100
|
19 |
+
success_rate: 0.95
|
20 |
+
|
21 |
+
# Alpaca configuration
|
22 |
+
alpaca:
|
23 |
+
api_key: '' # Set via environment variable ALPACA_API_KEY
|
24 |
+
secret_key: '' # Set via environment variable ALPACA_SECRET_KEY
|
25 |
+
paper_trading: true # Use paper trading by default
|
26 |
+
base_url: 'https://paper-api.alpaca.markets' # Paper trading URL
|
27 |
+
live_url: 'https://api.alpaca.markets' # Live trading URL
|
28 |
+
data_url: 'https://data.alpaca.markets' # Market data URL
|
29 |
+
websocket_url: 'wss://stream.data.alpaca.markets/v2/iex' # WebSocket URL
|
30 |
+
account_type: 'paper' # 'paper' or 'live'
|
31 |
|
32 |
# Synthetic data generation settings
|
33 |
synthetic_data:
|
demo.py
CHANGED
@@ -1,148 +1,329 @@
|
|
1 |
#!/usr/bin/env python3
|
2 |
"""
|
3 |
-
|
4 |
|
5 |
-
This script demonstrates the
|
6 |
-
-
|
7 |
-
-
|
8 |
-
-
|
9 |
-
-
|
|
|
10 |
"""
|
11 |
|
12 |
-
import
|
13 |
-
import
|
14 |
-
|
15 |
-
|
16 |
-
from
|
|
|
|
|
|
|
|
|
|
|
17 |
from agentic_ai_system.main import load_config
|
|
|
|
|
|
|
|
|
18 |
|
19 |
-
def
|
20 |
-
"""
|
21 |
-
|
22 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
23 |
|
24 |
-
# Load configuration
|
25 |
try:
|
26 |
-
|
27 |
-
print("
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
28 |
except Exception as e:
|
29 |
-
print(f"❌ Error
|
30 |
-
return
|
|
|
|
|
|
|
|
|
|
|
31 |
|
32 |
-
|
33 |
-
|
34 |
-
|
35 |
|
36 |
-
|
37 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
38 |
print("-" * 30)
|
39 |
|
40 |
try:
|
41 |
-
|
42 |
-
|
43 |
-
|
44 |
-
|
45 |
-
|
46 |
-
|
47 |
-
|
48 |
-
|
49 |
-
|
|
|
|
|
|
|
|
|
50 |
)
|
51 |
-
print(f"✅ Generated {len(ohlcv_data)} OHLCV data points")
|
52 |
|
53 |
-
|
54 |
-
print("\nSample OHLCV data:")
|
55 |
-
print(ohlcv_data.head())
|
56 |
|
57 |
-
#
|
58 |
-
|
59 |
-
|
60 |
|
61 |
-
|
62 |
-
|
63 |
-
|
64 |
-
|
|
|
|
|
|
|
|
|
65 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
66 |
except Exception as e:
|
67 |
-
print(f"❌ Error in
|
68 |
-
|
69 |
-
|
70 |
-
|
|
|
|
|
71 |
print("-" * 30)
|
72 |
|
73 |
try:
|
|
|
74 |
print("Running trading workflow...")
|
75 |
result = run(config)
|
76 |
|
77 |
if result['success']:
|
78 |
-
print("✅ Trading workflow completed successfully")
|
79 |
-
print(f"
|
80 |
-
print(f"
|
81 |
-
print(f"
|
82 |
-
print(f"
|
83 |
|
84 |
if result['order_executed'] and result['execution_result']:
|
85 |
exec_result = result['execution_result']
|
86 |
-
print(f"
|
|
|
|
|
|
|
|
|
|
|
87 |
else:
|
88 |
-
print("❌ Trading workflow failed")
|
89 |
-
|
90 |
-
|
|
|
|
|
|
|
91 |
except Exception as e:
|
92 |
print(f"❌ Error in trading workflow: {e}")
|
93 |
-
|
94 |
-
|
95 |
-
|
|
|
|
|
96 |
print("-" * 30)
|
97 |
|
98 |
try:
|
99 |
-
|
100 |
-
|
101 |
-
|
102 |
-
|
103 |
-
|
104 |
-
|
105 |
-
|
106 |
-
|
107 |
-
print(
|
108 |
-
print(f"
|
109 |
-
|
110 |
-
print("
|
111 |
-
print(f"
|
112 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
113 |
except Exception as e:
|
114 |
print(f"❌ Error in backtesting: {e}")
|
115 |
-
|
116 |
-
|
117 |
-
|
118 |
-
|
|
|
119 |
|
120 |
try:
|
121 |
-
#
|
122 |
-
|
123 |
-
|
124 |
-
|
125 |
-
|
126 |
-
|
127 |
-
|
128 |
-
|
129 |
-
|
130 |
-
|
131 |
-
|
132 |
-
|
133 |
-
|
134 |
-
|
135 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
136 |
|
137 |
except Exception as e:
|
138 |
-
print(f"❌
|
139 |
-
|
140 |
-
print("\n🎉 Demo completed!")
|
141 |
-
print("\n📝 Check the logs directory for detailed logs:")
|
142 |
-
print(" - logs/trading_system.log")
|
143 |
-
print(" - logs/trading.log")
|
144 |
-
print(" - logs/performance.log")
|
145 |
-
print(" - logs/errors.log")
|
146 |
|
147 |
-
if __name__ ==
|
148 |
main()
|
|
|
1 |
#!/usr/bin/env python3
|
2 |
"""
|
3 |
+
Demo script for the Algorithmic Trading System with FinRL and Alpaca Integration
|
4 |
|
5 |
+
This script demonstrates the complete trading workflow including:
|
6 |
+
- Data ingestion from multiple sources (CSV, Alpaca, Synthetic)
|
7 |
+
- Strategy generation with technical indicators
|
8 |
+
- Order execution with Alpaca broker
|
9 |
+
- FinRL reinforcement learning integration
|
10 |
+
- Real-time trading capabilities
|
11 |
"""
|
12 |
|
13 |
+
import os
|
14 |
+
import sys
|
15 |
+
import time
|
16 |
+
import logging
|
17 |
+
from datetime import datetime, timedelta
|
18 |
+
from typing import Dict, Any
|
19 |
+
|
20 |
+
# Add the project root to the path
|
21 |
+
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
|
22 |
+
|
23 |
from agentic_ai_system.main import load_config
|
24 |
+
from agentic_ai_system.orchestrator import run, run_backtest, run_live_trading
|
25 |
+
from agentic_ai_system.data_ingestion import load_data, validate_data, add_technical_indicators
|
26 |
+
from agentic_ai_system.finrl_agent import FinRLAgent, FinRLConfig
|
27 |
+
from agentic_ai_system.alpaca_broker import AlpacaBroker
|
28 |
|
29 |
+
def setup_logging():
|
30 |
+
"""Setup logging configuration"""
|
31 |
+
logging.basicConfig(
|
32 |
+
level=logging.INFO,
|
33 |
+
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
34 |
+
handlers=[
|
35 |
+
logging.StreamHandler(),
|
36 |
+
logging.FileHandler('logs/demo.log')
|
37 |
+
]
|
38 |
+
)
|
39 |
+
|
40 |
+
def print_system_info(config: Dict[str, Any]):
|
41 |
+
"""Print system configuration information"""
|
42 |
+
print("\n" + "="*60)
|
43 |
+
print("🤖 ALGORITHMIC TRADING SYSTEM WITH FINRL & ALPACA")
|
44 |
+
print("="*60)
|
45 |
+
|
46 |
+
print(f"\n📊 Data Source: {config['data_source']['type']}")
|
47 |
+
print(f"📈 Trading Symbol: {config['trading']['symbol']}")
|
48 |
+
print(f"💰 Capital: ${config['trading']['capital']:,}")
|
49 |
+
print(f"⏱️ Timeframe: {config['trading']['timeframe']}")
|
50 |
+
print(f"🔧 Broker API: {config['execution']['broker_api']}")
|
51 |
+
|
52 |
+
if config['execution']['broker_api'] in ['alpaca_paper', 'alpaca_live']:
|
53 |
+
print(f"🏦 Alpaca Account Type: {config['alpaca']['account_type']}")
|
54 |
+
print(f"📡 Alpaca Base URL: {config['alpaca']['base_url']}")
|
55 |
+
|
56 |
+
print(f"🧠 FinRL Algorithm: {config['finrl']['algorithm']}")
|
57 |
+
print(f"📚 Learning Rate: {config['finrl']['learning_rate']}")
|
58 |
+
print(f"🎯 Training Steps: {config['finrl']['training']['total_timesteps']:,}")
|
59 |
+
|
60 |
+
print("\n" + "="*60)
|
61 |
+
|
62 |
+
def demo_data_ingestion(config: Dict[str, Any]):
|
63 |
+
"""Demonstrate data ingestion capabilities"""
|
64 |
+
print("\n📥 DATA INGESTION DEMO")
|
65 |
+
print("-" * 30)
|
66 |
|
|
|
67 |
try:
|
68 |
+
# Load data
|
69 |
+
print(f"Loading data from source: {config['data_source']['type']}")
|
70 |
+
data = load_data(config)
|
71 |
+
|
72 |
+
if data is not None and not data.empty:
|
73 |
+
print(f"✅ Successfully loaded {len(data)} data points")
|
74 |
+
print(f"📅 Date range: {data['timestamp'].min()} to {data['timestamp'].max()}")
|
75 |
+
print(f"💰 Price range: ${data['close'].min():.2f} - ${data['close'].max():.2f}")
|
76 |
+
|
77 |
+
# Validate data
|
78 |
+
if validate_data(data):
|
79 |
+
print("✅ Data validation passed")
|
80 |
+
|
81 |
+
# Add technical indicators
|
82 |
+
data_with_indicators = add_technical_indicators(data)
|
83 |
+
print(f"✅ Added {len(data_with_indicators.columns) - len(data.columns)} technical indicators")
|
84 |
+
|
85 |
+
return data_with_indicators
|
86 |
+
else:
|
87 |
+
print("❌ Data validation failed")
|
88 |
+
return None
|
89 |
+
else:
|
90 |
+
print("❌ Failed to load data")
|
91 |
+
return None
|
92 |
+
|
93 |
except Exception as e:
|
94 |
+
print(f"❌ Error in data ingestion: {e}")
|
95 |
+
return None
|
96 |
+
|
97 |
+
def demo_alpaca_integration(config: Dict[str, Any]):
|
98 |
+
"""Demonstrate Alpaca broker integration"""
|
99 |
+
print("\n🏦 ALPACA INTEGRATION DEMO")
|
100 |
+
print("-" * 30)
|
101 |
|
102 |
+
if config['execution']['broker_api'] not in ['alpaca_paper', 'alpaca_live']:
|
103 |
+
print("⚠️ Alpaca integration not configured (using simulation mode)")
|
104 |
+
return None
|
105 |
|
106 |
+
try:
|
107 |
+
# Initialize Alpaca broker
|
108 |
+
print("Connecting to Alpaca...")
|
109 |
+
alpaca_broker = AlpacaBroker(config)
|
110 |
+
|
111 |
+
# Get account information
|
112 |
+
account_info = alpaca_broker.get_account_info()
|
113 |
+
if account_info:
|
114 |
+
print(f"✅ Connected to Alpaca {config['alpaca']['account_type']} account")
|
115 |
+
print(f" Account ID: {account_info['account_id']}")
|
116 |
+
print(f" Status: {account_info['status']}")
|
117 |
+
print(f" Buying Power: ${account_info['buying_power']:,.2f}")
|
118 |
+
print(f" Portfolio Value: ${account_info['portfolio_value']:,.2f}")
|
119 |
+
print(f" Equity: ${account_info['equity']:,.2f}")
|
120 |
+
|
121 |
+
# Check market status
|
122 |
+
market_hours = alpaca_broker.get_market_hours()
|
123 |
+
if market_hours:
|
124 |
+
print(f"📈 Market Status: {'🟢 OPEN' if market_hours['is_open'] else '🔴 CLOSED'}")
|
125 |
+
if market_hours['next_open']:
|
126 |
+
print(f" Next Open: {market_hours['next_open']}")
|
127 |
+
if market_hours['next_close']:
|
128 |
+
print(f" Next Close: {market_hours['next_close']}")
|
129 |
+
|
130 |
+
# Get current positions
|
131 |
+
positions = alpaca_broker.get_positions()
|
132 |
+
if positions:
|
133 |
+
print(f"📊 Current Positions: {len(positions)}")
|
134 |
+
for pos in positions:
|
135 |
+
print(f" {pos['symbol']}: {pos['quantity']} shares @ ${pos['current_price']:.2f}")
|
136 |
+
else:
|
137 |
+
print("📊 No current positions")
|
138 |
+
|
139 |
+
return alpaca_broker
|
140 |
+
|
141 |
+
except Exception as e:
|
142 |
+
print(f"❌ Error connecting to Alpaca: {e}")
|
143 |
+
return None
|
144 |
+
|
145 |
+
def demo_finrl_training(config: Dict[str, Any], data):
|
146 |
+
"""Demonstrate FinRL training"""
|
147 |
+
print("\n🧠 FINRL TRAINING DEMO")
|
148 |
print("-" * 30)
|
149 |
|
150 |
try:
|
151 |
+
# Initialize FinRL agent
|
152 |
+
finrl_config = FinRLConfig(
|
153 |
+
algorithm=config['finrl']['algorithm'],
|
154 |
+
learning_rate=config['finrl']['learning_rate'],
|
155 |
+
batch_size=config['finrl']['batch_size'],
|
156 |
+
buffer_size=config['finrl']['buffer_size'],
|
157 |
+
learning_starts=config['finrl']['learning_starts'],
|
158 |
+
gamma=config['finrl']['gamma'],
|
159 |
+
tau=config['finrl']['tau'],
|
160 |
+
train_freq=config['finrl']['train_freq'],
|
161 |
+
gradient_steps=config['finrl']['gradient_steps'],
|
162 |
+
verbose=config['finrl']['verbose'],
|
163 |
+
tensorboard_log=config['finrl']['tensorboard_log']
|
164 |
)
|
|
|
165 |
|
166 |
+
agent = FinRLAgent(finrl_config)
|
|
|
|
|
167 |
|
168 |
+
# Use a subset of data for demo training
|
169 |
+
demo_data = data.tail(500) if len(data) > 500 else data
|
170 |
+
print(f"Training on {len(demo_data)} data points...")
|
171 |
|
172 |
+
# Train the agent (shorter training for demo)
|
173 |
+
training_steps = min(10000, config['finrl']['training']['total_timesteps'])
|
174 |
+
result = agent.train(
|
175 |
+
data=demo_data,
|
176 |
+
config=config,
|
177 |
+
total_timesteps=training_steps,
|
178 |
+
use_real_broker=False # Use simulation for demo training
|
179 |
+
)
|
180 |
|
181 |
+
if result['success']:
|
182 |
+
print(f"✅ Training completed successfully!")
|
183 |
+
print(f" Algorithm: {result['algorithm']}")
|
184 |
+
print(f" Timesteps: {result['total_timesteps']:,}")
|
185 |
+
print(f" Model saved: {result['model_path']}")
|
186 |
+
|
187 |
+
# Test prediction
|
188 |
+
print("\n🔮 Testing predictions...")
|
189 |
+
prediction_result = agent.predict(
|
190 |
+
data=demo_data.tail(100),
|
191 |
+
config=config,
|
192 |
+
use_real_broker=False
|
193 |
+
)
|
194 |
+
|
195 |
+
if prediction_result['success']:
|
196 |
+
print(f"✅ Prediction completed!")
|
197 |
+
print(f" Initial Value: ${prediction_result['initial_value']:,.2f}")
|
198 |
+
print(f" Final Value: ${prediction_result['final_value']:,.2f}")
|
199 |
+
print(f" Total Return: {prediction_result['total_return']:.2%}")
|
200 |
+
print(f" Total Trades: {prediction_result['total_trades']}")
|
201 |
+
|
202 |
+
return agent
|
203 |
+
else:
|
204 |
+
print(f"��� Training failed: {result['error']}")
|
205 |
+
return None
|
206 |
+
|
207 |
except Exception as e:
|
208 |
+
print(f"❌ Error in FinRL training: {e}")
|
209 |
+
return None
|
210 |
+
|
211 |
+
def demo_trading_workflow(config: Dict[str, Any], data):
|
212 |
+
"""Demonstrate complete trading workflow"""
|
213 |
+
print("\n🔄 TRADING WORKFLOW DEMO")
|
214 |
print("-" * 30)
|
215 |
|
216 |
try:
|
217 |
+
# Run single trading cycle
|
218 |
print("Running trading workflow...")
|
219 |
result = run(config)
|
220 |
|
221 |
if result['success']:
|
222 |
+
print("✅ Trading workflow completed successfully!")
|
223 |
+
print(f" Data Loaded: {'✅' if result['data_loaded'] else '❌'}")
|
224 |
+
print(f" Signal Generated: {'✅' if result['signal_generated'] else '❌'}")
|
225 |
+
print(f" Order Executed: {'✅' if result['order_executed'] else '❌'}")
|
226 |
+
print(f" Execution Time: {result['execution_time']:.2f} seconds")
|
227 |
|
228 |
if result['order_executed'] and result['execution_result']:
|
229 |
exec_result = result['execution_result']
|
230 |
+
print(f" Order ID: {exec_result.get('order_id', 'N/A')}")
|
231 |
+
print(f" Action: {exec_result['action']}")
|
232 |
+
print(f" Symbol: {exec_result['symbol']}")
|
233 |
+
print(f" Quantity: {exec_result['quantity']}")
|
234 |
+
print(f" Price: ${exec_result['price']:.2f}")
|
235 |
+
print(f" Total Value: ${exec_result['total_value']:.2f}")
|
236 |
else:
|
237 |
+
print("❌ Trading workflow failed!")
|
238 |
+
for error in result['errors']:
|
239 |
+
print(f" Error: {error}")
|
240 |
+
|
241 |
+
return result
|
242 |
+
|
243 |
except Exception as e:
|
244 |
print(f"❌ Error in trading workflow: {e}")
|
245 |
+
return None
|
246 |
+
|
247 |
+
def demo_backtest(config: Dict[str, Any], data):
|
248 |
+
"""Demonstrate backtesting capabilities"""
|
249 |
+
print("\n📊 BACKTESTING DEMO")
|
250 |
print("-" * 30)
|
251 |
|
252 |
try:
|
253 |
+
# Run backtest on recent data
|
254 |
+
end_date = datetime.now().strftime('%Y-%m-%d')
|
255 |
+
start_date = (datetime.now() - timedelta(days=30)).strftime('%Y-%m-%d')
|
256 |
+
|
257 |
+
print(f"Running backtest from {start_date} to {end_date}...")
|
258 |
+
result = run_backtest(config, start_date, end_date)
|
259 |
+
|
260 |
+
if result['success']:
|
261 |
+
print("✅ Backtest completed successfully!")
|
262 |
+
print(f" Initial Capital: ${result['initial_capital']:,.2f}")
|
263 |
+
print(f" Final Value: ${result['final_value']:,.2f}")
|
264 |
+
print(f" Total Return: {result['total_return']:.2%}")
|
265 |
+
print(f" Total Trades: {result['total_trades']}")
|
266 |
|
267 |
+
# Calculate additional metrics
|
268 |
+
if result['total_trades'] > 0:
|
269 |
+
win_rate = len([t for t in result['trades'] if t.get('execution', {}).get('success', False)]) / result['total_trades']
|
270 |
+
print(f" Win Rate: {win_rate:.2%}")
|
271 |
+
else:
|
272 |
+
print(f"❌ Backtest failed: {result.get('error', 'Unknown error')}")
|
273 |
+
|
274 |
+
return result
|
275 |
+
|
276 |
except Exception as e:
|
277 |
print(f"❌ Error in backtesting: {e}")
|
278 |
+
return None
|
279 |
+
|
280 |
+
def main():
|
281 |
+
"""Main demo function"""
|
282 |
+
setup_logging()
|
283 |
|
284 |
try:
|
285 |
+
# Load configuration
|
286 |
+
config = load_config()
|
287 |
+
print_system_info(config)
|
288 |
+
|
289 |
+
# Demo 1: Data Ingestion
|
290 |
+
data = demo_data_ingestion(config)
|
291 |
+
if data is None:
|
292 |
+
print("❌ Cannot proceed without data")
|
293 |
+
return
|
294 |
+
|
295 |
+
# Demo 2: Alpaca Integration
|
296 |
+
alpaca_broker = demo_alpaca_integration(config)
|
297 |
+
|
298 |
+
# Demo 3: FinRL Training
|
299 |
+
finrl_agent = demo_finrl_training(config, data)
|
300 |
+
|
301 |
+
# Demo 4: Trading Workflow
|
302 |
+
workflow_result = demo_trading_workflow(config, data)
|
303 |
+
|
304 |
+
# Demo 5: Backtesting
|
305 |
+
backtest_result = demo_backtest(config, data)
|
306 |
+
|
307 |
+
# Summary
|
308 |
+
print("\n" + "="*60)
|
309 |
+
print("🎉 DEMO COMPLETED SUCCESSFULLY!")
|
310 |
+
print("="*60)
|
311 |
+
print("\n📋 Summary:")
|
312 |
+
print(f" ✅ Data Ingestion: {'Working' if data is not None else 'Failed'}")
|
313 |
+
print(f" ✅ Alpaca Integration: {'Working' if alpaca_broker is not None else 'Simulation Mode'}")
|
314 |
+
print(f" ✅ FinRL Training: {'Working' if finrl_agent is not None else 'Failed'}")
|
315 |
+
print(f" ✅ Trading Workflow: {'Working' if workflow_result and workflow_result['success'] else 'Failed'}")
|
316 |
+
print(f" ✅ Backtesting: {'Working' if backtest_result and backtest_result['success'] else 'Failed'}")
|
317 |
+
|
318 |
+
print("\n🚀 Next Steps:")
|
319 |
+
print(" 1. Set up your Alpaca API credentials in .env file")
|
320 |
+
print(" 2. Configure your trading strategy in config.yaml")
|
321 |
+
print(" 3. Run live trading with: python -m agentic_ai_system.main --mode live")
|
322 |
+
print(" 4. Monitor performance in logs/ directory")
|
323 |
|
324 |
except Exception as e:
|
325 |
+
print(f"❌ Demo failed with error: {e}")
|
326 |
+
logging.error(f"Demo error: {e}", exc_info=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
327 |
|
328 |
+
if __name__ == "__main__":
|
329 |
main()
|
env.example
ADDED
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Alpaca API Configuration
|
2 |
+
# Copy this file to .env and fill in your actual API credentials
|
3 |
+
|
4 |
+
# Alpaca API Keys (get from https://app.alpaca.markets/paper/dashboard/overview)
|
5 |
+
ALPACA_API_KEY=your_paper_api_key_here
|
6 |
+
ALPACA_SECRET_KEY=your_paper_secret_key_here
|
7 |
+
|
8 |
+
# For live trading (use with caution!)
|
9 |
+
# ALPACA_API_KEY=your_live_api_key_here
|
10 |
+
# ALPACA_SECRET_KEY=your_live_secret_key_here
|
11 |
+
|
12 |
+
# Optional: Override default URLs
|
13 |
+
# ALPACA_PAPER_URL=https://paper-api.alpaca.markets
|
14 |
+
# ALPACA_LIVE_URL=https://api.alpaca.markets
|
15 |
+
# ALPACA_DATA_URL=https://data.alpaca.markets
|
16 |
+
|
17 |
+
# Trading Configuration
|
18 |
+
TRADING_SYMBOL=AAPL
|
19 |
+
TRADING_CAPITAL=100000
|
20 |
+
TRADING_TIMEFRAME=1m
|
21 |
+
|
22 |
+
# Risk Management
|
23 |
+
MAX_POSITION_SIZE=100
|
24 |
+
MAX_DRAWDOWN=0.05
|
25 |
+
|
26 |
+
# Logging
|
27 |
+
LOG_LEVEL=INFO
|
28 |
+
LOG_DIR=logs
|
requirements.txt
CHANGED
@@ -13,3 +13,7 @@ gymnasium
|
|
13 |
tensorboard
|
14 |
torch
|
15 |
opencv-python-headless
|
|
|
|
|
|
|
|
|
|
13 |
tensorboard
|
14 |
torch
|
15 |
opencv-python-headless
|
16 |
+
alpaca-py
|
17 |
+
python-dotenv
|
18 |
+
requests
|
19 |
+
websocket-client
|