updating for pulling
This commit is contained in:
@@ -115,3 +115,4 @@
|
|||||||
2025-04-12 18:44:42,777 - WARNING - You are saving your model as an HDF5 file via `model.save()` or `keras.saving.save_model(model)`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')` or `keras.saving.save_model(model, 'my_model.keras')`.
|
2025-04-12 18:44:42,777 - WARNING - You are saving your model as an HDF5 file via `model.save()` or `keras.saving.save_model(model)`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')` or `keras.saving.save_model(model, 'my_model.keras')`.
|
||||||
2025-04-12 18:44:42,811 - INFO - Saved best LSTM model and scaler objects.
|
2025-04-12 18:44:42,811 - INFO - Saved best LSTM model and scaler objects.
|
||||||
2025-04-12 18:44:42,875 - INFO - Starting PPO training...
|
2025-04-12 18:44:42,875 - INFO - Starting PPO training...
|
||||||
|
2025-04-12 21:00:16,687 - INFO - PPO training completed and model saved.
|
||||||
|
|||||||
Binary file not shown.
205
src/MidasHL/README.md
Normal file
205
src/MidasHL/README.md
Normal file
@@ -0,0 +1,205 @@
|
|||||||
|
# MidasHL
|
||||||
|
|
||||||
|
Hierarchical Learning for Live /MES Futures Trading
|
||||||
|
A modular, production‑grade trading system combining an LSTM forecaster and a PPO trading agent. Designed for 5‑minute `/MES` futures trading with advanced risk management and easy extensibility.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- **Hierarchical AI Architecture**
|
||||||
|
- **LSTM Forecaster** for short‑term price prediction (bidirectional layers, dropout, L2 regularization)
|
||||||
|
- **PPO Trading Agent** for decision making in a custom Gym environment
|
||||||
|
- **Risk Management**: ATR‑based trailing stops, 2:1 risk/reward filter
|
||||||
|
- **Highly Modular**
|
||||||
|
- Clear separation of concerns: data loading, indicators, preprocessing, modeling, trading, execution, API, logging, resource monitoring
|
||||||
|
- **Multi‑Format Data Support**
|
||||||
|
- CSV & JSON loaders with robust error handling and column standardization
|
||||||
|
- **Performance‑Critical C Module**
|
||||||
|
- Fast ATR computation via a Python C‑extension (`fast_indicators`)
|
||||||
|
- **API Layer**
|
||||||
|
- Flask endpoints to initialize models and generate trade signals
|
||||||
|
- **Backtesting & Live Modes**
|
||||||
|
- Run full backtests, train models, or integrate live trading (placeholder for IBKR API)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Directory Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
FuturesTradingAI/
|
||||||
|
├── README.md
|
||||||
|
├── setup.py
|
||||||
|
├── requirements.txt
|
||||||
|
├── data/
|
||||||
|
│ └── sample_data.csv # Example data
|
||||||
|
├── src/
|
||||||
|
│ ├── config.py # Central settings & hyperparameters
|
||||||
|
│ ├── main.py # CLI entry point
|
||||||
|
│ ├── data/
|
||||||
|
│ │ ├── loader.py # CSV/JSON loader & renaming
|
||||||
|
│ │ ├── technical_indicators.py
|
||||||
|
│ │ └── preprocessing.py
|
||||||
|
│ ├── models/
|
||||||
|
│ │ ├── lstm_forecaster.py
|
||||||
|
│ │ ├── ppo_trader.py
|
||||||
|
│ │ └── hierarchical.py
|
||||||
|
│ ├── trading/
|
||||||
|
│ │ ├── env.py
|
||||||
|
│ │ ├── execution.py
|
||||||
|
│ │ └── risk_manager.py
|
||||||
|
│ ├── api/
|
||||||
|
│ │ └── api_ai.py
|
||||||
|
│ └── utils/
|
||||||
|
│ ├── logger.py
|
||||||
|
│ └── resource_monitor.py
|
||||||
|
├── src/c_modules/
|
||||||
|
│ ├── fast_indicators.c # ATR C‑extension
|
||||||
|
│ └── setup_fast_indicators.py
|
||||||
|
├── tests/ # Unit tests
|
||||||
|
├── examples/ # Example backtest/live scripts
|
||||||
|
└── output/ # Generated models, logs, and reports
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ⚙️ Installation
|
||||||
|
|
||||||
|
1. **Clone the repo**
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/YourOrg/FuturesTradingAI.git
|
||||||
|
cd FuturesTradingAI
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Create a virtual environment**
|
||||||
|
```bash
|
||||||
|
python3 -m venv venv
|
||||||
|
source venv/bin/activate
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Install Python dependencies**
|
||||||
|
```bash
|
||||||
|
pip install --upgrade pip
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Build the C‑extension (optional)**
|
||||||
|
```bash
|
||||||
|
cd src/c_modules
|
||||||
|
python setup_fast_indicators.py build_ext --inplace
|
||||||
|
cd ../..
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 💻 Usage
|
||||||
|
|
||||||
|
All commands assume you are in the project root with `venv` activated.
|
||||||
|
|
||||||
|
### 1. Training
|
||||||
|
|
||||||
|
Train both LSTM and PPO models on your historical data:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python -m src.main \
|
||||||
|
--mode train \
|
||||||
|
--data path/to/your_data.csv
|
||||||
|
```
|
||||||
|
|
||||||
|
- Models and scalers will be saved into `output/models/`
|
||||||
|
|
||||||
|
### 2. Backtesting
|
||||||
|
|
||||||
|
Run a backtest over historical data:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python -m src.main \
|
||||||
|
--mode backtest \
|
||||||
|
--data path/to/your_data.csv
|
||||||
|
```
|
||||||
|
|
||||||
|
- Prints total reward, number of trades, average reward, etc.
|
||||||
|
|
||||||
|
### 3. Live Trading (Simulation)
|
||||||
|
|
||||||
|
Placeholder for live trading loop (integration with IBKR API required):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python -m src.main \
|
||||||
|
--mode live \
|
||||||
|
--data path/to/your_data.csv
|
||||||
|
```
|
||||||
|
|
||||||
|
- Uses `FuturesTradingEnv` and `HierarchicalTrader.live_trading()`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📡 API Endpoints
|
||||||
|
|
||||||
|
Start the Flask server:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python src/api/api_ai.py
|
||||||
|
```
|
||||||
|
|
||||||
|
1. **Initialize Models**
|
||||||
|
`POST /initialize`
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"data_path": "data/your_data.csv",
|
||||||
|
"lstm_model_path": "output/models/lstm_forecaster.keras",
|
||||||
|
"ppo_model_path": "output/models/ppo_trader.zip"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Get Trade Signal**
|
||||||
|
`POST /trade_signal`
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"observation": [ /* numeric vector matching env.observation_space */ ]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
Returns:
|
||||||
|
```json
|
||||||
|
{ "trade_signal": 1 }
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🧪 Testing
|
||||||
|
|
||||||
|
Run unit tests with:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pytest tests/
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔧 Configuration
|
||||||
|
|
||||||
|
All hyperparameters and paths are in `src/config.py`. Key sections:
|
||||||
|
|
||||||
|
- **LSTM_PARAMS**
|
||||||
|
- **PPO_PARAMS**
|
||||||
|
- **TRADING_ENV_CONFIG**
|
||||||
|
- **API_CONFIG**
|
||||||
|
- **LOGGING_CONFIG**
|
||||||
|
- **MONITOR_CONFIG**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔜 Next Steps
|
||||||
|
|
||||||
|
- **IBKR Integration**:
|
||||||
|
- Install `ibjts` package and configure `execution.py` to send orders.
|
||||||
|
- Wire live market data feed into `FuturesTradingEnv.get_live_market_data()`.
|
||||||
|
|
||||||
|
- **Enhanced Backtests**:
|
||||||
|
- Extend `HierarchicalTrader.backtest()` to log per‑trade metrics and equity curves.
|
||||||
|
|
||||||
|
- **Parameter Tuning**:
|
||||||
|
- Leverage Optuna across both LSTM and PPO for automated hyperparameter search.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
3
src/MidasHL/TODO.txt
Normal file
3
src/MidasHL/TODO.txt
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
Objectives:
|
||||||
|
1. We need the live bot to run the backtest on only the 10 most recent bars, and then every 5 minutes run the backtest on the
|
||||||
|
newest bar,
|
||||||
Binary file not shown.
61
src/MidasHL/examples/run_backtest_test.py
Normal file
61
src/MidasHL/examples/run_backtest_test.py
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
"""
|
||||||
|
examples/run_backtest.py
|
||||||
|
|
||||||
|
Script to run a backtesting simulation for FuturesTradingAI.
|
||||||
|
This script loads CSV historical data, computes technical indicators,
|
||||||
|
preprocesses the data, instantiates the hierarchical trader, and runs backtesting.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import os
|
||||||
|
import logging
|
||||||
|
import pandas as pd
|
||||||
|
|
||||||
|
from src.data.loader import load_data
|
||||||
|
from src.data.technical_indicators import calculate_all_indicators
|
||||||
|
from src.data.preprocessing import preprocess_data
|
||||||
|
from src.models.hierarchialtrader import HierarchicalTrader
|
||||||
|
from src.utils.logger import setup_logging
|
||||||
|
|
||||||
|
def main():
|
||||||
|
# Parse command-line arguments.
|
||||||
|
parser = argparse.ArgumentParser(description="Run backtesting simulation for FuturesTradingAI")
|
||||||
|
parser.add_argument("--data", type=str, required=True, help="Path to CSV data file for backtesting")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
# Set up logging to output both to file and console.
|
||||||
|
log_dir = os.path.join(os.getcwd(), "logs")
|
||||||
|
setup_logging(log_dir)
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# Load the historical data.
|
||||||
|
try:
|
||||||
|
df = load_data(args.data)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error loading data from {args.data}: {e}")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Calculate technical indicators and preprocess data.
|
||||||
|
try:
|
||||||
|
df = calculate_all_indicators(df)
|
||||||
|
df_processed = preprocess_data(df)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error in indicator calculation or data preprocessing: {e}")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Initialize the hierarchical trader.
|
||||||
|
# If you have trained models, load and pass them here (e.g., lstm_model, ppo_model).
|
||||||
|
hierarchical_trader = HierarchicalTrader(data=df_processed)
|
||||||
|
|
||||||
|
# Run backtesting.
|
||||||
|
try:
|
||||||
|
results = hierarchical_trader.backtest()
|
||||||
|
logger.info("Backtesting Results:")
|
||||||
|
logger.info(results)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error during backtesting: {e}")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
|
|
||||||
File diff suppressed because it is too large
Load Diff
BIN
src/MidasHL/output/models/lstm_forecaster.h5
Normal file
BIN
src/MidasHL/output/models/lstm_forecaster.h5
Normal file
Binary file not shown.
BIN
src/MidasHL/output/models/ppo_trader.zip
Normal file
BIN
src/MidasHL/output/models/ppo_trader.zip
Normal file
Binary file not shown.
BIN
src/MidasHL/output/models/scaler_features.pkl
Normal file
BIN
src/MidasHL/output/models/scaler_features.pkl
Normal file
Binary file not shown.
BIN
src/MidasHL/output/models/scaler_target.pkl
Normal file
BIN
src/MidasHL/output/models/scaler_target.pkl
Normal file
Binary file not shown.
@@ -9,4 +9,5 @@ joblib
|
|||||||
flask
|
flask
|
||||||
psutil
|
psutil
|
||||||
GPUtil
|
GPUtil
|
||||||
|
shimmy
|
||||||
|
|
||||||
|
|||||||
0
src/MidasHL/src/__init__.py
Normal file
0
src/MidasHL/src/__init__.py
Normal file
BIN
src/MidasHL/src/__pycache__/__init__.cpython-311.pyc
Normal file
BIN
src/MidasHL/src/__pycache__/__init__.cpython-311.pyc
Normal file
Binary file not shown.
Binary file not shown.
@@ -42,7 +42,7 @@ PPO_PARAMS = {
|
|||||||
# Trading environment configuration
|
# Trading environment configuration
|
||||||
TRADING_ENV_CONFIG = {
|
TRADING_ENV_CONFIG = {
|
||||||
'window_size': 15, # Lookback window size for LSTM forecasting
|
'window_size': 15, # Lookback window size for LSTM forecasting
|
||||||
'transaction_cost': 0.001, # Transaction cost fee as a fraction of notional
|
'transaction_cost': 0.85, # Transaction cost fee as a fraction of notional
|
||||||
'max_contracts': 1, # Initial max number of contracts for trading (scalable for multi‑contract trading)
|
'max_contracts': 1, # Initial max number of contracts for trading (scalable for multi‑contract trading)
|
||||||
'risk_reward_threshold': 2.0, # Minimum risk/reward ratio required to execute a trade (2:1)
|
'risk_reward_threshold': 2.0, # Minimum risk/reward ratio required to execute a trade (2:1)
|
||||||
'atr_period': 14, # Period for ATR calculation
|
'atr_period': 14, # Period for ATR calculation
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,38 +1,15 @@
|
|||||||
"""
|
|
||||||
src/data/loader.py
|
|
||||||
|
|
||||||
This module provides functions to load and clean market data from various file types.
|
|
||||||
It now supports both CSV and JSON files. When a file path is passed (for example,
|
|
||||||
using the --mode testing flag), the function detects the file extension and loads
|
|
||||||
the data with the appropriate Pandas reader.
|
|
||||||
|
|
||||||
The expected columns are: time, open, high, low, close, volume.
|
|
||||||
After loading, columns are renamed for consistency and the data is sorted chronologically.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
import logging
|
import logging
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
|
|
||||||
def load_data(file_path):
|
def load_data(file_path):
|
||||||
"""
|
|
||||||
Load market data from a specified file, supporting both CSV and JSON formats.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
- file_path (str): Path to the data file (CSV or JSON).
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
- pandas.DataFrame: Loaded and cleaned data with standardized column names.
|
|
||||||
"""
|
|
||||||
logging.info(f"Loading data from: {file_path}")
|
logging.info(f"Loading data from: {file_path}")
|
||||||
|
|
||||||
# Check if the file exists
|
|
||||||
if not os.path.exists(file_path):
|
if not os.path.exists(file_path):
|
||||||
logging.error(f"Data file not found: {file_path}")
|
logging.error(f"Data file not found: {file_path}")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
# Determine file type based on extension (case-insensitive)
|
|
||||||
file_ext = os.path.splitext(file_path)[1].lower()
|
file_ext = os.path.splitext(file_path)[1].lower()
|
||||||
try:
|
try:
|
||||||
if file_ext == '.csv':
|
if file_ext == '.csv':
|
||||||
@@ -40,36 +17,20 @@ def load_data(file_path):
|
|||||||
df = pd.read_csv(file_path, parse_dates=['time'])
|
df = pd.read_csv(file_path, parse_dates=['time'])
|
||||||
elif file_ext == '.json':
|
elif file_ext == '.json':
|
||||||
logging.info("Detected JSON file format.")
|
logging.info("Detected JSON file format.")
|
||||||
# For JSON files, we assume a records-oriented format.
|
|
||||||
df = pd.read_json(file_path, convert_dates=['time'])
|
df = pd.read_json(file_path, convert_dates=['time'])
|
||||||
else:
|
else:
|
||||||
logging.error("Unsupported file format. Only CSV and JSON are supported.")
|
logging.error("Unsupported file format. Only CSV and JSON are supported.")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
except FileNotFoundError:
|
|
||||||
logging.error(f"File not found: {file_path}")
|
|
||||||
sys.exit(1)
|
|
||||||
except pd.errors.ParserError as e:
|
|
||||||
logging.error(f"Error parsing file: {e}")
|
|
||||||
sys.exit(1)
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(f"Unexpected error: {e}")
|
logging.error(f"Error loading data: {e}")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
# Standardize column names. Adjust this mapping if your JSON/CSV keys differ.
|
|
||||||
rename_mapping = {
|
|
||||||
'time': 'Date',
|
|
||||||
'open': 'Open',
|
|
||||||
'high': 'High',
|
|
||||||
'low': 'Low',
|
|
||||||
'close': 'Close',
|
|
||||||
'volume': 'Volume'
|
|
||||||
}
|
|
||||||
df.rename(columns=rename_mapping, inplace=True)
|
|
||||||
|
|
||||||
logging.info(f"Data columns after renaming: {df.columns.tolist()}")
|
|
||||||
df.sort_values('Date', inplace=True)
|
|
||||||
df.reset_index(drop=True, inplace=True)
|
|
||||||
logging.info("Data loaded and sorted successfully.")
|
|
||||||
|
|
||||||
return df
|
|
||||||
|
|
||||||
|
# unify to lowercase for consistency
|
||||||
|
df.columns = df.columns.str.lower()
|
||||||
|
|
||||||
|
# sort by time
|
||||||
|
df.sort_values('time', inplace=True)
|
||||||
|
df.reset_index(drop=True, inplace=True)
|
||||||
|
|
||||||
|
logging.info(f"Data columns after normalization: {df.columns.tolist()}")
|
||||||
|
return df
|
||||||
|
|||||||
75
src/MidasHL/src/data/old_loader.py
Normal file
75
src/MidasHL/src/data/old_loader.py
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
"""
|
||||||
|
src/data/loader.py
|
||||||
|
|
||||||
|
This module provides functions to load and clean market data from various file types.
|
||||||
|
It now supports both CSV and JSON files. When a file path is passed (for example,
|
||||||
|
using the --mode testing flag), the function detects the file extension and loads
|
||||||
|
the data with the appropriate Pandas reader.
|
||||||
|
|
||||||
|
The expected columns are: time, open, high, low, close, volume.
|
||||||
|
After loading, columns are renamed for consistency and the data is sorted chronologically.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import pandas as pd
|
||||||
|
import logging
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
|
||||||
|
def load_data(file_path):
|
||||||
|
"""
|
||||||
|
Load market data from a specified file, supporting both CSV and JSON formats.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
- file_path (str): Path to the data file (CSV or JSON).
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- pandas.DataFrame: Loaded and cleaned data with standardized column names.
|
||||||
|
"""
|
||||||
|
logging.info(f"Loading data from: {file_path}")
|
||||||
|
|
||||||
|
# Check if the file exists
|
||||||
|
if not os.path.exists(file_path):
|
||||||
|
logging.error(f"Data file not found: {file_path}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Determine file type based on extension (case-insensitive)
|
||||||
|
file_ext = os.path.splitext(file_path)[1].lower()
|
||||||
|
try:
|
||||||
|
if file_ext == '.csv':
|
||||||
|
logging.info("Detected CSV file format.")
|
||||||
|
df = pd.read_csv(file_path, parse_dates=['time'])
|
||||||
|
elif file_ext == '.json':
|
||||||
|
logging.info("Detected JSON file format.")
|
||||||
|
# For JSON files, we assume a records-oriented format.
|
||||||
|
df = pd.read_json(file_path, convert_dates=['time'])
|
||||||
|
else:
|
||||||
|
logging.error("Unsupported file format. Only CSV and JSON are supported.")
|
||||||
|
sys.exit(1)
|
||||||
|
except FileNotFoundError:
|
||||||
|
logging.error(f"File not found: {file_path}")
|
||||||
|
sys.exit(1)
|
||||||
|
except pd.errors.ParserError as e:
|
||||||
|
logging.error(f"Error parsing file: {e}")
|
||||||
|
sys.exit(1)
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"Unexpected error: {e}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Standardize column names. Adjust this mapping if your JSON/CSV keys differ.
|
||||||
|
rename_mapping = {
|
||||||
|
'time': 'Date',
|
||||||
|
'open': 'Open',
|
||||||
|
'high': 'High',
|
||||||
|
'low': 'Low',
|
||||||
|
'close': 'Close',
|
||||||
|
'volume': 'Volume'
|
||||||
|
}
|
||||||
|
df.rename(columns=rename_mapping, inplace=True)
|
||||||
|
|
||||||
|
logging.info(f"Data columns after renaming: {df.columns.tolist()}")
|
||||||
|
df.sort_values('Date', inplace=True)
|
||||||
|
df.reset_index(drop=True, inplace=True)
|
||||||
|
logging.info("Data loaded and sorted successfully.")
|
||||||
|
|
||||||
|
return df
|
||||||
|
|
||||||
64
src/MidasHL/src/data/old_preprocessing.py
Normal file
64
src/MidasHL/src/data/old_preprocessing.py
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
"""
|
||||||
|
src/data/preprocessing.py
|
||||||
|
|
||||||
|
This module provides advanced data preprocessing routines, including parallel feature engineering,
|
||||||
|
normalization, and any additional cleanup required for the trading system.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import pandas as pd
|
||||||
|
import numpy as np
|
||||||
|
import logging
|
||||||
|
from multiprocessing import Pool, cpu_count
|
||||||
|
|
||||||
|
def parallel_feature_engineering(row):
|
||||||
|
"""
|
||||||
|
Perform feature engineering on a single row of data.
|
||||||
|
|
||||||
|
This can include custom transformations, creation of new features, etc.
|
||||||
|
Currently, this is an example that adds a feature: the ratio of high to low prices.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
- row (pandas.Series): A row from the DataFrame.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- dict: A dictionary with engineered features.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
engineered = row.to_dict()
|
||||||
|
engineered['hl_ratio'] = row['High'] / (row['Low'] + 1e-9)
|
||||||
|
return engineered
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"Error processing row for feature engineering: {e}")
|
||||||
|
return row.to_dict()
|
||||||
|
|
||||||
|
def preprocess_data(df):
|
||||||
|
"""
|
||||||
|
Perform preprocessing on the entire DataFrame, including parallel feature engineering
|
||||||
|
and normalization routines.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
- df (pandas.DataFrame): Input market data.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- pandas.DataFrame: Preprocessed data ready for modeling.
|
||||||
|
"""
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
logger.info("Starting parallel feature engineering...")
|
||||||
|
num_workers = max(1, cpu_count() - 2)
|
||||||
|
with Pool(processes=num_workers) as pool:
|
||||||
|
processed_data = pool.map(parallel_feature_engineering, [row for _, row in df.iterrows()])
|
||||||
|
|
||||||
|
df_processed = pd.DataFrame(processed_data)
|
||||||
|
|
||||||
|
# Normalization: Scale price‑related columns (open, high, low, close) using min‑max scaling.
|
||||||
|
price_cols = ['Open', 'High', 'Low', 'Close']
|
||||||
|
for col in price_cols:
|
||||||
|
if col in df_processed.columns:
|
||||||
|
min_val = df_processed[col].min()
|
||||||
|
max_val = df_processed[col].max()
|
||||||
|
df_processed[col] = (df_processed[col] - min_val) / (max_val - min_val + 1e-9)
|
||||||
|
|
||||||
|
logger.info("Data preprocessing and feature engineering completed.")
|
||||||
|
return df_processed
|
||||||
|
|
||||||
214
src/MidasHL/src/data/old_technical_indicators.py
Normal file
214
src/MidasHL/src/data/old_technical_indicators.py
Normal file
@@ -0,0 +1,214 @@
|
|||||||
|
"""
|
||||||
|
src/data/technical_indicators.py
|
||||||
|
|
||||||
|
This module implements a comprehensive set of technical indicators used in the trading system.
|
||||||
|
Indicators include:
|
||||||
|
- RSI (Relative Strength Index)
|
||||||
|
- MACD (Moving Average Convergence Divergence)
|
||||||
|
- OBV (On-Balance Volume)
|
||||||
|
- ADX (Average Directional Index)
|
||||||
|
- Bollinger Bands
|
||||||
|
- MFI (Money Flow Index)
|
||||||
|
- ATR (Average True Range) for trailing stop calculations
|
||||||
|
"""
|
||||||
|
|
||||||
|
import pandas as pd
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
def compute_rsi(series, window=14):
|
||||||
|
"""
|
||||||
|
Compute Relative Strength Index (RSI).
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
- series (pandas.Series): Series of prices.
|
||||||
|
- window (int): Number of periods to use for calculation.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- pandas.Series: RSI values.
|
||||||
|
"""
|
||||||
|
delta = series.diff()
|
||||||
|
gain = delta.clip(lower=0)
|
||||||
|
loss = -delta.clip(upper=0)
|
||||||
|
avg_gain = gain.rolling(window=window, min_periods=window).mean()
|
||||||
|
avg_loss = loss.rolling(window=window, min_periods=window).mean()
|
||||||
|
rs = avg_gain / (avg_loss + 1e-9)
|
||||||
|
rsi = 100 - (100 / (1 + rs))
|
||||||
|
return rsi
|
||||||
|
|
||||||
|
def compute_macd(series, span_short=12, span_long=26, span_signal=9):
|
||||||
|
"""
|
||||||
|
Compute MACD (Moving Average Convergence Divergence) indicator.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
- series (pandas.Series): Series of prices.
|
||||||
|
- span_short (int): Short‑term EMA span.
|
||||||
|
- span_long (int): Long‑term EMA span.
|
||||||
|
- span_signal (int): Signal line EMA span.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- pandas.Series: MACD histogram values.
|
||||||
|
"""
|
||||||
|
ema_short = series.ewm(span=span_short, adjust=False).mean()
|
||||||
|
ema_long = series.ewm(span=span_long, adjust=False).mean()
|
||||||
|
macd_line = ema_short - ema_long
|
||||||
|
signal_line = macd_line.ewm(span=span_signal, adjust=False).mean()
|
||||||
|
macd_hist = macd_line - signal_line
|
||||||
|
return macd_hist
|
||||||
|
|
||||||
|
def compute_obv(df):
|
||||||
|
"""
|
||||||
|
Compute On‑Balance Volume (OBV).
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
- df (pandas.DataFrame): DataFrame containing 'close' and 'volume'.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- pandas.Series: OBV values.
|
||||||
|
"""
|
||||||
|
direction = np.sign(df['Close'].diff()).fillna(0)
|
||||||
|
obv = (direction * df['Volume']).cumsum()
|
||||||
|
return obv
|
||||||
|
|
||||||
|
def compute_adx(df, window=14):
|
||||||
|
"""
|
||||||
|
Compute Average Directional Index (ADX).
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
- df (pandas.DataFrame): DataFrame containing 'high', 'low', and 'close'.
|
||||||
|
- window (int): Window size for calculation.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- pandas.Series: ADX values.
|
||||||
|
"""
|
||||||
|
high = df['High']
|
||||||
|
low = df['Low']
|
||||||
|
close = df['Close']
|
||||||
|
|
||||||
|
tr1 = high - low
|
||||||
|
tr2 = (high - close.shift()).abs()
|
||||||
|
tr3 = (low - close.shift()).abs()
|
||||||
|
tr = pd.concat([tr1, tr2, tr3], axis=1).max(axis=1)
|
||||||
|
|
||||||
|
atr = tr.rolling(window=window, min_periods=window).mean()
|
||||||
|
up_move = high.diff()
|
||||||
|
down_move = low.diff().abs()
|
||||||
|
|
||||||
|
plus_dm = np.where((up_move > down_move) & (up_move > 0), up_move, 0)
|
||||||
|
minus_dm = np.where((down_move > up_move) & (down_move > 0), down_move, 0)
|
||||||
|
|
||||||
|
plus_di = 100 * (pd.Series(plus_dm).rolling(window=window, min_periods=window).sum() / (atr + 1e-9))
|
||||||
|
minus_di = 100 * (pd.Series(minus_dm).rolling(window=window, min_periods=window).sum() / (atr + 1e-9))
|
||||||
|
|
||||||
|
dx = (abs(plus_di - minus_di) / (plus_di + minus_di + 1e-9)) * 100
|
||||||
|
adx = dx.rolling(window=window, min_periods=window).mean()
|
||||||
|
return adx
|
||||||
|
|
||||||
|
def compute_bollinger_bands(series, window=20, num_std=2):
|
||||||
|
"""
|
||||||
|
Compute Bollinger Bands.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
- series (pandas.Series): Series of prices.
|
||||||
|
- window (int): Window size for moving average.
|
||||||
|
- num_std (int): Number of standard deviations for the bands.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- tuple: Upper band, lower band, and bandwidth as pandas.Series objects.
|
||||||
|
"""
|
||||||
|
sma = series.rolling(window=window, min_periods=window).mean()
|
||||||
|
std = series.rolling(window=window, min_periods=window).std()
|
||||||
|
upper = sma + num_std * std
|
||||||
|
lower = sma - num_std * std
|
||||||
|
bandwidth = (upper - lower) / (sma + 1e-9)
|
||||||
|
return upper, lower, bandwidth
|
||||||
|
|
||||||
|
def compute_mfi(df, window=14):
|
||||||
|
"""
|
||||||
|
Compute Money Flow Index (MFI).
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
- df (pandas.DataFrame): DataFrame containing 'high', 'low', 'close', and 'volume'.
|
||||||
|
- window (int): Window size for calculation.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- pandas.Series: MFI values.
|
||||||
|
"""
|
||||||
|
typical_price = (df['High'] + df['Low'] + df['Close']) / 3
|
||||||
|
money_flow = typical_price * df['Volume']
|
||||||
|
positive_flow = []
|
||||||
|
negative_flow = []
|
||||||
|
|
||||||
|
for i in range(1, len(typical_price)):
|
||||||
|
if typical_price.iloc[i] > typical_price.iloc[i-1]:
|
||||||
|
positive_flow.append(money_flow.iloc[i])
|
||||||
|
negative_flow.append(0)
|
||||||
|
elif typical_price.iloc[i] < typical_price.iloc[i-1]:
|
||||||
|
positive_flow.append(0)
|
||||||
|
negative_flow.append(money_flow.iloc[i])
|
||||||
|
else:
|
||||||
|
positive_flow.append(0)
|
||||||
|
negative_flow.append(0)
|
||||||
|
|
||||||
|
positive_mf = pd.Series(positive_flow).rolling(window=window, min_periods=window).sum()
|
||||||
|
negative_mf = pd.Series(negative_flow).rolling(window=window, min_periods=window).sum()
|
||||||
|
|
||||||
|
mfi = 100 - (100 / (1 + (positive_mf / (negative_mf + 1e-9))))
|
||||||
|
mfi.index = df.index[1:]
|
||||||
|
mfi = mfi.reindex(df.index)
|
||||||
|
return mfi
|
||||||
|
|
||||||
|
def compute_atr(df, period=14):
|
||||||
|
"""
|
||||||
|
Compute Average True Range (ATR).
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
- df (pandas.DataFrame): DataFrame containing 'high', 'low', and 'close'.
|
||||||
|
- period (int): Number of periods to use for the ATR calculation.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- pandas.Series: ATR values.
|
||||||
|
"""
|
||||||
|
high = df['High']
|
||||||
|
low = df['Low']
|
||||||
|
close = df['Close']
|
||||||
|
|
||||||
|
tr1 = high - low
|
||||||
|
tr2 = (high - close.shift()).abs()
|
||||||
|
tr3 = (low - close.shift()).abs()
|
||||||
|
true_range = pd.concat([tr1, tr2, tr3], axis=1).max(axis=1)
|
||||||
|
|
||||||
|
atr = true_range.rolling(window=period, min_periods=period).mean()
|
||||||
|
return atr
|
||||||
|
|
||||||
|
def calculate_all_indicators(df):
|
||||||
|
"""
|
||||||
|
Calculate and append all technical indicators to the DataFrame.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
- df (pandas.DataFrame): Input market data.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- pandas.DataFrame: DataFrame enriched with technical indicator columns.
|
||||||
|
"""
|
||||||
|
df['RSI'] = compute_rsi(df['Close'])
|
||||||
|
df['MACD'] = compute_macd(df['Close'])
|
||||||
|
df['OBV'] = compute_obv(df)
|
||||||
|
df['ADX'] = compute_adx(df)
|
||||||
|
|
||||||
|
bollinger_upper, bollinger_lower, bollinger_bandwidth = compute_bollinger_bands(df['Close'])
|
||||||
|
df['BB_Upper'] = bollinger_upper
|
||||||
|
df['BB_Lower'] = bollinger_lower
|
||||||
|
df['BB_Bandwidth'] = bollinger_bandwidth
|
||||||
|
|
||||||
|
df['MFI'] = compute_mfi(df)
|
||||||
|
df['ATR'] = compute_atr(df)
|
||||||
|
|
||||||
|
# Additional moving averages as sample features
|
||||||
|
df['SMA_5'] = df['Close'].rolling(window=5, min_periods=5).mean()
|
||||||
|
df['SMA_10'] = df['Close'].rolling(window=10, min_periods=10).mean()
|
||||||
|
|
||||||
|
# Drop rows with NaN values resulting from indicator calculations
|
||||||
|
df.dropna(inplace=True)
|
||||||
|
|
||||||
|
return df
|
||||||
|
|
||||||
@@ -1,64 +1,29 @@
|
|||||||
"""
|
|
||||||
src/data/preprocessing.py
|
|
||||||
|
|
||||||
This module provides advanced data preprocessing routines, including parallel feature engineering,
|
|
||||||
normalization, and any additional cleanup required for the trading system.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import logging
|
import logging
|
||||||
from multiprocessing import Pool, cpu_count
|
|
||||||
|
|
||||||
def parallel_feature_engineering(row):
|
def preprocess_data(df: pd.DataFrame) -> pd.DataFrame:
|
||||||
"""
|
|
||||||
Perform feature engineering on a single row of data.
|
|
||||||
|
|
||||||
This can include custom transformations, creation of new features, etc.
|
|
||||||
Currently, this is an example that adds a feature: the ratio of high to low prices.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
- row (pandas.Series): A row from the DataFrame.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
- dict: A dictionary with engineered features.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
engineered = row.to_dict()
|
|
||||||
engineered['hl_ratio'] = row['high'] / (row['low'] + 1e-9)
|
|
||||||
return engineered
|
|
||||||
except Exception as e:
|
|
||||||
logging.error(f"Error processing row for feature engineering: {e}")
|
|
||||||
return row.to_dict()
|
|
||||||
|
|
||||||
def preprocess_data(df):
|
|
||||||
"""
|
|
||||||
Perform preprocessing on the entire DataFrame, including parallel feature engineering
|
|
||||||
and normalization routines.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
- df (pandas.DataFrame): Input market data.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
- pandas.DataFrame: Preprocessed data ready for modeling.
|
|
||||||
"""
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
logger.info("Starting data preprocessing...")
|
||||||
logger.info("Starting parallel feature engineering...")
|
|
||||||
num_workers = max(1, cpu_count() - 2)
|
# enforce lowercase
|
||||||
with Pool(processes=num_workers) as pool:
|
df = df.rename(columns=str.lower)
|
||||||
processed_data = pool.map(parallel_feature_engineering, [row for _, row in df.iterrows()])
|
|
||||||
|
# feature: high/low ratio
|
||||||
df_processed = pd.DataFrame(processed_data)
|
if 'high' in df.columns and 'low' in df.columns:
|
||||||
|
df['hl_ratio'] = df['high'] / (df['low'] + 1e-9)
|
||||||
# Normalization: Scale price‑related columns (open, high, low, close) using min‑max scaling.
|
else:
|
||||||
|
df['hl_ratio'] = np.nan
|
||||||
|
logger.warning("Columns 'high' and/or 'low' not found; 'hl_ratio' set to NaN.")
|
||||||
|
|
||||||
|
# min-max normalize price columns
|
||||||
price_cols = ['open', 'high', 'low', 'close']
|
price_cols = ['open', 'high', 'low', 'close']
|
||||||
for col in price_cols:
|
for col in price_cols:
|
||||||
if col in df_processed.columns:
|
if col in df.columns:
|
||||||
min_val = df_processed[col].min()
|
mi, ma = df[col].min(), df[col].max()
|
||||||
max_val = df_processed[col].max()
|
df[col] = (df[col] - mi) / (ma - mi + 1e-9)
|
||||||
df_processed[col] = (df_processed[col] - min_val) / (max_val - min_val + 1e-9)
|
else:
|
||||||
|
logger.warning(f"Column '{col}' missing; skipping normalization.")
|
||||||
logger.info("Data preprocessing and feature engineering completed.")
|
|
||||||
return df_processed
|
|
||||||
|
|
||||||
|
logger.info("Data preprocessing completed.")
|
||||||
|
return df
|
||||||
|
|||||||
@@ -1,120 +1,49 @@
|
|||||||
"""
|
|
||||||
src/data/technical_indicators.py
|
|
||||||
|
|
||||||
This module implements a comprehensive set of technical indicators used in the trading system.
|
|
||||||
Indicators include:
|
|
||||||
- RSI (Relative Strength Index)
|
|
||||||
- MACD (Moving Average Convergence Divergence)
|
|
||||||
- OBV (On-Balance Volume)
|
|
||||||
- ADX (Average Directional Index)
|
|
||||||
- Bollinger Bands
|
|
||||||
- MFI (Money Flow Index)
|
|
||||||
- ATR (Average True Range) for trailing stop calculations
|
|
||||||
"""
|
|
||||||
|
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
def compute_rsi(series, window=14):
|
def compute_rsi(series: pd.Series, window: int = 14) -> pd.Series:
|
||||||
"""
|
|
||||||
Compute Relative Strength Index (RSI).
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
- series (pandas.Series): Series of prices.
|
|
||||||
- window (int): Number of periods to use for calculation.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
- pandas.Series: RSI values.
|
|
||||||
"""
|
|
||||||
delta = series.diff()
|
delta = series.diff()
|
||||||
gain = delta.clip(lower=0)
|
gain = delta.clip(lower=0)
|
||||||
loss = -delta.clip(upper=0)
|
loss = -delta.clip(upper=0)
|
||||||
avg_gain = gain.rolling(window=window, min_periods=window).mean()
|
avg_gain = gain.rolling(window=window, min_periods=window).mean()
|
||||||
avg_loss = loss.rolling(window=window, min_periods=window).mean()
|
avg_loss = loss.rolling(window=window, min_periods=window).mean()
|
||||||
rs = avg_gain / (avg_loss + 1e-9)
|
rs = avg_gain / (avg_loss + 1e-9)
|
||||||
rsi = 100 - (100 / (1 + rs))
|
return 100 - (100 / (1 + rs))
|
||||||
return rsi
|
|
||||||
|
|
||||||
def compute_macd(series, span_short=12, span_long=26, span_signal=9):
|
|
||||||
"""
|
def compute_macd(series: pd.Series, span_short=12, span_long=26, span_signal=9) -> pd.Series:
|
||||||
Compute MACD (Moving Average Convergence Divergence) indicator.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
- series (pandas.Series): Series of prices.
|
|
||||||
- span_short (int): Short‑term EMA span.
|
|
||||||
- span_long (int): Long‑term EMA span.
|
|
||||||
- span_signal (int): Signal line EMA span.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
- pandas.Series: MACD histogram values.
|
|
||||||
"""
|
|
||||||
ema_short = series.ewm(span=span_short, adjust=False).mean()
|
ema_short = series.ewm(span=span_short, adjust=False).mean()
|
||||||
ema_long = series.ewm(span=span_long, adjust=False).mean()
|
ema_long = series.ewm(span=span_long, adjust=False).mean()
|
||||||
macd_line = ema_short - ema_long
|
macd_line = ema_short - ema_long
|
||||||
signal_line = macd_line.ewm(span=span_signal, adjust=False).mean()
|
signal = macd_line.ewm(span=span_signal, adjust=False).mean()
|
||||||
macd_hist = macd_line - signal_line
|
return macd_line - signal
|
||||||
return macd_hist
|
|
||||||
|
|
||||||
def compute_obv(df):
|
|
||||||
"""
|
def compute_obv(df: pd.DataFrame) -> pd.Series:
|
||||||
Compute On‑Balance Volume (OBV).
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
- df (pandas.DataFrame): DataFrame containing 'close' and 'volume'.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
- pandas.Series: OBV values.
|
|
||||||
"""
|
|
||||||
direction = np.sign(df['close'].diff()).fillna(0)
|
direction = np.sign(df['close'].diff()).fillna(0)
|
||||||
obv = (direction * df['volume']).cumsum()
|
return (direction * df['volume']).cumsum()
|
||||||
return obv
|
|
||||||
|
|
||||||
def compute_adx(df, window=14):
|
|
||||||
"""
|
def compute_adx(df: pd.DataFrame, window: int = 14) -> pd.Series:
|
||||||
Compute Average Directional Index (ADX).
|
high, low, close = df['high'], df['low'], df['close']
|
||||||
|
|
||||||
Parameters:
|
|
||||||
- df (pandas.DataFrame): DataFrame containing 'high', 'low', and 'close'.
|
|
||||||
- window (int): Window size for calculation.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
- pandas.Series: ADX values.
|
|
||||||
"""
|
|
||||||
high = df['high']
|
|
||||||
low = df['low']
|
|
||||||
close = df['close']
|
|
||||||
|
|
||||||
tr1 = high - low
|
tr1 = high - low
|
||||||
tr2 = (high - close.shift()).abs()
|
tr2 = (high - close.shift()).abs()
|
||||||
tr3 = (low - close.shift()).abs()
|
tr3 = (low - close.shift()).abs()
|
||||||
tr = pd.concat([tr1, tr2, tr3], axis=1).max(axis=1)
|
tr = pd.concat([tr1, tr2, tr3], axis=1).max(axis=1)
|
||||||
|
|
||||||
atr = tr.rolling(window=window, min_periods=window).mean()
|
atr = tr.rolling(window=window, min_periods=window).mean()
|
||||||
up_move = high.diff()
|
|
||||||
down_move = low.diff().abs()
|
|
||||||
|
|
||||||
plus_dm = np.where((up_move > down_move) & (up_move > 0), up_move, 0)
|
|
||||||
minus_dm = np.where((down_move > up_move) & (down_move > 0), down_move, 0)
|
|
||||||
|
|
||||||
plus_di = 100 * (pd.Series(plus_dm).rolling(window=window, min_periods=window).sum() / (atr + 1e-9))
|
|
||||||
minus_di = 100 * (pd.Series(minus_dm).rolling(window=window, min_periods=window).sum() / (atr + 1e-9))
|
|
||||||
|
|
||||||
dx = (abs(plus_di - minus_di) / (plus_di + minus_di + 1e-9)) * 100
|
|
||||||
adx = dx.rolling(window=window, min_periods=window).mean()
|
|
||||||
return adx
|
|
||||||
|
|
||||||
def compute_bollinger_bands(series, window=20, num_std=2):
|
up = high.diff()
|
||||||
"""
|
down = low.diff().abs()
|
||||||
Compute Bollinger Bands.
|
plus_dm = np.where((up > down) & (up > 0), up, 0)
|
||||||
|
minus_dm = np.where((down > up) & (down > 0), down, 0)
|
||||||
Parameters:
|
plus_di = 100 * pd.Series(plus_dm).rolling(window=window, min_periods=window).sum() / (atr + 1e-9)
|
||||||
- series (pandas.Series): Series of prices.
|
minus_di = 100 * pd.Series(minus_dm).rolling(window=window, min_periods=window).sum() / (atr + 1e-9)
|
||||||
- window (int): Window size for moving average.
|
|
||||||
- num_std (int): Number of standard deviations for the bands.
|
dx = (abs(plus_di - minus_di) / (plus_di + minus_di + 1e-9)) * 100
|
||||||
|
return dx.rolling(window=window, min_periods=window).mean()
|
||||||
Returns:
|
|
||||||
- tuple: Upper band, lower band, and bandwidth as pandas.Series objects.
|
|
||||||
"""
|
def compute_bollinger_bands(series: pd.Series, window: int = 20, num_std: int = 2):
|
||||||
sma = series.rolling(window=window, min_periods=window).mean()
|
sma = series.rolling(window=window, min_periods=window).mean()
|
||||||
std = series.rolling(window=window, min_periods=window).std()
|
std = series.rolling(window=window, min_periods=window).std()
|
||||||
upper = sma + num_std * std
|
upper = sma + num_std * std
|
||||||
@@ -122,93 +51,44 @@ def compute_bollinger_bands(series, window=20, num_std=2):
|
|||||||
bandwidth = (upper - lower) / (sma + 1e-9)
|
bandwidth = (upper - lower) / (sma + 1e-9)
|
||||||
return upper, lower, bandwidth
|
return upper, lower, bandwidth
|
||||||
|
|
||||||
def compute_mfi(df, window=14):
|
|
||||||
"""
|
|
||||||
Compute Money Flow Index (MFI).
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
- df (pandas.DataFrame): DataFrame containing 'high', 'low', 'close', and 'volume'.
|
|
||||||
- window (int): Window size for calculation.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
- pandas.Series: MFI values.
|
|
||||||
"""
|
|
||||||
typical_price = (df['high'] + df['low'] + df['close']) / 3
|
|
||||||
money_flow = typical_price * df['volume']
|
|
||||||
positive_flow = []
|
|
||||||
negative_flow = []
|
|
||||||
|
|
||||||
for i in range(1, len(typical_price)):
|
|
||||||
if typical_price.iloc[i] > typical_price.iloc[i-1]:
|
|
||||||
positive_flow.append(money_flow.iloc[i])
|
|
||||||
negative_flow.append(0)
|
|
||||||
elif typical_price.iloc[i] < typical_price.iloc[i-1]:
|
|
||||||
positive_flow.append(0)
|
|
||||||
negative_flow.append(money_flow.iloc[i])
|
|
||||||
else:
|
|
||||||
positive_flow.append(0)
|
|
||||||
negative_flow.append(0)
|
|
||||||
|
|
||||||
positive_mf = pd.Series(positive_flow).rolling(window=window, min_periods=window).sum()
|
|
||||||
negative_mf = pd.Series(negative_flow).rolling(window=window, min_periods=window).sum()
|
|
||||||
|
|
||||||
mfi = 100 - (100 / (1 + (positive_mf / (negative_mf + 1e-9))))
|
|
||||||
mfi.index = df.index[1:]
|
|
||||||
mfi = mfi.reindex(df.index)
|
|
||||||
return mfi
|
|
||||||
|
|
||||||
def compute_atr(df, period=14):
|
def compute_mfi(df: pd.DataFrame, window: int = 14) -> pd.Series:
|
||||||
"""
|
tp = (df['high'] + df['low'] + df['close']) / 3
|
||||||
Compute Average True Range (ATR).
|
mf = tp * df['volume']
|
||||||
|
pos_flow, neg_flow = [], []
|
||||||
Parameters:
|
for i in range(1, len(tp)):
|
||||||
- df (pandas.DataFrame): DataFrame containing 'high', 'low', and 'close'.
|
if tp.iat[i] > tp.iat[i-1]:
|
||||||
- period (int): Number of periods to use for the ATR calculation.
|
pos_flow.append(mf.iat[i]); neg_flow.append(0)
|
||||||
|
elif tp.iat[i] < tp.iat[i-1]:
|
||||||
Returns:
|
pos_flow.append(0); neg_flow.append(mf.iat[i])
|
||||||
- pandas.Series: ATR values.
|
else:
|
||||||
"""
|
pos_flow.append(0); neg_flow.append(0)
|
||||||
high = df['high']
|
pos_mf = pd.Series(pos_flow).rolling(window=window, min_periods=window).sum()
|
||||||
low = df['low']
|
neg_mf = pd.Series(neg_flow).rolling(window=window, min_periods=window).sum()
|
||||||
close = df['close']
|
mfi = 100 - (100 / (1 + (pos_mf / (neg_mf + 1e-9))))
|
||||||
|
mfi.index = df.index[1:]
|
||||||
|
return mfi.reindex(df.index)
|
||||||
|
|
||||||
|
|
||||||
|
def compute_atr(df: pd.DataFrame, period: int = 14) -> pd.Series:
|
||||||
|
high, low, close = df['high'], df['low'], df['close']
|
||||||
tr1 = high - low
|
tr1 = high - low
|
||||||
tr2 = (high - close.shift()).abs()
|
tr2 = (high - close.shift()).abs()
|
||||||
tr3 = (low - close.shift()).abs()
|
tr3 = (low - close.shift()).abs()
|
||||||
true_range = pd.concat([tr1, tr2, tr3], axis=1).max(axis=1)
|
tr = pd.concat([tr1, tr2, tr3], axis=1).max(axis=1)
|
||||||
|
return tr.rolling(window=period, min_periods=period).mean()
|
||||||
atr = true_range.rolling(window=period, min_periods=period).mean()
|
|
||||||
return atr
|
|
||||||
|
|
||||||
def calculate_all_indicators(df):
|
|
||||||
"""
|
def calculate_all_indicators(df: pd.DataFrame) -> pd.DataFrame:
|
||||||
Calculate and append all technical indicators to the DataFrame.
|
df['rsi'] = compute_rsi(df['close'])
|
||||||
|
df['macd'] = compute_macd(df['close'])
|
||||||
Parameters:
|
df['obv'] = compute_obv(df)
|
||||||
- df (pandas.DataFrame): Input market data.
|
df['adx'] = compute_adx(df)
|
||||||
|
ub, lb, bw = compute_bollinger_bands(df['close'])
|
||||||
Returns:
|
df['bb_upper'], df['bb_lower'], df['bb_bandwidth'] = ub, lb, bw
|
||||||
- pandas.DataFrame: DataFrame enriched with technical indicator columns.
|
df['mfi'] = compute_mfi(df)
|
||||||
"""
|
df['atr'] = compute_atr(df)
|
||||||
df['RSI'] = compute_rsi(df['close'])
|
df['sma_5'] = df['close'].rolling(window=5, min_periods=5).mean()
|
||||||
df['MACD'] = compute_macd(df['close'])
|
df['sma_10'] = df['close'].rolling(window=10, min_periods=10).mean()
|
||||||
df['OBV'] = compute_obv(df)
|
|
||||||
df['ADX'] = compute_adx(df)
|
|
||||||
|
|
||||||
bollinger_upper, bollinger_lower, bollinger_bandwidth = compute_bollinger_bands(df['close'])
|
|
||||||
df['BB_Upper'] = bollinger_upper
|
|
||||||
df['BB_Lower'] = bollinger_lower
|
|
||||||
df['BB_Bandwidth'] = bollinger_bandwidth
|
|
||||||
|
|
||||||
df['MFI'] = compute_mfi(df)
|
|
||||||
df['ATR'] = compute_atr(df)
|
|
||||||
|
|
||||||
# Additional moving averages as sample features
|
|
||||||
df['SMA_5'] = df['close'].rolling(window=5, min_periods=5).mean()
|
|
||||||
df['SMA_10'] = df['close'].rolling(window=10, min_periods=10).mean()
|
|
||||||
|
|
||||||
# Drop rows with NaN values resulting from indicator calculations
|
|
||||||
df.dropna(inplace=True)
|
df.dropna(inplace=True)
|
||||||
|
|
||||||
return df
|
return df
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,11 @@
|
|||||||
"""
|
|
||||||
src/main.py
|
|
||||||
|
|
||||||
Main entry point for the FuturesTradingAI application.
|
|
||||||
This script parses command‑line arguments, sets up logging, loads configurations,
|
|
||||||
initializes data processing, model training, and can start the live trading loop or backtesting.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import os
|
import os
|
||||||
import logging
|
import logging
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
|
from tensorflow.keras.models import load_model
|
||||||
|
import joblib
|
||||||
|
|
||||||
from src.config import SAMPLE_DATA_PATH, OUTPUT_DIR, MODEL_DIR, LOG_DIR, MONITOR_CONFIG
|
from src.config import SAMPLE_DATA_PATH, OUTPUT_DIR, MODEL_DIR, LOG_DIR, MONITOR_CONFIG
|
||||||
from src.utils.logger import setup_logging
|
from src.utils.logger import setup_logging
|
||||||
from src.data.loader import load_data
|
from src.data.loader import load_data
|
||||||
@@ -22,83 +17,93 @@ from src.models.hierarchical import HierarchicalTrader
|
|||||||
from src.trading.env import FuturesTradingEnv
|
from src.trading.env import FuturesTradingEnv
|
||||||
from src.utils.resource_monitor import start_resource_monitor
|
from src.utils.resource_monitor import start_resource_monitor
|
||||||
|
|
||||||
|
|
||||||
def parse_args():
|
def parse_args():
|
||||||
"""
|
|
||||||
Parse command‑line arguments.
|
|
||||||
"""
|
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
description="FuturesTradingAI - Hierarchical Learning for Live /MES Futures Trading"
|
description="FuturesTradingAI - Hierarchical Learning for Live /MES Futures Trading"
|
||||||
)
|
)
|
||||||
parser.add_argument("--data", type=str, default=SAMPLE_DATA_PATH, help="Path to input CSV data file")
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--mode", type=str, choices=["train", "backtest", "live"], default="train",
|
"--data", type=str, default=SAMPLE_DATA_PATH,
|
||||||
help="Mode to run: train for model training, backtest for historical simulation, live for live trading"
|
help="Path to input CSV or JSON data file"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--mode", type=str,
|
||||||
|
choices=["train", "backtest", "live"],
|
||||||
|
default="train",
|
||||||
|
help="Mode to run: 'train' for model training, 'backtest' for historical simulation, 'live' for live trading"
|
||||||
)
|
)
|
||||||
return parser.parse_args()
|
return parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
# Parse command‑line arguments
|
|
||||||
args = parse_args()
|
args = parse_args()
|
||||||
|
|
||||||
# Ensure necessary output directories exist
|
|
||||||
os.makedirs(OUTPUT_DIR, exist_ok=True)
|
os.makedirs(OUTPUT_DIR, exist_ok=True)
|
||||||
os.makedirs(MODEL_DIR, exist_ok=True)
|
os.makedirs(MODEL_DIR, exist_ok=True)
|
||||||
os.makedirs(LOG_DIR, exist_ok=True)
|
os.makedirs(LOG_DIR, exist_ok=True)
|
||||||
|
|
||||||
# Setup logging configuration
|
|
||||||
setup_logging(LOG_DIR)
|
setup_logging(LOG_DIR)
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
logger.info("Starting FuturesTradingAI application...")
|
logger.info("Starting FuturesTradingAI application...")
|
||||||
|
|
||||||
# Optionally start system resource monitoring in a separate thread
|
# start resource monitor
|
||||||
monitor_thread = threading.Thread(target=start_resource_monitor, args=(MONITOR_CONFIG['interval'],), daemon=True)
|
monitor_thread = threading.Thread(
|
||||||
|
target=start_resource_monitor,
|
||||||
|
args=(MONITOR_CONFIG['interval'],),
|
||||||
|
daemon=True
|
||||||
|
)
|
||||||
monitor_thread.start()
|
monitor_thread.start()
|
||||||
|
|
||||||
# Load historical market data
|
# load & prepare data
|
||||||
logger.info(f"Loading data from {args.data}...")
|
logger.info(f"Loading data from {args.data}...")
|
||||||
df = load_data(args.data)
|
df = load_data(args.data)
|
||||||
|
logger.info("Calculating technical indicators...")
|
||||||
# Calculate technical indicators (RSI, MACD, OBV, ADX, Bollinger Bands, MFI, ATR, etc.)
|
|
||||||
logger.info("Calculating technical indicators on dataset...")
|
|
||||||
df = calculate_all_indicators(df)
|
df = calculate_all_indicators(df)
|
||||||
|
|
||||||
# Advanced data preprocessing and feature engineering
|
|
||||||
logger.info("Preprocessing data...")
|
logger.info("Preprocessing data...")
|
||||||
df_processed = preprocess_data(df)
|
df_processed = preprocess_data(df)
|
||||||
|
df_processed.columns = df_processed.columns.str.lower()
|
||||||
# Depending on mode, run training, backtesting or live trading
|
|
||||||
if args.mode == "train":
|
if args.mode == "train":
|
||||||
# Train LSTM forecaster
|
# Train LSTM forecaster
|
||||||
logger.info("Initializing LSTM forecaster...")
|
logger.info("Initializing LSTM forecaster...")
|
||||||
lstm_forecaster = LSTMForecaster(data=df_processed)
|
lstm_forecaster = LSTMForecaster(data=df_processed)
|
||||||
lstm_forecaster.train()
|
lstm_forecaster.train()
|
||||||
|
|
||||||
# Train PPO trading agent using custom environment
|
# Train PPO trading agent
|
||||||
logger.info("Initializing PPO trader...")
|
logger.info("Initializing PPO trader...")
|
||||||
ppo_trader = PPOTrader(data=df_processed, lstm_model=lstm_forecaster.model)
|
ppo_trader = PPOTrader(data=df_processed, lstm_model=lstm_forecaster.model)
|
||||||
ppo_trader.train()
|
ppo_trader.train()
|
||||||
|
|
||||||
# Save models
|
# Save models
|
||||||
lstm_forecaster.save(MODEL_DIR)
|
lstm_forecaster.save(MODEL_DIR)
|
||||||
ppo_trader.save(MODEL_DIR)
|
ppo_trader.save(MODEL_DIR)
|
||||||
|
|
||||||
elif args.mode == "backtest":
|
elif args.mode == "backtest":
|
||||||
# Perform backtesting using hierarchical integration
|
logger.info("Running backtest...")
|
||||||
logger.info("Running backtesting simulation...")
|
lstm_path = os.path.join(MODEL_DIR, 'lstm_forecaster.h5')
|
||||||
hierarchical_trader = HierarchicalTrader(data=df_processed)
|
feat_p = os.path.join(MODEL_DIR, 'scaler_features.pkl')
|
||||||
results = hierarchical_trader.backtest()
|
targ_p = os.path.join(MODEL_DIR, 'scaler_target.pkl')
|
||||||
logger.info("Backtest completed. Results:")
|
logger.info(f"Loading LSTM model from {lstm_path}")
|
||||||
logger.info(results)
|
lstm = LSTMForecaster(data=df_processed)
|
||||||
|
lstm.model = load_model(lstm_path, compile=False)
|
||||||
|
lstm.scaler_features = joblib.load(feat_p)
|
||||||
|
lstm.scaler_target = joblib.load(targ_p)
|
||||||
|
|
||||||
|
logger.info("Loading PPO trader model...")
|
||||||
|
ppo = PPOTrader(data=df_processed, lstm_model=lstm)
|
||||||
|
ppo.load(os.path.join(MODEL_DIR, 'ppo_trader.zip'))
|
||||||
|
|
||||||
|
hier = HierarchicalTrader(data=df_processed, lstm_model=lstm, ppo_model=ppo.model)
|
||||||
|
results = hier.backtest()
|
||||||
|
logger.info(f"Backtest results: {results}")
|
||||||
|
|
||||||
elif args.mode == "live":
|
elif args.mode == "live":
|
||||||
# Initiate live trading simulation
|
|
||||||
logger.info("Starting live trading simulation...")
|
logger.info("Starting live trading simulation...")
|
||||||
env = FuturesTradingEnv(data=df_processed)
|
env = FuturesTradingEnv(data=df_processed)
|
||||||
hierarchical_trader = HierarchicalTrader(data=df_processed, env=env)
|
live_trader = HierarchicalTrader(data=df_processed, env=env)
|
||||||
hierarchical_trader.live_trading()
|
live_trader.live_trading()
|
||||||
|
|
||||||
logger.info("FuturesTradingAI application finished.")
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
logger.info("Application finished.")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
main()
|
main()
|
||||||
|
|
||||||
|
|||||||
0
src/MidasHL/src/models/__init__.py
Normal file
0
src/MidasHL/src/models/__init__.py
Normal file
BIN
src/MidasHL/src/models/__pycache__/__init__.cpython-311.pyc
Normal file
BIN
src/MidasHL/src/models/__pycache__/__init__.cpython-311.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,145 +1,90 @@
|
|||||||
"""
|
# src/models/hierarchical.py
|
||||||
src/models/hierarchical.py
|
|
||||||
|
|
||||||
This module integrates the LSTM forecaster and the PPO trading agent to form a hierarchical decision‑making system.
|
|
||||||
It fuses short‑term price forecasts with trading signals and enforces risk management constraints before executing trades.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
class HierarchicalTrader:
|
class HierarchicalTrader:
|
||||||
"""
|
"""
|
||||||
HierarchicalTrader integrates the outputs from the LSTM forecaster and PPO agent,
|
HierarchicalTrader integrates LSTM forecasts with PPO signals,
|
||||||
applies risk management filters, and simulates trade execution.
|
applies risk management, and simulates or executes trades.
|
||||||
"""
|
"""
|
||||||
def __init__(self, data, lstm_model=None, ppo_model=None, env=None, risk_reward_threshold=2.0):
|
def __init__(self, data, lstm_model=None, ppo_model=None, env=None, risk_reward_threshold=2.0):
|
||||||
"""
|
|
||||||
Initialize HierarchicalTrader.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
- data (pandas.DataFrame): Preprocessed market data.
|
|
||||||
- lstm_model: Pre‑trained LSTM model for forecasting.
|
|
||||||
- ppo_model: Trained PPO model for trading decisions.
|
|
||||||
- env: Trading environment.
|
|
||||||
- risk_reward_threshold (float): Minimum risk/reward ratio required to execute trades.
|
|
||||||
"""
|
|
||||||
self.logger = logging.getLogger(__name__)
|
self.logger = logging.getLogger(__name__)
|
||||||
self.data = data
|
self.data = data
|
||||||
self.lstm_model = lstm_model
|
self.lstm_model = lstm_model
|
||||||
self.ppo_model = ppo_model
|
self.ppo_model = ppo_model
|
||||||
self.env = env
|
self.env = env
|
||||||
self.risk_reward_threshold = risk_reward_threshold
|
self.risk_reward_threshold = risk_reward_threshold
|
||||||
|
|
||||||
def assess_risk_reward(self, current_price, forecast, atr, current_position):
|
def assess_risk_reward(self, current_price, forecast, atr, current_position):
|
||||||
"""
|
|
||||||
Assess risk/reward ratio based on current price, forecast, ATR, and position.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
- current_price (float): Current market price.
|
|
||||||
- forecast (float): Forecasted price change (as a ratio).
|
|
||||||
- atr (float): Current ATR value.
|
|
||||||
- current_position (int): Number of contracts held.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
- risk_reward (float): Calculated risk/reward ratio.
|
|
||||||
"""
|
|
||||||
expected_price = current_price * (1 + forecast)
|
expected_price = current_price * (1 + forecast)
|
||||||
reward = abs(expected_price - current_price)
|
reward = abs(expected_price - current_price)
|
||||||
risk = atr
|
risk = atr
|
||||||
risk_reward = reward / (risk + 1e-9)
|
return reward / (risk + 1e-9)
|
||||||
return risk_reward
|
|
||||||
|
|
||||||
def generate_trade_signal(self, observation):
|
def generate_trade_signal(self, observation):
|
||||||
"""
|
# Ensure correct dtype (float32) and shape ([1, obs_dim])
|
||||||
Generate a trade signal by combining PPO agent output and LSTM forecast.
|
obs = np.array(observation, dtype=np.float32).reshape(1, -1)
|
||||||
Applies risk/reward filtering.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
- observation: Current state from the trading environment.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
- signal (int): Trade signal (e.g., number of contracts to buy/sell), or 0 for no action.
|
|
||||||
"""
|
|
||||||
# Assume observation consists of technical indicators, normalized position, and forecast (last element)
|
|
||||||
current_forecast = observation[-1]
|
|
||||||
current_position = observation[-2]
|
|
||||||
|
|
||||||
if self.ppo_model is None:
|
if self.ppo_model is None:
|
||||||
self.logger.error("PPO model not provided for hierarchical trading.")
|
self.logger.error("PPO model not provided for hierarchical trading.")
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
action, _ = self.ppo_model.predict(observation, deterministic=True)
|
action, _ = self.ppo_model.predict(obs, deterministic=True)
|
||||||
trade_signal = int(action) if isinstance(action, (int, np.integer)) else int(round(float(action)))
|
# Unwrap action array if needed
|
||||||
|
trade_signal = int(action[0]) if hasattr(action, '__len__') else int(action)
|
||||||
# For simplicity, we simulate current price and ATR extraction; in practice, these come from the environment.
|
|
||||||
current_price = 1.0 # Placeholder
|
# Extract forecast and position
|
||||||
atr = 0.01 # Placeholder
|
current_forecast = float(obs[0, -1])
|
||||||
|
current_position = float(obs[0, -2])
|
||||||
|
|
||||||
|
# Get live price and ATR from env or fallback
|
||||||
|
if self.env is not None:
|
||||||
|
idx = self.env.current_step
|
||||||
|
current_price = float(self.data.at[idx, 'close'])
|
||||||
|
atr = float(self.data.at[idx, 'atr']) if 'atr' in self.data.columns else 0.01
|
||||||
|
else:
|
||||||
|
current_price, atr = 1.0, 0.01
|
||||||
|
|
||||||
rr_ratio = self.assess_risk_reward(current_price, current_forecast, atr, current_position)
|
rr_ratio = self.assess_risk_reward(current_price, current_forecast, atr, current_position)
|
||||||
self.logger.info(f"Calculated risk/reward ratio: {rr_ratio:.2f}")
|
self.logger.info(f"Calculated risk/reward ratio: {rr_ratio:.2f}")
|
||||||
|
|
||||||
if rr_ratio < self.risk_reward_threshold:
|
if rr_ratio < self.risk_reward_threshold:
|
||||||
self.logger.info("Risk/Reward ratio below threshold. No trade executed.")
|
self.logger.info("Risk/Reward ratio below threshold. No trade executed.")
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
self.logger.info(f"Trade signal generated: {trade_signal}")
|
self.logger.info(f"Trade signal generated: {trade_signal}")
|
||||||
return trade_signal
|
return trade_signal
|
||||||
|
|
||||||
|
def _create_observation(self, row):
|
||||||
|
# Drop time, date, and target 'close'
|
||||||
|
drop_cols = [c for c in row.index if c.lower() in ('time', 'date', 'close')]
|
||||||
|
tech = row.drop(labels=drop_cols).values.astype(np.float32)
|
||||||
|
pos = np.array([0.0], dtype=np.float32)
|
||||||
|
forecast = np.array([0.001], dtype=np.float32)
|
||||||
|
return np.concatenate([tech, pos, forecast])
|
||||||
|
|
||||||
def backtest(self):
|
def backtest(self):
|
||||||
"""
|
|
||||||
Perform backtesting simulation over historical data using hierarchical decision making.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
- dict: Backtesting results (e.g., total PnL, win rate).
|
|
||||||
"""
|
|
||||||
self.logger.info("Starting backtest simulation...")
|
self.logger.info("Starting backtest simulation...")
|
||||||
total_reward = 0.0
|
total_reward, num_trades = 0.0, 0
|
||||||
num_trades = 0
|
for _, row in self.data.iterrows():
|
||||||
for idx, row in self.data.iterrows():
|
obs = self._create_observation(row)
|
||||||
observation = self._create_observation(row)
|
signal = self.generate_trade_signal(obs)
|
||||||
signal = self.generate_trade_signal(observation)
|
reward = signal * 0.001
|
||||||
reward = signal * 0.001 # Simplified reward calculation
|
|
||||||
total_reward += reward
|
total_reward += reward
|
||||||
if signal != 0:
|
if signal != 0:
|
||||||
num_trades += 1
|
num_trades += 1
|
||||||
results = {
|
return {
|
||||||
'total_reward': total_reward,
|
'total_reward': total_reward,
|
||||||
'num_trades': num_trades,
|
'num_trades': num_trades,
|
||||||
'average_reward': total_reward / (num_trades + 1e-9)
|
'average_reward': total_reward / (num_trades + 1e-9)
|
||||||
}
|
}
|
||||||
self.logger.info("Backtest simulation completed.")
|
|
||||||
return results
|
|
||||||
|
|
||||||
def _create_observation(self, row):
|
|
||||||
"""
|
|
||||||
Create a synthetic observation vector from a data row.
|
|
||||||
In a full implementation, this should mirror the environment's observation space.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
- row (pandas.Series): A row from the historical dataset.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
- numpy.array: Observation vector.
|
|
||||||
"""
|
|
||||||
tech_indicators = row.drop(labels=['time', 'close']).values
|
|
||||||
normalized_position = np.array([0.0])
|
|
||||||
forecast = np.array([0.001]) # Placeholder forecast value
|
|
||||||
|
|
||||||
observation = np.concatenate([tech_indicators, normalized_position, forecast])
|
|
||||||
return observation
|
|
||||||
|
|
||||||
def live_trading(self):
|
def live_trading(self):
|
||||||
"""
|
|
||||||
Simulate live trading by continuously generating trade signals.
|
|
||||||
This function is a placeholder for integrating with live market data and execution systems.
|
|
||||||
"""
|
|
||||||
self.logger.info("Starting live trading simulation...")
|
self.logger.info("Starting live trading simulation...")
|
||||||
import time
|
import time
|
||||||
while True:
|
while True:
|
||||||
observation = self._create_observation(self.data.iloc[-1])
|
obs = self._create_observation(self.data.iloc[-1])
|
||||||
signal = self.generate_trade_signal(observation)
|
signal = self.generate_trade_signal(obs)
|
||||||
self.logger.info(f"Live trade signal: {signal}")
|
self.logger.info(f"Live trade signal: {signal}")
|
||||||
# In production, signal triggers execution via the execution module.
|
time.sleep(5)
|
||||||
time.sleep(5) # Simulate 5‑minute reevaluation in a live setting.
|
|
||||||
|
|
||||||
|
|||||||
@@ -19,45 +19,42 @@ from sklearn.preprocessing import MinMaxScaler
|
|||||||
import optuna
|
import optuna
|
||||||
|
|
||||||
class LSTMForecaster:
|
class LSTMForecaster:
|
||||||
"""
|
|
||||||
LSTMForecaster builds, trains, and evaluates an LSTM model for price forecasting.
|
|
||||||
"""
|
|
||||||
def __init__(self, data, window_size=15, model_save_path=None, hyperparams=None):
|
def __init__(self, data, window_size=15, model_save_path=None, hyperparams=None):
|
||||||
"""
|
|
||||||
Initialize the LSTMForecaster.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
- data (pandas.DataFrame): Preprocessed market data with technical indicators.
|
|
||||||
- window_size (int): The number of time steps in the input sequence.
|
|
||||||
- model_save_path (str): Directory path where the model will be saved.
|
|
||||||
- hyperparams (dict): Dictionary of hyperparameters (optional).
|
|
||||||
"""
|
|
||||||
self.logger = logging.getLogger(__name__)
|
self.logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# 1) Normalize column names to lowercase (just in case)
|
||||||
|
data.columns = data.columns.str.lower()
|
||||||
|
# -> ['time','open','high','low','close','volume', ... other indicators ...]
|
||||||
|
|
||||||
self.data = data.copy()
|
self.data = data.copy()
|
||||||
self.window_size = window_size
|
self.window_size = window_size
|
||||||
self.model_save_path = model_save_path if model_save_path else os.getcwd()
|
self.model_save_path = model_save_path or os.getcwd()
|
||||||
self.hyperparams = hyperparams
|
self.hyperparams = hyperparams
|
||||||
|
|
||||||
# Define target column and feature columns. Here we assume 'close' is the target.
|
# 2) Define target and feature columns
|
||||||
self.target_column = 'close'
|
self.target_column = 'close'
|
||||||
self.feature_columns = [col for col in data.columns if col not in [self.target_column, 'time']]
|
# drop both 'time' and 'close' from features
|
||||||
|
self.feature_columns = [
|
||||||
# Initialize scalers for features and target
|
col for col in self.data.columns
|
||||||
|
if col not in [self.target_column, 'time']
|
||||||
|
]
|
||||||
|
|
||||||
|
# 3) Scalers
|
||||||
self.scaler_features = MinMaxScaler()
|
self.scaler_features = MinMaxScaler()
|
||||||
self.scaler_target = MinMaxScaler()
|
self.scaler_target = MinMaxScaler()
|
||||||
|
|
||||||
# Prepare training sequences
|
# 4) Create sequences
|
||||||
self.X, self.y = self._create_sequences()
|
self.X, self.y = self._create_sequences()
|
||||||
|
|
||||||
# Build model based on hyperparameters or default settings
|
# 5) Build & compile
|
||||||
if self.hyperparams is None:
|
if self.hyperparams is None:
|
||||||
self.hyperparams = {
|
self.hyperparams = {
|
||||||
'num_layers': 2,
|
'num_layers': 2,
|
||||||
'units': 64,
|
'units': 64,
|
||||||
'dropout_rate': 0.2,
|
'dropout_rate': 0.2,
|
||||||
'learning_rate': 1e-3,
|
'learning_rate': 1e-3,
|
||||||
'optimizer': 'Adam',
|
'optimizer': 'Adam',
|
||||||
'l2_reg': 1e-4
|
'l2_reg': 1e-4
|
||||||
}
|
}
|
||||||
self.model = self._build_model()
|
self.model = self._build_model()
|
||||||
|
|
||||||
|
|||||||
163
src/MidasHL/src/models/old_hierarchial.py
Normal file
163
src/MidasHL/src/models/old_hierarchial.py
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
"""
|
||||||
|
src/models/hierarchical.py
|
||||||
|
|
||||||
|
This module integrates the LSTM forecaster and the PPO trading agent to form a hierarchical decision‑making system.
|
||||||
|
It fuses short‑term price forecasts with trading signals and enforces risk management constraints before executing trades.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
class HierarchicalTrader:
|
||||||
|
"""
|
||||||
|
HierarchicalTrader integrates the outputs from the LSTM forecaster and PPO agent,
|
||||||
|
applies risk management filters, and simulates trade execution.
|
||||||
|
"""
|
||||||
|
def __init__(self, data, lstm_model=None, ppo_model=None, env=None, risk_reward_threshold=2.0):
|
||||||
|
"""
|
||||||
|
Initialize HierarchicalTrader.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
- data (pandas.DataFrame): Preprocessed market data.
|
||||||
|
- lstm_model: Pre‑trained LSTM model for forecasting.
|
||||||
|
- ppo_model: Trained PPO model for trading decisions.
|
||||||
|
- env: Trading environment.
|
||||||
|
- risk_reward_threshold (float): Minimum risk/reward ratio required to execute trades.
|
||||||
|
"""
|
||||||
|
self.logger = logging.getLogger(__name__)
|
||||||
|
self.data = data
|
||||||
|
self.lstm_model = lstm_model
|
||||||
|
self.ppo_model = ppo_model
|
||||||
|
self.env = env
|
||||||
|
self.risk_reward_threshold = risk_reward_threshold
|
||||||
|
|
||||||
|
def assess_risk_reward(self, current_price, forecast, atr, current_position):
|
||||||
|
"""
|
||||||
|
Assess risk/reward ratio based on current price, forecast, ATR, and position.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
- current_price (float): Current market price.
|
||||||
|
- forecast (float): Forecasted price change (as a ratio).
|
||||||
|
- atr (float): Current ATR value.
|
||||||
|
- current_position (int): Number of contracts held.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- risk_reward (float): Calculated risk/reward ratio.
|
||||||
|
"""
|
||||||
|
expected_price = current_price * (1 + forecast)
|
||||||
|
reward = abs(expected_price - current_price)
|
||||||
|
risk = atr
|
||||||
|
risk_reward = reward / (risk + 1e-9)
|
||||||
|
return risk_reward
|
||||||
|
|
||||||
|
def generate_trade_signal(self, observation):
|
||||||
|
"""
|
||||||
|
Generate a trade signal by combining PPO agent output and LSTM forecast.
|
||||||
|
Applies risk/reward filtering.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
- observation: Current state from the trading environment.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- signal (int): Trade signal (e.g., number of contracts to buy/sell), or 0 for no action.
|
||||||
|
"""
|
||||||
|
# Assume observation consists of technical indicators, normalized position, and forecast (last element)
|
||||||
|
current_forecast = observation[-1]
|
||||||
|
current_position = observation[-2]
|
||||||
|
|
||||||
|
if self.ppo_model is None:
|
||||||
|
self.logger.error("PPO model not provided for hierarchical trading.")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
action, _ = self.ppo_model.predict(observation, deterministic=True)
|
||||||
|
trade_signal = int(action) if isinstance(action, (int, np.integer)) else int(round(float(action)))
|
||||||
|
|
||||||
|
# For simplicity, we simulate current price and ATR extraction; in practice, these come from the environment.
|
||||||
|
current_price = 1.0 # Placeholder
|
||||||
|
atr = 0.01 # Placeholder
|
||||||
|
|
||||||
|
rr_ratio = self.assess_risk_reward(current_price, current_forecast, atr, current_position)
|
||||||
|
self.logger.info(f"Calculated risk/reward ratio: {rr_ratio:.2f}")
|
||||||
|
|
||||||
|
if rr_ratio < self.risk_reward_threshold:
|
||||||
|
self.logger.info("Risk/Reward ratio below threshold. No trade executed.")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
self.logger.info(f"Trade signal generated: {trade_signal}")
|
||||||
|
return trade_signal
|
||||||
|
|
||||||
|
def backtest(self):
|
||||||
|
"""
|
||||||
|
Perform backtesting simulation over historical data using hierarchical decision making.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- dict: Backtesting results (e.g., total PnL, win rate).
|
||||||
|
"""
|
||||||
|
self.logger.info("Starting backtest simulation...")
|
||||||
|
total_reward = 0.0
|
||||||
|
num_trades = 0
|
||||||
|
for idx, row in self.data.iterrows():
|
||||||
|
observation = self._create_observation(row)
|
||||||
|
signal = self.generate_trade_signal(observation)
|
||||||
|
reward = signal * 0.001 # Simplified reward calculation
|
||||||
|
total_reward += reward
|
||||||
|
if signal != 0:
|
||||||
|
num_trades += 1
|
||||||
|
results = {
|
||||||
|
'total_reward': total_reward,
|
||||||
|
'num_trades': num_trades,
|
||||||
|
'average_reward': total_reward / (num_trades + 1e-9)
|
||||||
|
}
|
||||||
|
self.logger.info("Backtest simulation completed.")
|
||||||
|
return results
|
||||||
|
|
||||||
|
#### Changes ####
|
||||||
|
def _create_observation(self, row):
|
||||||
|
# drop any timestamp/date column plus the target 'close'
|
||||||
|
drop_cols = [c for c in row.index if c.lower() in ('time', 'date', 'close')]
|
||||||
|
tech_indicators = row.drop(labels=drop_cols).values
|
||||||
|
|
||||||
|
# position (if you later include it in the row, pop it here)
|
||||||
|
normalized_position = np.array([0.0])
|
||||||
|
|
||||||
|
# if you ever add a real forecast column, you can pull that instead
|
||||||
|
# for now we keep your placeholder
|
||||||
|
forecast = np.array([0.001])
|
||||||
|
|
||||||
|
# final observation vector
|
||||||
|
return np.concatenate([tech_indicators, normalized_position, forecast])
|
||||||
|
|
||||||
|
'''
|
||||||
|
def _create_observation(self, row):
|
||||||
|
"""
|
||||||
|
Create a synthetic observation vector from a data row.
|
||||||
|
In a full implementation, this should mirror the environment's observation space.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
- row (pandas.Series): A row from the historical dataset.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- numpy.array: Observation vector.
|
||||||
|
"""
|
||||||
|
tech_indicators = row.drop(labels=['time', 'close']).values
|
||||||
|
normalized_position = np.array([0.0])
|
||||||
|
forecast = np.array([0.001]) # Placeholder forecast value
|
||||||
|
|
||||||
|
observation = np.concatenate([tech_indicators, normalized_position, forecast])
|
||||||
|
return observation
|
||||||
|
'''
|
||||||
|
|
||||||
|
def live_trading(self):
|
||||||
|
"""
|
||||||
|
Simulate live trading by continuously generating trade signals.
|
||||||
|
This function is a placeholder for integrating with live market data and execution systems.
|
||||||
|
"""
|
||||||
|
self.logger.info("Starting live trading simulation...")
|
||||||
|
import time
|
||||||
|
while True:
|
||||||
|
observation = self._create_observation(self.data.iloc[-1])
|
||||||
|
signal = self.generate_trade_signal(observation)
|
||||||
|
self.logger.info(f"Live trade signal: {signal}")
|
||||||
|
# In production, signal triggers execution via the execution module.
|
||||||
|
time.sleep(5) # Simulate 5‑minute reevaluation in a live setting.
|
||||||
|
|
||||||
110
src/MidasHL/src/old_main.py
Normal file
110
src/MidasHL/src/old_main.py
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
"""
|
||||||
|
src/main.py
|
||||||
|
|
||||||
|
Main entry point for the FuturesTradingAI application.
|
||||||
|
This script parses command‑line arguments, sets up logging, loads configurations,
|
||||||
|
initializes data processing, model training, and can start the live trading loop or backtesting.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import os
|
||||||
|
import logging
|
||||||
|
import threading
|
||||||
|
|
||||||
|
from src.config import SAMPLE_DATA_PATH, OUTPUT_DIR, MODEL_DIR, LOG_DIR, MONITOR_CONFIG
|
||||||
|
from src.utils.logger import setup_logging
|
||||||
|
from src.data.loader import load_data
|
||||||
|
from src.data.technical_indicators import calculate_all_indicators
|
||||||
|
from src.data.preprocessing import preprocess_data
|
||||||
|
from src.models.lstm_forecaster import LSTMForecaster
|
||||||
|
from src.models.ppo_trader import PPOTrader
|
||||||
|
from src.models.hierarchical import HierarchicalTrader
|
||||||
|
from src.trading.env import FuturesTradingEnv
|
||||||
|
from src.utils.resource_monitor import start_resource_monitor
|
||||||
|
|
||||||
|
def parse_args():
|
||||||
|
"""
|
||||||
|
Parse command‑line arguments.
|
||||||
|
"""
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description="FuturesTradingAI - Hierarchical Learning for Live /MES Futures Trading"
|
||||||
|
)
|
||||||
|
parser.add_argument("--data", type=str, default=SAMPLE_DATA_PATH, help="Path to input CSV data file")
|
||||||
|
parser.add_argument(
|
||||||
|
"--mode", type=str, choices=["train", "backtest", "live"], default="train",
|
||||||
|
help="Mode to run: train for model training, backtest for historical simulation, live for live trading"
|
||||||
|
)
|
||||||
|
return parser.parse_args()
|
||||||
|
|
||||||
|
def main():
|
||||||
|
# Parse command‑line arguments
|
||||||
|
args = parse_args()
|
||||||
|
|
||||||
|
# Ensure necessary output directories exist
|
||||||
|
os.makedirs(OUTPUT_DIR, exist_ok=True)
|
||||||
|
os.makedirs(MODEL_DIR, exist_ok=True)
|
||||||
|
os.makedirs(LOG_DIR, exist_ok=True)
|
||||||
|
|
||||||
|
# Setup logging configuration
|
||||||
|
setup_logging(LOG_DIR)
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
logger.info("Starting FuturesTradingAI application...")
|
||||||
|
|
||||||
|
# Optionally start system resource monitoring in a separate thread
|
||||||
|
monitor_thread = threading.Thread(target=start_resource_monitor, args=(MONITOR_CONFIG['interval'],), daemon=True)
|
||||||
|
monitor_thread.start()
|
||||||
|
|
||||||
|
# Load historical market data
|
||||||
|
logger.info(f"Loading data from {args.data}...")
|
||||||
|
df = load_data(args.data)
|
||||||
|
|
||||||
|
# Calculate technical indicators (RSI, MACD, OBV, ADX, Bollinger Bands, MFI, ATR, etc.)
|
||||||
|
logger.info("Calculating technical indicators on dataset...")
|
||||||
|
df = calculate_all_indicators(df)
|
||||||
|
|
||||||
|
# Advanced data preprocessing and feature engineering
|
||||||
|
logger.info("Preprocessing data...")
|
||||||
|
df_processed = preprocess_data(df)
|
||||||
|
|
||||||
|
#### Changes ####
|
||||||
|
# normalize columns for HierarchicalTrader
|
||||||
|
df_processed.columns = df_processed.columns.str.lower()
|
||||||
|
if 'date' in df_processed.columns:
|
||||||
|
df_processed = df_processed.rename(columns={'date': 'time'})
|
||||||
|
|
||||||
|
# Depending on mode, run training, backtesting or live trading
|
||||||
|
if args.mode == "train":
|
||||||
|
# Train LSTM forecaster
|
||||||
|
logger.info("Initializing LSTM forecaster...")
|
||||||
|
lstm_forecaster = LSTMForecaster(data=df_processed)
|
||||||
|
lstm_forecaster.train()
|
||||||
|
|
||||||
|
# Train PPO trading agent using custom environment
|
||||||
|
logger.info("Initializing PPO trader...")
|
||||||
|
ppo_trader = PPOTrader(data=df_processed, lstm_model=lstm_forecaster.model)
|
||||||
|
ppo_trader.train()
|
||||||
|
|
||||||
|
# Save models
|
||||||
|
lstm_forecaster.save(MODEL_DIR)
|
||||||
|
ppo_trader.save(MODEL_DIR)
|
||||||
|
|
||||||
|
elif args.mode == "backtest":
|
||||||
|
# Perform backtesting using hierarchical integration
|
||||||
|
logger.info("Running backtesting simulation...")
|
||||||
|
hierarchical_trader = HierarchicalTrader(data=df_processed)
|
||||||
|
results = hierarchical_trader.backtest()
|
||||||
|
logger.info("Backtest completed. Results:")
|
||||||
|
logger.info(results)
|
||||||
|
|
||||||
|
elif args.mode == "live":
|
||||||
|
# Initiate live trading simulation
|
||||||
|
logger.info("Starting live trading simulation...")
|
||||||
|
env = FuturesTradingEnv(data=df_processed)
|
||||||
|
hierarchical_trader = HierarchicalTrader(data=df_processed, env=env)
|
||||||
|
hierarchical_trader.live_trading()
|
||||||
|
|
||||||
|
logger.info("FuturesTradingAI application finished.")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
|
|
||||||
0
src/MidasHL/src/trading/__init__.py
Normal file
0
src/MidasHL/src/trading/__init__.py
Normal file
BIN
src/MidasHL/src/trading/__pycache__/__init__.cpython-311.pyc
Normal file
BIN
src/MidasHL/src/trading/__pycache__/__init__.cpython-311.pyc
Normal file
Binary file not shown.
Binary file not shown.
@@ -1,163 +1,91 @@
|
|||||||
"""
|
|
||||||
src/trading/env.py
|
|
||||||
|
|
||||||
This module defines a custom OpenAI Gym environment for /MES futures trading on a 5‑minute timeframe.
|
|
||||||
The environment includes logic for:
|
|
||||||
- Tracking positions (long/short)
|
|
||||||
- Evaluating positions every 5 minutes
|
|
||||||
- Implementing ATR‑based trailing stops for risk management
|
|
||||||
- Allowing a single contract initially with hooks for multi‑contract trading in the future
|
|
||||||
"""
|
|
||||||
|
|
||||||
import gym
|
import gym
|
||||||
from gym import spaces
|
from gym import spaces
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import pandas as pd
|
|
||||||
import logging
|
import logging
|
||||||
import threading
|
|
||||||
|
|
||||||
class FuturesTradingEnv(gym.Env):
|
class FuturesTradingEnv(gym.Env):
|
||||||
"""
|
|
||||||
Custom Gym environment for futures trading.
|
|
||||||
"""
|
|
||||||
metadata = {'render.modes': ['human']}
|
metadata = {'render.modes': ['human']}
|
||||||
|
|
||||||
def __init__(self, data, lstm_model=None, config=None):
|
def __init__(self, data, lstm_model=None, config=None):
|
||||||
"""
|
super().__init__()
|
||||||
Initialize the trading environment.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
- data (pandas.DataFrame): Preprocessed market data.
|
|
||||||
- lstm_model: Pre‑trained LSTM model for price forecasting (optional).
|
|
||||||
- config (dict): Configuration parameters for the environment.
|
|
||||||
"""
|
|
||||||
super(FuturesTradingEnv, self).__init__()
|
|
||||||
self.logger = logging.getLogger(__name__)
|
self.logger = logging.getLogger(__name__)
|
||||||
self.data = data.reset_index(drop=True)
|
self.data = data.reset_index(drop=True)
|
||||||
self.lstm_model = lstm_model
|
self.lstm_model = lstm_model
|
||||||
self.config = config if config is not None else {}
|
self.config = config or {}
|
||||||
|
|
||||||
# Define action space: discrete orders ranging from -max_contracts to +max_contracts.
|
# action space
|
||||||
self.max_contracts = self.config.get('max_contracts', 1)
|
self.max_contracts = self.config.get('max_contracts', 1)
|
||||||
self.action_space = spaces.Discrete(2 * self.max_contracts + 1)
|
self.action_space = spaces.Discrete(2 * self.max_contracts + 1)
|
||||||
|
|
||||||
# Define observation space: technical indicators plus current position and forecast.
|
# observation space: drop time and close
|
||||||
self.tech_indicator_cols = [col for col in self.data.columns if col not in ['time', 'close']]
|
self.tech_indicator_cols = [c for c in self.data.columns if c not in ['time', 'close']]
|
||||||
obs_dim = len(self.tech_indicator_cols) + 2 # +1 for normalized position, +1 for forecast.
|
obs_dim = len(self.tech_indicator_cols) + 2
|
||||||
self.observation_space = spaces.Box(low=-np.inf, high=np.inf, shape=(obs_dim,), dtype=np.float32)
|
self.observation_space = spaces.Box(low=-np.inf, high=np.inf, shape=(obs_dim,), dtype=np.float32)
|
||||||
|
|
||||||
# Trading variables
|
# trading vars
|
||||||
self.current_step = 0
|
self.current_step = 0
|
||||||
self.contracts_held = 0
|
self.contracts_held = 0
|
||||||
self.entry_price = None
|
self.entry_price = None
|
||||||
|
self.trailing_stop = None
|
||||||
# ATR‑based trailing stop parameters
|
|
||||||
|
# ATR stop params
|
||||||
self.atr_period = self.config.get('atr_period', 14)
|
self.atr_period = self.config.get('atr_period', 14)
|
||||||
self.trailing_stop_multiplier = self.config.get('trailing_stop_multiplier', 1.5)
|
self.trailing_stop_multiplier = self.config.get('trailing_stop_multiplier', 1.5)
|
||||||
self.trailing_stop = None
|
|
||||||
|
|
||||||
# Lock for thread‑safe LSTM predictions
|
|
||||||
self.lstm_lock = threading.Lock()
|
|
||||||
|
|
||||||
def _get_observation(self):
|
def _get_observation(self):
|
||||||
"""
|
|
||||||
Construct the observation vector from the current market data.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
- observation (numpy.array): Observation vector for the current step.
|
|
||||||
"""
|
|
||||||
row = self.data.iloc[self.current_step]
|
row = self.data.iloc[self.current_step]
|
||||||
tech_indicators = row[self.tech_indicator_cols].values.astype(np.float32)
|
tech = row[self.tech_indicator_cols].values.astype(np.float32)
|
||||||
norm_position = np.array([self.contracts_held / self.max_contracts], dtype=np.float32)
|
pos = np.array([self.contracts_held / self.max_contracts], dtype=np.float32)
|
||||||
|
|
||||||
if self.lstm_model and self.current_step >= self.config.get('window_size', 15):
|
if self.lstm_model and self.current_step >= self.config.get('window_size', 15):
|
||||||
# For proper forecasting, construct an input sequence; here we use a placeholder.
|
|
||||||
forecast = np.array([0.001], dtype=np.float32)
|
forecast = np.array([0.001], dtype=np.float32)
|
||||||
else:
|
else:
|
||||||
forecast = np.array([0.0], dtype=np.float32)
|
forecast = np.array([0.0], dtype=np.float32)
|
||||||
|
return np.concatenate([tech, pos, forecast])
|
||||||
observation = np.concatenate([tech_indicators, norm_position, forecast])
|
|
||||||
return observation
|
|
||||||
|
|
||||||
def reset(self):
|
def reset(self):
|
||||||
"""
|
self.current_step, self.contracts_held = 0, 0
|
||||||
Reset the environment to an initial state.
|
self.entry_price, self.trailing_stop = None, None
|
||||||
|
|
||||||
Returns:
|
|
||||||
- observation (numpy.array): The initial observation.
|
|
||||||
"""
|
|
||||||
self.current_step = 0
|
|
||||||
self.contracts_held = 0
|
|
||||||
self.entry_price = None
|
|
||||||
self.trailing_stop = None
|
|
||||||
return self._get_observation()
|
return self._get_observation()
|
||||||
|
|
||||||
def step(self, action):
|
def step(self, action):
|
||||||
"""
|
trade_adj = action - self.max_contracts
|
||||||
Execute one time step in the environment.
|
current_price = self.data.at[self.current_step, 'close']
|
||||||
|
cost = self.config.get('transaction_cost', 0.001) * abs(trade_adj) * current_price
|
||||||
Parameters:
|
|
||||||
- action (int): Action taken by the agent (discrete order adjustment).
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
- observation (numpy.array): Next state observation.
|
|
||||||
- reward (float): Reward obtained from the action.
|
|
||||||
- done (bool): Whether the episode has ended.
|
|
||||||
- info (dict): Additional environment info.
|
|
||||||
"""
|
|
||||||
trade_adjustment = action - self.max_contracts
|
|
||||||
current_price = self.data.iloc[self.current_step]['close']
|
|
||||||
|
|
||||||
transaction_cost = self.config.get('transaction_cost', 0.001) * abs(trade_adjustment) * current_price
|
|
||||||
|
|
||||||
reward = 0.0
|
reward = 0.0
|
||||||
if trade_adjustment != 0:
|
if trade_adj != 0:
|
||||||
if self.contracts_held == 0:
|
if self.contracts_held == 0:
|
||||||
self.contracts_held = trade_adjustment
|
self.contracts_held = trade_adj
|
||||||
self.entry_price = current_price
|
self.entry_price = current_price
|
||||||
atr = self.data.iloc[self.current_step].get('ATR', 0.01)
|
atr = self.data.at[self.current_step, 'atr']
|
||||||
self.trailing_stop = current_price - self.trailing_stop_multiplier * atr if trade_adjustment > 0 else current_price + self.trailing_stop_multiplier * atr
|
self.trailing_stop = (current_price - self.trailing_stop_multiplier * atr
|
||||||
|
if trade_adj > 0 else current_price + self.trailing_stop_multiplier * atr)
|
||||||
else:
|
else:
|
||||||
prev_position = self.contracts_held
|
prev = self.contracts_held
|
||||||
self.contracts_held += trade_adjustment
|
self.contracts_held += trade_adj
|
||||||
if self.contracts_held != 0:
|
if self.contracts_held != 0:
|
||||||
self.entry_price = (self.entry_price * prev_position + current_price * trade_adjustment) / self.contracts_held
|
self.entry_price = (self.entry_price * prev + current_price * trade_adj) / self.contracts_held
|
||||||
else:
|
else:
|
||||||
self.entry_price = None
|
self.entry_price = None
|
||||||
reward -= transaction_cost
|
reward -= cost
|
||||||
else:
|
else:
|
||||||
if self.contracts_held != 0 and self.entry_price is not None:
|
if self.contracts_held != 0 and self.entry_price is not None:
|
||||||
reward = (current_price - self.entry_price) * self.contracts_held
|
reward = (current_price - self.entry_price) * self.contracts_held
|
||||||
|
|
||||||
# ATR‑based trailing stop logic:
|
# trailing stop
|
||||||
if self.trailing_stop is not None:
|
if self.trailing_stop is not None:
|
||||||
if self.contracts_held > 0 and current_price < self.trailing_stop:
|
if self.contracts_held > 0 and current_price < self.trailing_stop:
|
||||||
self.logger.info("ATR trailing stop triggered for long position.")
|
|
||||||
reward += (current_price - self.entry_price) * self.contracts_held
|
reward += (current_price - self.entry_price) * self.contracts_held
|
||||||
self.contracts_held = 0
|
self.contracts_held = 0; self.entry_price = None; self.trailing_stop = None
|
||||||
self.entry_price = None
|
|
||||||
self.trailing_stop = None
|
|
||||||
elif self.contracts_held < 0 and current_price > self.trailing_stop:
|
elif self.contracts_held < 0 and current_price > self.trailing_stop:
|
||||||
self.logger.info("ATR trailing stop triggered for short position.")
|
|
||||||
reward += (self.entry_price - current_price) * abs(self.contracts_held)
|
reward += (self.entry_price - current_price) * abs(self.contracts_held)
|
||||||
self.contracts_held = 0
|
self.contracts_held = 0; self.entry_price = None; self.trailing_stop = None
|
||||||
self.entry_price = None
|
|
||||||
self.trailing_stop = None
|
|
||||||
|
|
||||||
self.current_step += 1
|
self.current_step += 1
|
||||||
done = self.current_step >= len(self.data) - 1
|
done = self.current_step >= len(self.data) - 1
|
||||||
observation = self._get_observation()
|
obs = self._get_observation()
|
||||||
|
info = {'contracts_held': self.contracts_held, 'entry_price': self.entry_price}
|
||||||
info = {
|
return obs, reward, done, info
|
||||||
'contracts_held': self.contracts_held,
|
|
||||||
'entry_price': self.entry_price
|
|
||||||
}
|
|
||||||
return observation, reward, done, info
|
|
||||||
|
|
||||||
def render(self, mode='human'):
|
|
||||||
"""
|
|
||||||
Render the current state.
|
|
||||||
"""
|
|
||||||
current_price = self.data.iloc[self.current_step]['close']
|
|
||||||
print(f"Step: {self.current_step}, Price: {current_price:.4f}, Contracts Held: {self.contracts_held}, Entry Price: {self.entry_price}")
|
|
||||||
|
|
||||||
|
def render(self, mode='human'):
|
||||||
|
price = self.data.at[self.current_step, 'close']
|
||||||
|
print(f"Step {self.current_step}, Price {price:.4f}, Held {self.contracts_held}")
|
||||||
|
|||||||
@@ -41,12 +41,12 @@ def get_live_market_data():
|
|||||||
"""
|
"""
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
live_data = {
|
live_data = {
|
||||||
'time': None,
|
'Date': None,
|
||||||
'open': None,
|
'Open': None,
|
||||||
'high': None,
|
'High': None,
|
||||||
'low': None,
|
'Low': None,
|
||||||
'close': 1.0, # Dummy value for testing
|
'Close': 1.0, # Dummy value for testing
|
||||||
'volume': None
|
'Volume': None
|
||||||
}
|
}
|
||||||
logger.info("Retrieved live market data (placeholder).")
|
logger.info("Retrieved live market data (placeholder).")
|
||||||
return live_data
|
return live_data
|
||||||
|
|||||||
163
src/MidasHL/src/trading/old_env.py
Normal file
163
src/MidasHL/src/trading/old_env.py
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
"""
|
||||||
|
src/trading/env.py
|
||||||
|
|
||||||
|
This module defines a custom OpenAI Gym environment for /MES futures trading on a 5‑minute timeframe.
|
||||||
|
The environment includes logic for:
|
||||||
|
- Tracking positions (long/short)
|
||||||
|
- Evaluating positions every 5 minutes
|
||||||
|
- Implementing ATR‑based trailing stops for risk management
|
||||||
|
- Allowing a single contract initially with hooks for multi‑contract trading in the future
|
||||||
|
"""
|
||||||
|
|
||||||
|
import gym
|
||||||
|
from gym import spaces
|
||||||
|
import numpy as np
|
||||||
|
import pandas as pd
|
||||||
|
import logging
|
||||||
|
import threading
|
||||||
|
|
||||||
|
class FuturesTradingEnv(gym.Env):
|
||||||
|
"""
|
||||||
|
Custom Gym environment for futures trading.
|
||||||
|
"""
|
||||||
|
metadata = {'render.modes': ['human']}
|
||||||
|
|
||||||
|
def __init__(self, data, lstm_model=None, config=None):
|
||||||
|
"""
|
||||||
|
Initialize the trading environment.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
- data (pandas.DataFrame): Preprocessed market data.
|
||||||
|
- lstm_model: Pre‑trained LSTM model for price forecasting (optional).
|
||||||
|
- config (dict): Configuration parameters for the environment.
|
||||||
|
"""
|
||||||
|
super(FuturesTradingEnv, self).__init__()
|
||||||
|
self.logger = logging.getLogger(__name__)
|
||||||
|
self.data = data.reset_index(drop=True)
|
||||||
|
self.lstm_model = lstm_model
|
||||||
|
self.config = config if config is not None else {}
|
||||||
|
|
||||||
|
# Define action space: discrete orders ranging from -max_contracts to +max_contracts.
|
||||||
|
self.max_contracts = self.config.get('max_contracts', 1)
|
||||||
|
self.action_space = spaces.Discrete(2 * self.max_contracts + 1)
|
||||||
|
|
||||||
|
# Define observation space: technical indicators plus current position and forecast.
|
||||||
|
self.tech_indicator_cols = [col for col in self.data.columns if col not in ['Date', 'Close']]
|
||||||
|
obs_dim = len(self.tech_indicator_cols) + 2 # +1 for normalized position, +1 for forecast.
|
||||||
|
self.observation_space = spaces.Box(low=-np.inf, high=np.inf, shape=(obs_dim,), dtype=np.float32)
|
||||||
|
|
||||||
|
# Trading variables
|
||||||
|
self.current_step = 0
|
||||||
|
self.contracts_held = 0
|
||||||
|
self.entry_price = None
|
||||||
|
|
||||||
|
# ATR‑based trailing stop parameters
|
||||||
|
self.atr_period = self.config.get('atr_period', 14)
|
||||||
|
self.trailing_stop_multiplier = self.config.get('trailing_stop_multiplier', 1.5)
|
||||||
|
self.trailing_stop = None
|
||||||
|
|
||||||
|
# Lock for thread‑safe LSTM predictions
|
||||||
|
self.lstm_lock = threading.Lock()
|
||||||
|
|
||||||
|
def _get_observation(self):
|
||||||
|
"""
|
||||||
|
Construct the observation vector from the current market data.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- observation (numpy.array): Observation vector for the current step.
|
||||||
|
"""
|
||||||
|
row = self.data.iloc[self.current_step]
|
||||||
|
tech_indicators = row[self.tech_indicator_cols].values.astype(np.float32)
|
||||||
|
norm_position = np.array([self.contracts_held / self.max_contracts], dtype=np.float32)
|
||||||
|
|
||||||
|
if self.lstm_model and self.current_step >= self.config.get('window_size', 15):
|
||||||
|
# For proper forecasting, construct an input sequence; here we use a placeholder.
|
||||||
|
forecast = np.array([0.001], dtype=np.float32)
|
||||||
|
else:
|
||||||
|
forecast = np.array([0.0], dtype=np.float32)
|
||||||
|
|
||||||
|
observation = np.concatenate([tech_indicators, norm_position, forecast])
|
||||||
|
return observation
|
||||||
|
|
||||||
|
def reset(self):
|
||||||
|
"""
|
||||||
|
Reset the environment to an initial state.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- observation (numpy.array): The initial observation.
|
||||||
|
"""
|
||||||
|
self.current_step = 0
|
||||||
|
self.contracts_held = 0
|
||||||
|
self.entry_price = None
|
||||||
|
self.trailing_stop = None
|
||||||
|
return self._get_observation()
|
||||||
|
|
||||||
|
def step(self, action):
|
||||||
|
"""
|
||||||
|
Execute one time step in the environment.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
- action (int): Action taken by the agent (discrete order adjustment).
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- observation (numpy.array): Next state observation.
|
||||||
|
- reward (float): Reward obtained from the action.
|
||||||
|
- done (bool): Whether the episode has ended.
|
||||||
|
- info (dict): Additional environment info.
|
||||||
|
"""
|
||||||
|
trade_adjustment = action - self.max_contracts
|
||||||
|
current_price = self.data.iloc[self.current_step]['Close']
|
||||||
|
|
||||||
|
transaction_cost = self.config.get('transaction_cost', 0.001) * abs(trade_adjustment) * current_price
|
||||||
|
|
||||||
|
reward = 0.0
|
||||||
|
if trade_adjustment != 0:
|
||||||
|
if self.contracts_held == 0:
|
||||||
|
self.contracts_held = trade_adjustment
|
||||||
|
self.entry_price = current_price
|
||||||
|
atr = self.data.iloc[self.current_step].get('ATR', 0.01)
|
||||||
|
self.trailing_stop = current_price - self.trailing_stop_multiplier * atr if trade_adjustment > 0 else current_price + self.trailing_stop_multiplier * atr
|
||||||
|
else:
|
||||||
|
prev_position = self.contracts_held
|
||||||
|
self.contracts_held += trade_adjustment
|
||||||
|
if self.contracts_held != 0:
|
||||||
|
self.entry_price = (self.entry_price * prev_position + current_price * trade_adjustment) / self.contracts_held
|
||||||
|
else:
|
||||||
|
self.entry_price = None
|
||||||
|
reward -= transaction_cost
|
||||||
|
else:
|
||||||
|
if self.contracts_held != 0 and self.entry_price is not None:
|
||||||
|
reward = (current_price - self.entry_price) * self.contracts_held
|
||||||
|
|
||||||
|
# ATR‑based trailing stop logic:
|
||||||
|
if self.trailing_stop is not None:
|
||||||
|
if self.contracts_held > 0 and current_price < self.trailing_stop:
|
||||||
|
self.logger.info("ATR trailing stop triggered for long position.")
|
||||||
|
reward += (current_price - self.entry_price) * self.contracts_held
|
||||||
|
self.contracts_held = 0
|
||||||
|
self.entry_price = None
|
||||||
|
self.trailing_stop = None
|
||||||
|
elif self.contracts_held < 0 and current_price > self.trailing_stop:
|
||||||
|
self.logger.info("ATR trailing stop triggered for short position.")
|
||||||
|
reward += (self.entry_price - current_price) * abs(self.contracts_held)
|
||||||
|
self.contracts_held = 0
|
||||||
|
self.entry_price = None
|
||||||
|
self.trailing_stop = None
|
||||||
|
|
||||||
|
self.current_step += 1
|
||||||
|
done = self.current_step >= len(self.data) - 1
|
||||||
|
observation = self._get_observation()
|
||||||
|
|
||||||
|
info = {
|
||||||
|
'contracts_held': self.contracts_held,
|
||||||
|
'entry_price': self.entry_price
|
||||||
|
}
|
||||||
|
return observation, reward, done, info
|
||||||
|
|
||||||
|
def render(self, mode='human'):
|
||||||
|
"""
|
||||||
|
Render the current state.
|
||||||
|
"""
|
||||||
|
current_price = self.data.iloc[self.current_step]['Close']
|
||||||
|
print(f"Step: {self.current_step}, Price: {current_price:.4f}, Contracts Held: {self.contracts_held}, Entry Price: {self.entry_price}")
|
||||||
|
|
||||||
Binary file not shown.
Binary file not shown.
39
src/MidasHL/t
Normal file
39
src/MidasHL/t
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
---------------------------------
|
||||||
|
------------------------------------------
|
||||||
|
| time/ | |
|
||||||
|
| fps | 469 |
|
||||||
|
| iterations | 49 |
|
||||||
|
| time_elapsed | 213 |
|
||||||
|
| total_timesteps | 100352 |
|
||||||
|
| train/ | |
|
||||||
|
| approx_kl | 0.0016052579 |
|
||||||
|
| clip_fraction | 0 |
|
||||||
|
| clip_range | 0.2 |
|
||||||
|
| entropy_loss | -1.09 |
|
||||||
|
| explained_variance | 0 |
|
||||||
|
| learning_rate | 0.0003 |
|
||||||
|
| loss | 3.19e+05 |
|
||||||
|
| n_updates | 480 |
|
||||||
|
| policy_gradient_loss | -0.00846 |
|
||||||
|
| value_loss | 6.55e+05 |
|
||||||
|
------------------------------------------
|
||||||
|
2025-04-13 00:42:59,396 - src.models.ppo_trader - INFO - PPO training completed.
|
||||||
|
2025-04-13 00:42:59,398 - absl - WARNING - You are saving your model as an HDF5 file via `model.save()` or `keras.saving.save_model(model)`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')` or `keras.saving.save_model(model, 'my_model.keras')`.
|
||||||
|
2025-04-13 00:42:59,479 - src.models.lstm_forecaster - INFO - Model and scalers saved to /home/midas/codeWS/Projects/MidasTechnologiesINC/MidasEngine/src/MidasHL/output/models
|
||||||
|
2025-04-13 00:42:59,494 - src.models.ppo_trader - INFO - PPO model saved to /home/midas/codeWS/Projects/MidasTechnologiesINC/MidasEngine/src/MidasHL/output/models/ppo_trader.zip
|
||||||
|
2025-04-13 00:42:59,494 - __main__ - INFO - FuturesTradingAI application finished.
|
||||||
|
midas:~/codeWS/Projects/MidasTechnologiesINC/MidasEngine/src/MidasHL$ ls
|
||||||
|
FuturesTradingAI.egg-info build dist examples output requirements.txt setup.py src t tests venv
|
||||||
|
midas:~/codeWS/Projects/MidasTechnologiesINC/MidasEngine/src/MidasHL$ python -m src.main --mode backtest
|
||||||
|
|
||||||
|
2025-04-13 01:11:16.950650: E external/local_xla/xla/stream_executor/cuda/cuda_fft.cc:477] Unable to register cuFFT factory: Attempting to register factory for plugin cuFFT when one has already been registered
|
||||||
|
WARNING: All log messages before absl::InitializeLog() is called are written to STDERR
|
||||||
|
E0000 00:00:1744506676.974089 1409604 cuda_dnn.cc:8310] Unable to register cuDNN factory: Attempting to register factory for plugin cuDNN when one has already been registered
|
||||||
|
E0000 00:00:1744506676.981420 1409604 cuda_blas.cc:1418] Unable to register cuBLAS factory: Attempting to register factory for plugin cuBLAS when one has already been registered
|
||||||
|
2025-04-13 01:11:17.005681: I tensorflow/core/platform/cpu_feature_guard.cc:210] This TensorFlow binary is optimized to use available CPU instructions in performance-critical operations.
|
||||||
|
To enable the following instructions: AVX2 FMA, in other operations, rebuild TensorFlow with the appropriate compiler flags.
|
||||||
|
2025-04-13 01:11:23,004 - __main__ - INFO - Starting FuturesTradingAI application...
|
||||||
|
2025-04-13 01:11:23,005 - __main__ - INFO - Loading data from /home/midas/codeWS/Projects/MidasTechnologiesINC/MidasEngine/src/MidasHL/data/sample_data.csv...
|
||||||
|
2025-04-13 01:11:23,005 - root - INFO - Loading data from: /home/midas/codeWS/Projects/MidasTechnologiesINC/MidasEngine/src/MidasHL/data/sample_data.csv
|
||||||
|
2025-04-13 01:11:23,005 - root - ERROR - Data file not found: /home/midas/codeWS/Projects/MidasTechnologiesINC/MidasEngine/src/MidasHL/data/sample_data.csv
|
||||||
|
|
||||||
Reference in New Issue
Block a user