updating for pulling

This commit is contained in:
2025-05-15 01:45:28 +00:00
parent 916b8b4611
commit 7bab96f66c
44 changed files with 249710 additions and 587 deletions

View File

@@ -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,811 - INFO - Saved best LSTM model and scaler objects.
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.

205
src/MidasHL/README.md Normal file
View File

@@ -0,0 +1,205 @@
# MidasHL
Hierarchical Learning for Live /MES Futures Trading
A modular, productiongrade trading system combining an LSTM forecaster and a PPO trading agent. Designed for 5minute `/MES` futures trading with advanced risk management and easy extensibility.
---
## Features
- **Hierarchical AI Architecture**
- **LSTM Forecaster** for shortterm price prediction (bidirectional layers, dropout, L2 regularization)
- **PPO Trading Agent** for decision making in a custom Gym environment
- **Risk Management**: ATRbased trailing stops, 2:1 risk/reward filter
- **Highly Modular**
- Clear separation of concerns: data loading, indicators, preprocessing, modeling, trading, execution, API, logging, resource monitoring
- **MultiFormat Data Support**
- CSV & JSON loaders with robust error handling and column standardization
- **PerformanceCritical C Module**
- Fast ATR computation via a Python Cextension (`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 Cextension
│ └── 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 Cextension (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 pertrade metrics and equity curves.
- **Parameter Tuning**:
- Leverage Optuna across both LSTM and PPO for automated hyperparameter search.
---

3
src/MidasHL/TODO.txt Normal file
View 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,

View 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

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -9,4 +9,5 @@ joblib
flask
psutil
GPUtil
shimmy

View File

Binary file not shown.

View File

@@ -42,7 +42,7 @@ PPO_PARAMS = {
# Trading environment configuration
TRADING_ENV_CONFIG = {
'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 multicontract trading)
'risk_reward_threshold': 2.0, # Minimum risk/reward ratio required to execute a trade (2:1)
'atr_period': 14, # Period for ATR calculation

View File

@@ -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 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':
@@ -40,36 +17,20 @@ def load_data(file_path):
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}")
logging.error(f"Error loading data: {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)
# unify to lowercase for consistency
df.columns = df.columns.str.lower()
logging.info(f"Data columns after renaming: {df.columns.tolist()}")
df.sort_values('Date', inplace=True)
# sort by time
df.sort_values('time', inplace=True)
df.reset_index(drop=True, inplace=True)
logging.info("Data loaded and sorted successfully.")
logging.info(f"Data columns after normalization: {df.columns.tolist()}")
return df

View 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

View 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 pricerelated columns (open, high, low, close) using minmax 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

View 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): Shortterm EMA span.
- span_long (int): Longterm 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 OnBalance 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

View File

@@ -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 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.
"""
def preprocess_data(df: pd.DataFrame) -> pd.DataFrame:
logger = logging.getLogger(__name__)
logger.info("Starting data preprocessing...")
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()])
# enforce lowercase
df = df.rename(columns=str.lower)
df_processed = pd.DataFrame(processed_data)
# feature: high/low ratio
if 'high' in df.columns and 'low' in df.columns:
df['hl_ratio'] = df['high'] / (df['low'] + 1e-9)
else:
df['hl_ratio'] = np.nan
logger.warning("Columns 'high' and/or 'low' not found; 'hl_ratio' set to NaN.")
# Normalization: Scale pricerelated columns (open, high, low, close) using minmax scaling.
# min-max normalize price columns
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
if col in df.columns:
mi, ma = df[col].min(), df[col].max()
df[col] = (df[col] - mi) / (ma - mi + 1e-9)
else:
logger.warning(f"Column '{col}' missing; skipping normalization.")
logger.info("Data preprocessing completed.")
return df

View File

@@ -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 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.
"""
def compute_rsi(series: pd.Series, window: int = 14) -> pd.Series:
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
return 100 - (100 / (1 + rs))
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): Shortterm EMA span.
- span_long (int): Longterm EMA span.
- span_signal (int): Signal line EMA span.
Returns:
- pandas.Series: MACD histogram values.
"""
def compute_macd(series: pd.Series, span_short=12, span_long=26, span_signal=9) -> pd.Series:
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
signal = macd_line.ewm(span=span_signal, adjust=False).mean()
return macd_line - signal
def compute_obv(df):
"""
Compute OnBalance Volume (OBV).
Parameters:
- df (pandas.DataFrame): DataFrame containing 'close' and 'volume'.
Returns:
- pandas.Series: OBV values.
"""
def compute_obv(df: pd.DataFrame) -> pd.Series:
direction = np.sign(df['close'].diff()).fillna(0)
obv = (direction * df['volume']).cumsum()
return obv
return (direction * df['volume']).cumsum()
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']
def compute_adx(df: pd.DataFrame, window: int = 14) -> pd.Series:
high, low, close = df['high'], df['low'], 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))
up = high.diff()
down = low.diff().abs()
plus_dm = np.where((up > down) & (up > 0), up, 0)
minus_dm = np.where((down > up) & (down > 0), down, 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
return dx.rolling(window=window, min_periods=window).mean()
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.
"""
def compute_bollinger_bands(series: pd.Series, window: int = 20, num_std: int = 2):
sma = series.rolling(window=window, min_periods=window).mean()
std = series.rolling(window=window, min_periods=window).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)
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])
def compute_mfi(df: pd.DataFrame, window: int = 14) -> pd.Series:
tp = (df['high'] + df['low'] + df['close']) / 3
mf = tp * df['volume']
pos_flow, neg_flow = [], []
for i in range(1, len(tp)):
if tp.iat[i] > tp.iat[i-1]:
pos_flow.append(mf.iat[i]); neg_flow.append(0)
elif tp.iat[i] < tp.iat[i-1]:
pos_flow.append(0); neg_flow.append(mf.iat[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))))
pos_flow.append(0); neg_flow.append(0)
pos_mf = pd.Series(pos_flow).rolling(window=window, min_periods=window).sum()
neg_mf = pd.Series(neg_flow).rolling(window=window, min_periods=window).sum()
mfi = 100 - (100 / (1 + (pos_mf / (neg_mf + 1e-9))))
mfi.index = df.index[1:]
mfi = mfi.reindex(df.index)
return mfi
return mfi.reindex(df.index)
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']
def compute_atr(df: pd.DataFrame, period: int = 14) -> pd.Series:
high, low, close = df['high'], df['low'], 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)
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):
"""
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
def calculate_all_indicators(df: pd.DataFrame) -> pd.DataFrame:
df['rsi'] = compute_rsi(df['close'])
df['macd'] = compute_macd(df['close'])
df['obv'] = compute_obv(df)
df['adx'] = compute_adx(df)
ub, lb, bw = compute_bollinger_bands(df['close'])
df['bb_upper'], df['bb_lower'], df['bb_bandwidth'] = ub, lb, bw
df['mfi'] = compute_mfi(df)
df['atr'] = compute_atr(df)
df['sma_5'] = df['close'].rolling(window=5, min_periods=5).mean()
df['sma_10'] = df['close'].rolling(window=10, min_periods=10).mean()
df.dropna(inplace=True)
return df

View File

@@ -1,16 +1,11 @@
"""
src/main.py
Main entry point for the FuturesTradingAI application.
This script parses commandline 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 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.utils.logger import setup_logging
from src.data.loader import load_data
@@ -22,58 +17,58 @@ 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 commandline 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"
"--data", type=str, default=SAMPLE_DATA_PATH,
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()
def main():
# Parse commandline arguments
args = parse_args()
# Ensure necessary output directories exist
def main():
args = parse_args()
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)
# start resource monitor
monitor_thread = threading.Thread(
target=start_resource_monitor,
args=(MONITOR_CONFIG['interval'],),
daemon=True
)
monitor_thread.start()
# Load historical market data
# load & prepare 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...")
logger.info("Calculating technical indicators...")
df = calculate_all_indicators(df)
# Advanced data preprocessing and feature engineering
logger.info("Preprocessing data...")
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":
# Train LSTM forecaster
logger.info("Initializing LSTM forecaster...")
lstm_forecaster = LSTMForecaster(data=df_processed)
lstm_forecaster.train()
# Train PPO trading agent using custom environment
# Train PPO trading agent
logger.info("Initializing PPO trader...")
ppo_trader = PPOTrader(data=df_processed, lstm_model=lstm_forecaster.model)
ppo_trader.train()
@@ -83,22 +78,32 @@ def main():
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)
logger.info("Running backtest...")
lstm_path = os.path.join(MODEL_DIR, 'lstm_forecaster.h5')
feat_p = os.path.join(MODEL_DIR, 'scaler_features.pkl')
targ_p = os.path.join(MODEL_DIR, 'scaler_target.pkl')
logger.info(f"Loading LSTM model from {lstm_path}")
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":
# 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()
live_trader = HierarchicalTrader(data=df_processed, env=env)
live_trader.live_trading()
logger.info("FuturesTradingAI application finished.")
logger.info("Application finished.")
if __name__ == "__main__":
if __name__ == '__main__':
main()

View File

View File

@@ -1,29 +1,14 @@
"""
src/models/hierarchical.py
This module integrates the LSTM forecaster and the PPO trading agent to form a hierarchical decisionmaking system.
It fuses shortterm price forecasts with trading signals and enforces risk management constraints before executing trades.
"""
# src/models/hierarchical.py
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.
HierarchicalTrader integrates LSTM forecasts with PPO signals,
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):
"""
Initialize HierarchicalTrader.
Parameters:
- data (pandas.DataFrame): Preprocessed market data.
- lstm_model: Pretrained 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
@@ -32,49 +17,34 @@ class HierarchicalTrader:
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
return reward / (risk + 1e-9)
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]
# Ensure correct dtype (float32) and shape ([1, obs_dim])
obs = np.array(observation, dtype=np.float32).reshape(1, -1)
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)))
action, _ = self.ppo_model.predict(obs, deterministic=True)
# 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
atr = 0.01 # Placeholder
# Extract forecast and position
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)
self.logger.info(f"Calculated risk/reward ratio: {rr_ratio:.2f}")
@@ -86,60 +56,35 @@ class HierarchicalTrader:
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.
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])
Returns:
- dict: Backtesting results (e.g., total PnL, win rate).
"""
def backtest(self):
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, num_trades = 0.0, 0
for _, row in self.data.iterrows():
obs = self._create_observation(row)
signal = self.generate_trade_signal(obs)
reward = signal * 0.001
total_reward += reward
if signal != 0:
num_trades += 1
results = {
return {
'total_reward': total_reward,
'num_trades': num_trades,
'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):
"""
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)
obs = self._create_observation(self.data.iloc[-1])
signal = self.generate_trade_signal(obs)
self.logger.info(f"Live trade signal: {signal}")
# In production, signal triggers execution via the execution module.
time.sleep(5) # Simulate 5minute reevaluation in a live setting.
time.sleep(5)

View File

@@ -19,45 +19,42 @@ from sklearn.preprocessing import MinMaxScaler
import optuna
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):
"""
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__)
# 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.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
# Define target column and feature columns. Here we assume 'close' is the target.
# 2) Define target and feature columns
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 = [
col for col in self.data.columns
if col not in [self.target_column, 'time']
]
# Initialize scalers for features and target
# 3) Scalers
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()
# Build model based on hyperparameters or default settings
# 5) Build & compile
if self.hyperparams is None:
self.hyperparams = {
'num_layers': 2,
'units': 64,
'dropout_rate': 0.2,
'num_layers': 2,
'units': 64,
'dropout_rate': 0.2,
'learning_rate': 1e-3,
'optimizer': 'Adam',
'l2_reg': 1e-4
'optimizer': 'Adam',
'l2_reg': 1e-4
}
self.model = self._build_model()

View File

@@ -0,0 +1,163 @@
"""
src/models/hierarchical.py
This module integrates the LSTM forecaster and the PPO trading agent to form a hierarchical decisionmaking system.
It fuses shortterm 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: Pretrained 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 5minute reevaluation in a live setting.

110
src/MidasHL/src/old_main.py Normal file
View File

@@ -0,0 +1,110 @@
"""
src/main.py
Main entry point for the FuturesTradingAI application.
This script parses commandline 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 commandline 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 commandline 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()

View File

View File

@@ -1,163 +1,91 @@
"""
src/trading/env.py
This module defines a custom OpenAI Gym environment for /MES futures trading on a 5minute timeframe.
The environment includes logic for:
- Tracking positions (long/short)
- Evaluating positions every 5 minutes
- Implementing ATRbased trailing stops for risk management
- Allowing a single contract initially with hooks for multicontract 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: Pretrained LSTM model for price forecasting (optional).
- config (dict): Configuration parameters for the environment.
"""
super(FuturesTradingEnv, self).__init__()
super().__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 {}
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.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 ['time', 'close']]
obs_dim = len(self.tech_indicator_cols) + 2 # +1 for normalized position, +1 for forecast.
# observation space: drop time and 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
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.contracts_held = 0
self.entry_price = None
# ATRbased 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 threadsafe LSTM predictions
self.lstm_lock = threading.Lock()
# ATR stop params
self.atr_period = self.config.get('atr_period', 14)
self.trailing_stop_multiplier = self.config.get('trailing_stop_multiplier', 1.5)
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)
tech = row[self.tech_indicator_cols].values.astype(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):
# 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
return np.concatenate([tech, pos, forecast])
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
self.current_step, self.contracts_held = 0, 0
self.entry_price, self.trailing_stop = None, 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
trade_adj = action - self.max_contracts
current_price = self.data.at[self.current_step, 'close']
cost = self.config.get('transaction_cost', 0.001) * abs(trade_adj) * current_price
reward = 0.0
if trade_adjustment != 0:
if trade_adj != 0:
if self.contracts_held == 0:
self.contracts_held = trade_adjustment
self.contracts_held = trade_adj
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
atr = self.data.at[self.current_step, 'atr']
self.trailing_stop = (current_price - self.trailing_stop_multiplier * atr
if trade_adj > 0 else current_price + self.trailing_stop_multiplier * atr)
else:
prev_position = self.contracts_held
self.contracts_held += trade_adjustment
prev = self.contracts_held
self.contracts_held += trade_adj
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:
self.entry_price = None
reward -= transaction_cost
reward -= cost
else:
if self.contracts_held != 0 and self.entry_price is not None:
reward = (current_price - self.entry_price) * self.contracts_held
# ATRbased trailing stop logic:
# trailing stop
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
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.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
obs = self._get_observation()
info = {'contracts_held': self.contracts_held, 'entry_price': self.entry_price}
return obs, 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}")
price = self.data.at[self.current_step, 'close']
print(f"Step {self.current_step}, Price {price:.4f}, Held {self.contracts_held}")

View File

@@ -41,12 +41,12 @@ def get_live_market_data():
"""
logger = logging.getLogger(__name__)
live_data = {
'time': None,
'open': None,
'high': None,
'low': None,
'close': 1.0, # Dummy value for testing
'volume': None
'Date': None,
'Open': None,
'High': None,
'Low': None,
'Close': 1.0, # Dummy value for testing
'Volume': None
}
logger.info("Retrieved live market data (placeholder).")
return live_data

View File

@@ -0,0 +1,163 @@
"""
src/trading/env.py
This module defines a custom OpenAI Gym environment for /MES futures trading on a 5minute timeframe.
The environment includes logic for:
- Tracking positions (long/short)
- Evaluating positions every 5 minutes
- Implementing ATRbased trailing stops for risk management
- Allowing a single contract initially with hooks for multicontract 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: Pretrained 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
# ATRbased 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 threadsafe 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
# ATRbased 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}")

39
src/MidasHL/t Normal file
View 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