mirror of
https://github.com/NicolasBohn/NexQuant.git
synced 2026-07-27 15:37:44 +00:00
feat: Add model loader system (same as prompts)
New structure:
- models/standard/*.py: Default models (XGBoost, LightGBM, RandomForest)
- models/local/*.py: Your improved models (NOT in Git!)
- models/README.md: Documentation
- rdagent/components/model_loader.py: Model loader with priority
Features:
- Loader checks models/local/ first (your better models)
- Falls back to models/standard/ if no local version
- Supports versioned models (model_v2.py, model_v1.py)
- Lists available models
- Test function included
.gitignore updated:
- models/local/ excluded (your proprietary models)
- *.local.py excluded
- *_private.py excluded
Usage:
from rdagent.components.model_loader import load_model
model = load_model('xgboost_factor') # Auto-loads your better version!
Standard models included:
- xgboost_factor.py: XGBoost for tabular data
- lightgbm_factor.py: LightGBM (faster than XGBoost)
This commit is contained in:
@@ -87,6 +87,11 @@ prompts/local/
|
||||
*.local.yaml
|
||||
*_private.yaml
|
||||
|
||||
# Private models (your improved versions)
|
||||
models/local/
|
||||
*.local.py
|
||||
*_private.py
|
||||
|
||||
# Test credentials
|
||||
.env.test
|
||||
*.test.env
|
||||
|
||||
@@ -0,0 +1,239 @@
|
||||
# Predix Models
|
||||
|
||||
This directory contains all ML model definitions for Predix trading factors.
|
||||
|
||||
---
|
||||
|
||||
## 📁 Directory Structure
|
||||
|
||||
```
|
||||
models/
|
||||
├── standard/ # Default models (committed to Git)
|
||||
│ ├── xgboost_factor.py # XGBoost for tabular data
|
||||
│ ├── lightgbm_factor.py # LightGBM (faster than XGBoost)
|
||||
│ └── randomforest_factor.py # Baseline model
|
||||
│
|
||||
├── local/ # YOUR IMPROVED MODELS (not in Git!)
|
||||
│ ├── transformer_factor.py # Your Transformer
|
||||
│ ├── tcn_factor.py # Your TCN
|
||||
│ ├── patchtst_factor.py # Your PatchTST
|
||||
│ ├── cnn_lstm_hybrid.py # Your Hybrid model
|
||||
│ └── optimized_xgboost.py # Your optimized XGBoost
|
||||
│
|
||||
└── README.md # This file
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 How It Works
|
||||
|
||||
**Model Loading Priority:**
|
||||
|
||||
1. **`models/local/*.py`** ← Your improved models (loaded first!)
|
||||
2. **`models/standard/*.py`** ← Default models (fallback)
|
||||
|
||||
**Example:**
|
||||
```python
|
||||
from rdagent.components.model_loader import load_model
|
||||
|
||||
# Load XGBoost model
|
||||
# If models/local/xgboost_factor*.py exists → loads that
|
||||
# Otherwise → loads from models/standard/
|
||||
model_factory = load_model("xgboost_factor")
|
||||
|
||||
# Create model instance
|
||||
model = model_factory(max_depth=8, learning_rate=0.1)
|
||||
|
||||
# Train
|
||||
model.fit(X_train, y_train)
|
||||
|
||||
# Predict
|
||||
predictions = model.predict(X_test)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📝 Available Standard Models
|
||||
|
||||
| Model | File | Use Case |
|
||||
|-------|------|----------|
|
||||
| **XGBoost** | `xgboost_factor.py` | Tabular factors, fast training |
|
||||
| **LightGBM** | `lightgbm_factor.py` | Large datasets, faster than XGBoost |
|
||||
| **RandomForest** | `randomforest_factor.py` | Baseline, robust |
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Creating Your Improved Models
|
||||
|
||||
### Step 1: Create Local Model File
|
||||
|
||||
```bash
|
||||
# Create local directory (if not exists)
|
||||
mkdir -p models/local
|
||||
|
||||
# Copy standard model as template
|
||||
cp models/standard/xgboost_factor.py models/local/optimized_xgboost.py
|
||||
```
|
||||
|
||||
### Step 2: Improve Your Model
|
||||
|
||||
```python
|
||||
# models/local/optimized_xgboost.py
|
||||
|
||||
class XGBoostFactorModel:
|
||||
"""Your optimized version with better hyperparameters."""
|
||||
|
||||
def __init__(self, **params):
|
||||
self.params = {
|
||||
'objective': 'reg:squarederror',
|
||||
'max_depth': 8, # Deeper trees
|
||||
'learning_rate': 0.03, # Slower learning
|
||||
'n_estimators': 1000, # More estimators
|
||||
'subsample': 0.9, # Less dropout
|
||||
'colsample_bytree': 0.9,
|
||||
'random_state': 42,
|
||||
# Your custom params
|
||||
'gamma': 0.1, # Regularization
|
||||
'min_child_weight': 3,
|
||||
**params
|
||||
}
|
||||
# ... rest of implementation
|
||||
```
|
||||
|
||||
### Step 3: Use in Trading
|
||||
|
||||
Your improved models are automatically used when running:
|
||||
|
||||
```python
|
||||
from rdagent.components.model_loader import load_model
|
||||
|
||||
# Auto-loads your optimized version!
|
||||
model_factory = load_model("xgboost_factor")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔐 Security
|
||||
|
||||
**What to keep in `models/local/`:**
|
||||
|
||||
✅ Your proprietary model architectures
|
||||
✅ Optimized hyperparameters
|
||||
✅ Custom feature engineering
|
||||
✅ Ensemble methods
|
||||
✅ Trade secrets & alpha-generating logic
|
||||
|
||||
**What NOT to commit to Git:**
|
||||
|
||||
❌ Anything in `models/local/` (already in .gitignore)
|
||||
❌ Files with `.local.py` suffix
|
||||
❌ Files with `_private.py` suffix
|
||||
|
||||
---
|
||||
|
||||
## 📊 Best Practices
|
||||
|
||||
### 1. Version Your Models
|
||||
|
||||
```python
|
||||
# Good naming:
|
||||
models/local/
|
||||
├── xgboost_v2.py # Version 2
|
||||
├── xgboost_v3_optimized.py # Version 3 optimized
|
||||
└── lightgbm_lstm_hybrid_v1.py # Hybrid v1
|
||||
```
|
||||
|
||||
### 2. Document Changes
|
||||
|
||||
```python
|
||||
# models/local/optimized_xgboost_v2.py
|
||||
"""
|
||||
XGBoost Factor Model v2.0
|
||||
|
||||
Changes from v1:
|
||||
- Increased max_depth from 6 to 8
|
||||
- Added gamma regularization
|
||||
- Increased n_estimators from 500 to 1000
|
||||
- Target: +2% ARR, +0.2 Sharpe
|
||||
|
||||
Author: Your Name
|
||||
Date: 2026-04-02
|
||||
"""
|
||||
```
|
||||
|
||||
### 3. Test Performance
|
||||
|
||||
```python
|
||||
# Compare model versions
|
||||
from rdagent.components.model_loader import load_model
|
||||
|
||||
# Load standard
|
||||
std_model = load_model("xgboost_factor", local_only=False)
|
||||
|
||||
# Load local (if exists)
|
||||
local_model = load_model("xgboost_factor", local_only=True)
|
||||
|
||||
# Backtest both and compare
|
||||
# ...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Advanced Usage
|
||||
|
||||
### Load All Models
|
||||
|
||||
```python
|
||||
from rdagent.components.model_loader import list_available_models
|
||||
|
||||
all_models = list_available_models()
|
||||
print(f"Standard: {all_models['standard']}")
|
||||
print(f"Local: {all_models['local']}")
|
||||
```
|
||||
|
||||
### Force Local Model
|
||||
|
||||
```python
|
||||
# Raise error if local model not found
|
||||
model = load_model("transformer_factor", local_only=True)
|
||||
```
|
||||
|
||||
### Custom Model Path
|
||||
|
||||
```python
|
||||
from rdagent.components.model_loader import load_module_from_path
|
||||
from pathlib import Path
|
||||
|
||||
# Load from custom location
|
||||
module = load_module_from_path(
|
||||
Path("/path/to/my/custom_model.py"),
|
||||
"custom_model"
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📈 Model Selection Guide
|
||||
|
||||
| Scenario | Recommended Model | Why |
|
||||
|----------|------------------|-----|
|
||||
| **Tabular Factors** | XGBoost / LightGBM | Fast, interpretable |
|
||||
| **Large Dataset** | LightGBM | Lower memory, faster |
|
||||
| **Baseline** | RandomForest | Robust, no tuning needed |
|
||||
| **Time-Series Patterns** | LSTM / GRU (local) | Sequential dependencies |
|
||||
| **Multi-Scale** | TCN (local) | Different time horizons |
|
||||
| **Long-Range** | Transformer (local) | Attention mechanism |
|
||||
| **Best Performance** | Ensemble (local) | Combine multiple models |
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Next Steps
|
||||
|
||||
1. **Review standard models:** `cat models/standard/*.py`
|
||||
2. **Create your improved version:** `mkdir -p models/local`
|
||||
3. **Test:** `python rdagent/components/model_loader.py`
|
||||
4. **Run trading:** `rdagent fin_quant`
|
||||
|
||||
---
|
||||
|
||||
**Your improved models in `models/local/` are your competitive edge! 🚀**
|
||||
@@ -0,0 +1,98 @@
|
||||
"""
|
||||
LightGBM Factor Model - Standard Version
|
||||
|
||||
Usage:
|
||||
from rdagent.components.model_loader import load_model
|
||||
model = load_model("lightgbm_factor")
|
||||
"""
|
||||
|
||||
import lightgbm as lgb
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
class LightGBMFactorModel:
|
||||
"""
|
||||
LightGBM-based factor model for EUR/USD trading.
|
||||
|
||||
Features:
|
||||
- Faster than XGBoost
|
||||
- Lower memory usage
|
||||
- Good for large datasets
|
||||
"""
|
||||
|
||||
def __init__(self, **params):
|
||||
self.params = {
|
||||
'objective': 'regression',
|
||||
'metric': 'mse',
|
||||
'num_leaves': 31,
|
||||
'learning_rate': 0.05,
|
||||
'feature_fraction': 0.8,
|
||||
'bagging_fraction': 0.8,
|
||||
'bagging_freq': 5,
|
||||
'verbose': -1,
|
||||
'random_state': 42,
|
||||
**params
|
||||
}
|
||||
self.model = None
|
||||
self.feature_names = None
|
||||
|
||||
def fit(self, X, y, feature_names=None, **fit_params):
|
||||
"""Train the model."""
|
||||
self.feature_names = feature_names
|
||||
|
||||
# Create LightGBM datasets
|
||||
train_data = lgb.Dataset(X, label=y, feature_name=feature_names if feature_names else 'auto')
|
||||
|
||||
self.model = lgb.train(
|
||||
self.params,
|
||||
train_data,
|
||||
num_boost_round=500,
|
||||
**fit_params
|
||||
)
|
||||
|
||||
return self
|
||||
|
||||
def predict(self, X):
|
||||
"""Generate predictions."""
|
||||
if self.model is None:
|
||||
raise ValueError("Model not trained. Call fit() first.")
|
||||
|
||||
return self.model.predict(X)
|
||||
|
||||
def get_feature_importance(self, top_n=10, importance_type='gain'):
|
||||
"""Get top N most important features."""
|
||||
if self.model is None:
|
||||
raise ValueError("Model not trained.")
|
||||
|
||||
importance = self.model.feature_importance(importance_type=importance_type)
|
||||
if self.feature_names is not None:
|
||||
indices = np.argsort(importance)[::-1][:top_n]
|
||||
return [(self.feature_names[i], importance[i]) for i in indices]
|
||||
return importance
|
||||
|
||||
def save(self, path: str):
|
||||
"""Save model to file."""
|
||||
Path(path).parent.mkdir(parents=True, exist_ok=True)
|
||||
self.model.save_model(path)
|
||||
print(f"✓ Model saved to {path}")
|
||||
|
||||
def load(self, path: str):
|
||||
"""Load model from file."""
|
||||
self.model = lgb.Booster(model_file=path)
|
||||
print(f"✓ Model loaded from {path}")
|
||||
return self
|
||||
|
||||
|
||||
# Convenience function
|
||||
def create_lightgbm_factor_model(**params):
|
||||
"""Create LightGBM factor model."""
|
||||
return LightGBMFactorModel(**params)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Test
|
||||
print("=== LightGBM Factor Model Test ===")
|
||||
model = create_lightgbm_factor_model()
|
||||
print(f"✓ Model created with params: {model.params}")
|
||||
@@ -0,0 +1,90 @@
|
||||
"""
|
||||
XGBoost Factor Model - Standard Version
|
||||
|
||||
Usage:
|
||||
from rdagent.components.model_loader import load_model
|
||||
model = load_model("xgboost_factor")
|
||||
"""
|
||||
|
||||
import xgboost as xgb
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
class XGBoostFactorModel:
|
||||
"""
|
||||
XGBoost-based factor model for EUR/USD trading.
|
||||
|
||||
Features:
|
||||
- Handles tabular data efficiently
|
||||
- Built-in feature importance
|
||||
- Fast training and inference
|
||||
"""
|
||||
|
||||
def __init__(self, **params):
|
||||
self.params = {
|
||||
'objective': 'reg:squarederror',
|
||||
'max_depth': 6,
|
||||
'learning_rate': 0.05,
|
||||
'n_estimators': 500,
|
||||
'subsample': 0.8,
|
||||
'colsample_bytree': 0.8,
|
||||
'random_state': 42,
|
||||
**params
|
||||
}
|
||||
self.model = None
|
||||
self.feature_names = None
|
||||
|
||||
def fit(self, X, y, feature_names=None, **fit_params):
|
||||
"""Train the model."""
|
||||
self.feature_names = feature_names
|
||||
|
||||
self.model = xgb.XGBRegressor(**self.params)
|
||||
self.model.fit(X, y, **fit_params)
|
||||
|
||||
return self
|
||||
|
||||
def predict(self, X):
|
||||
"""Generate predictions."""
|
||||
if self.model is None:
|
||||
raise ValueError("Model not trained. Call fit() first.")
|
||||
|
||||
return self.model.predict(X)
|
||||
|
||||
def get_feature_importance(self, top_n=10):
|
||||
"""Get top N most important features."""
|
||||
if self.model is None:
|
||||
raise ValueError("Model not trained.")
|
||||
|
||||
importance = self.model.feature_importances_
|
||||
if self.feature_names is not None:
|
||||
indices = np.argsort(importance)[::-1][:top_n]
|
||||
return [(self.feature_names[i], importance[i]) for i in indices]
|
||||
return importance
|
||||
|
||||
def save(self, path: str):
|
||||
"""Save model to file."""
|
||||
Path(path).parent.mkdir(parents=True, exist_ok=True)
|
||||
self.model.save_model(path)
|
||||
print(f"✓ Model saved to {path}")
|
||||
|
||||
def load(self, path: str):
|
||||
"""Load model from file."""
|
||||
self.model = xgb.XGBRegressor()
|
||||
self.model.load_model(path)
|
||||
print(f"✓ Model loaded from {path}")
|
||||
return self
|
||||
|
||||
|
||||
# Convenience function
|
||||
def create_xgboost_factor_model(**params):
|
||||
"""Create XGBoost factor model."""
|
||||
return XGBoostFactorModel(**params)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Test
|
||||
print("=== XGBoost Factor Model Test ===")
|
||||
model = create_xgboost_factor_model()
|
||||
print(f"✓ Model created with params: {model.params}")
|
||||
@@ -38,24 +38,18 @@ def get_job_options(base_path: Path) -> list[str]:
|
||||
has_root_tasks = False
|
||||
job_dirs = []
|
||||
|
||||
# Security fix: Validate base_path to prevent path traversal
|
||||
# Resolve to absolute path and ensure it's within allowed boundaries
|
||||
# Security: Validate base_path to prevent path traversal
|
||||
# Resolve to absolute path and ensure it's within the current working directory.
|
||||
try:
|
||||
base_path_resolved = base_path.resolve(strict=False)
|
||||
cwd_resolved = Path.cwd().resolve()
|
||||
|
||||
# Ensure base_path is within or relative to current working directory
|
||||
# This prevents accessing arbitrary filesystem locations
|
||||
try:
|
||||
base_path_resolved.relative_to(cwd_resolved)
|
||||
except ValueError:
|
||||
# Path is outside CWD, check if it's a safe relative path
|
||||
if base_path_resolved.is_relative_to(cwd_resolved):
|
||||
pass # OK
|
||||
else:
|
||||
# Path is completely outside project, reject it
|
||||
st.error(f"Invalid log base path: Must be within project directory")
|
||||
return options
|
||||
safe_root = Path.cwd().resolve()
|
||||
base_path_resolved = base_path.expanduser().resolve(strict=False)
|
||||
|
||||
# Ensure base_path_resolved is within safe_root; raises ValueError if not.
|
||||
base_path_resolved.relative_to(safe_root)
|
||||
except ValueError:
|
||||
# Path is outside the allowed root, reject it.
|
||||
st.error("Invalid log base path: Must be within project directory")
|
||||
return options
|
||||
except (OSError, RuntimeError) as e:
|
||||
st.error(f"Invalid path: {e}")
|
||||
return options
|
||||
@@ -175,22 +169,25 @@ def main():
|
||||
# ========== Main Content ==========
|
||||
if view_mode == "Job Summary":
|
||||
st.title("📊 FT Job Summary")
|
||||
|
||||
# Security fix: Validate job_folder to prevent path traversal
|
||||
|
||||
# Security: Validate job_folder to prevent path traversal
|
||||
# Only allow paths within the base_path directory
|
||||
try:
|
||||
job_path = Path(job_folder).resolve()
|
||||
base_path_resolved = Path(base_path).resolve()
|
||||
|
||||
# Ensure job_path is within base_path (prevent path traversal)
|
||||
job_path.relative_to(base_path_resolved)
|
||||
|
||||
safe_root = Path(base_path).resolve()
|
||||
job_path = Path(job_folder).expanduser().resolve(strict=False)
|
||||
|
||||
# Ensure job_path is within safe_root (prevent path traversal)
|
||||
job_path.relative_to(safe_root)
|
||||
|
||||
if job_path.exists():
|
||||
render_job_summary(job_path, is_root=is_root_job)
|
||||
else:
|
||||
st.warning(f"Job folder not found: {job_folder}")
|
||||
except (ValueError, RuntimeError) as e:
|
||||
st.error(f"Invalid job folder path: {e}")
|
||||
except ValueError:
|
||||
st.error("Invalid job folder path: Must be within base directory")
|
||||
st.info("Please select a valid job from the sidebar.")
|
||||
except (OSError, RuntimeError) as e:
|
||||
st.error(f"Invalid path: {e}")
|
||||
st.info("Please select a valid job from the sidebar.")
|
||||
return
|
||||
|
||||
|
||||
@@ -29,11 +29,25 @@ STANDARD_PROMPTS_FILE = PROMPTS_DIR / "standard_prompts.yaml"
|
||||
|
||||
|
||||
def get_local_prompt_path(name: str) -> Optional[Path]:
|
||||
"""Find local prompt file by name."""
|
||||
"""Find local prompt file by name.
|
||||
|
||||
Priority:
|
||||
1. {name}_v2.yaml (latest version)
|
||||
2. {name}_v1.yaml
|
||||
3. {name}.yaml
|
||||
"""
|
||||
if not LOCAL_PROMPTS_DIR.exists():
|
||||
return None
|
||||
|
||||
# Try different file extensions
|
||||
# Try versioned files first (v2, v1, etc.)
|
||||
for version in ["v2", "v1"]:
|
||||
for ext in ["yaml", "yml"]:
|
||||
path = LOCAL_PROMPTS_DIR / f"{name}_{version}.{ext}"
|
||||
if path.exists():
|
||||
print(f" (found versioned: {name}_{version}.{ext})")
|
||||
return path
|
||||
|
||||
# Try exact name
|
||||
for ext in ["yaml", "yml"]:
|
||||
path = LOCAL_PROMPTS_DIR / f"{name}.{ext}"
|
||||
if path.exists():
|
||||
@@ -187,7 +201,19 @@ if __name__ == "__main__":
|
||||
try:
|
||||
prompt = load_prompt("factor_discovery")
|
||||
print(f"✓ Loaded factor_discovery prompt")
|
||||
print(f" System: {len(prompt.get('system', ''))} chars")
|
||||
print(f" User: {len(prompt.get('user', ''))} chars")
|
||||
|
||||
# Handle nested dict structure (local prompts)
|
||||
if isinstance(prompt, dict):
|
||||
if 'factor_discovery' in prompt:
|
||||
# Local prompt structure
|
||||
fd = prompt['factor_discovery']
|
||||
print(f" System: {len(fd.get('system', ''))} chars")
|
||||
print(f" User: {len(fd.get('user', ''))} chars")
|
||||
else:
|
||||
# Standard prompt structure
|
||||
print(f" System: {len(prompt.get('system', ''))} chars")
|
||||
print(f" User: {len(prompt.get('user', ''))} chars")
|
||||
else:
|
||||
print(f" Content: {len(str(prompt))} chars")
|
||||
except FileNotFoundError as e:
|
||||
print(f"✗ Error: {e}")
|
||||
|
||||
@@ -0,0 +1,194 @@
|
||||
"""
|
||||
Predix Model Loader
|
||||
|
||||
Loads models from:
|
||||
1. models/local/*.py (your improved models - not in Git)
|
||||
2. models/standard/*.py (default models - in Git)
|
||||
|
||||
Usage:
|
||||
from rdagent.components.model_loader import load_model
|
||||
|
||||
# Load XGBoost model
|
||||
model = load_model("xgboost_factor")
|
||||
|
||||
# Load your improved version (if exists in models/local/)
|
||||
model = load_model("transformer_factor") # Auto-loads from local if exists
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import importlib.util
|
||||
from pathlib import Path
|
||||
from typing import Optional, Any
|
||||
|
||||
|
||||
# Base paths
|
||||
BASE_DIR = Path(__file__).parent.parent.parent # Predix/
|
||||
MODELS_DIR = BASE_DIR / "models"
|
||||
LOCAL_MODELS_DIR = MODELS_DIR / "local"
|
||||
STANDARD_MODELS_DIR = MODELS_DIR / "standard"
|
||||
|
||||
|
||||
def get_local_model_path(name: str) -> Optional[Path]:
|
||||
"""Find local model file by name.
|
||||
|
||||
Priority:
|
||||
1. {name}_v2.py (latest version)
|
||||
2. {name}_v1.py
|
||||
3. {name}.py
|
||||
"""
|
||||
if not LOCAL_MODELS_DIR.exists():
|
||||
return None
|
||||
|
||||
# Try versioned files first (v2, v1, etc.)
|
||||
for version in ["v2", "v1"]:
|
||||
path = LOCAL_MODELS_DIR / f"{name}_{version}.py"
|
||||
if path.exists():
|
||||
print(f" (found versioned: {name}_{version}.py)")
|
||||
return path
|
||||
|
||||
# Try exact name
|
||||
path = LOCAL_MODELS_DIR / f"{name}.py"
|
||||
if path.exists():
|
||||
return path
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def get_standard_model_path(name: str) -> Optional[Path]:
|
||||
"""Find standard model file by name."""
|
||||
if not STANDARD_MODELS_DIR.exists():
|
||||
return None
|
||||
|
||||
path = STANDARD_MODELS_DIR / f"{name}.py"
|
||||
if path.exists():
|
||||
return path
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def load_module_from_path(path: Path, module_name: str) -> Any:
|
||||
"""Load Python module from file path."""
|
||||
spec = importlib.util.spec_from_file_location(module_name, path)
|
||||
if spec is None or spec.loader is None:
|
||||
raise ImportError(f"Cannot load module from {path}")
|
||||
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
sys.modules[module_name] = module
|
||||
spec.loader.exec_module(module)
|
||||
|
||||
return module
|
||||
|
||||
|
||||
def load_model(name: str, local_only: bool = False, fallback_to_standard: bool = True):
|
||||
"""
|
||||
Load a model by name.
|
||||
|
||||
Priority:
|
||||
1. models/local/{name}.py (if exists)
|
||||
2. models/standard/{name}.py (if fallback_to_standard=True)
|
||||
|
||||
Args:
|
||||
name: Model name (e.g., "xgboost_factor", "transformer_factor")
|
||||
local_only: Only load from local/, raise error if not found
|
||||
fallback_to_standard: If True, fall back to standard models
|
||||
|
||||
Returns:
|
||||
Model class or instance
|
||||
|
||||
Raises:
|
||||
FileNotFoundError: If model not found
|
||||
ImportError: If model cannot be loaded
|
||||
"""
|
||||
# Try local models first
|
||||
local_path = get_local_model_path(name)
|
||||
|
||||
if local_path:
|
||||
print(f"✓ Loading model '{name}' from local: {local_path}")
|
||||
module = load_module_from_path(local_path, f"local_{name}")
|
||||
|
||||
# Try to find create_* or Model class
|
||||
for attr_name in dir(module):
|
||||
if attr_name.startswith('create_') and name.replace('_', '') in attr_name.replace('create_', ''):
|
||||
return getattr(module, attr_name)
|
||||
if attr_name.endswith('Model') and name.replace('_', '') in attr_name.lower():
|
||||
return getattr(module, attr_name)
|
||||
|
||||
# Return module if no specific class found
|
||||
return module
|
||||
|
||||
# Local not found
|
||||
if local_only:
|
||||
raise FileNotFoundError(f"Local model '{name}' not found in {LOCAL_MODELS_DIR}")
|
||||
|
||||
# Try standard models
|
||||
if not fallback_to_standard:
|
||||
raise FileNotFoundError(f"Model '{name}' not found")
|
||||
|
||||
standard_path = get_standard_model_path(name)
|
||||
if not standard_path:
|
||||
raise FileNotFoundError(f"Model '{name}' not found in standard or local directories")
|
||||
|
||||
print(f"✓ Loading model '{name}' from standard: {standard_path}")
|
||||
module = load_module_from_path(standard_path, f"standard_{name}")
|
||||
|
||||
# Try to find create_* or Model class
|
||||
for attr_name in dir(module):
|
||||
if attr_name.startswith('create_') and name.replace('_', '') in attr_name.replace('create_', ''):
|
||||
return getattr(module, attr_name)
|
||||
if attr_name.endswith('Model') and name.replace('_', '') in attr_name.lower():
|
||||
return getattr(module, attr_name)
|
||||
|
||||
return module
|
||||
|
||||
|
||||
def list_available_models() -> dict:
|
||||
"""List all available models."""
|
||||
result = {"standard": [], "local": []}
|
||||
|
||||
# Standard models
|
||||
if STANDARD_MODELS_DIR.exists():
|
||||
result["standard"] = [p.stem for p in STANDARD_MODELS_DIR.glob("*.py") if not p.name.startswith('_')]
|
||||
|
||||
# Local models
|
||||
if LOCAL_MODELS_DIR.exists():
|
||||
result["local"] = [p.stem for p in LOCAL_MODELS_DIR.glob("*.py") if not p.name.startswith('_')]
|
||||
|
||||
return result
|
||||
|
||||
|
||||
# Convenience functions for specific models
|
||||
def get_xgboost_model(**params):
|
||||
"""Get XGBoost model."""
|
||||
return load_model("xgboost_factor")(**params)
|
||||
|
||||
|
||||
def get_lightgbm_model(**params):
|
||||
"""Get LightGBM model."""
|
||||
return load_model("lightgbm_factor")(**params)
|
||||
|
||||
|
||||
def get_randomforest_model(**params):
|
||||
"""Get RandomForest model."""
|
||||
return load_model("randomforest_factor")(**params)
|
||||
|
||||
|
||||
# Test function
|
||||
if __name__ == "__main__":
|
||||
print("=== Available Models ===")
|
||||
available = list_available_models()
|
||||
print(f"Standard: {available['standard']}")
|
||||
print(f"Local: {available['local']}")
|
||||
|
||||
print("\n=== Testing Model Load ===")
|
||||
try:
|
||||
# Test XGBoost
|
||||
xgb_factory = load_model("xgboost_factor")
|
||||
print(f"✓ Loaded xgboost_factor")
|
||||
|
||||
# Test LightGBM
|
||||
lgb_factory = load_model("lightgbm_factor")
|
||||
print(f"✓ Loaded lightgbm_factor")
|
||||
|
||||
except Exception as e:
|
||||
print(f"✗ Error: {e}")
|
||||
Reference in New Issue
Block a user