Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 0 additions & 7 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -295,13 +295,6 @@ source = ["src/ml4t/engineer"]
omit = [
"*/tests/*",
"*/__init__.py",
"*/bars/base.py",
"*/bars/imbalance.py",
"*/bars/run.py",
"*/bars/vectorized.py",
"*/labeling/barriers.py",
"*/labeling/core.py",
"*/features/microstructure/*",
]

[tool.coverage.report]
Expand Down
6 changes: 2 additions & 4 deletions src/ml4t/engineer/AGENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,9 @@ Package-level navigation for feature engineering library.
| `preprocessing_config.py` | Scaler config | `PreprocessingConfig`, `ScalerConfig` |
| `base.py` | Base config classes | `BaseConfig` |

## selection/ - Feature Selection
## selection/ - Deprecated (Moved to ml4t-diagnostic)

| File | Purpose |
|------|---------|
| `systematic.py` | Correlation-based, variance threshold, mutual info |
FeatureSelector has moved to `ml4t.diagnostic.selection`.

## store/ - Storage

Expand Down
35 changes: 11 additions & 24 deletions src/ml4t/engineer/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,20 +78,6 @@
}
)

# Common default values for required parameters missing from metadata
# Used as fallback when feature registration is incomplete
COMMON_PARAM_DEFAULTS: dict[str, Any] = {
"period": 14,
"window": 20,
"lookback": 20,
"lag": 1,
"lags": [1],
"n": 5,
"bins": 10,
"features": ["close"],
"windows": [5, 10, 20],
}


def compute_features(
data: pl.DataFrame | pl.LazyFrame,
Expand Down Expand Up @@ -175,6 +161,11 @@ def compute_features(
- Circular dependencies are detected and raise ValueError
- Parameters in config override default parameters from registry
"""
from ml4t.engineer.core.schemas import validate_ohlcv_schema

# Validate input schema (flexible: no asset_id required, flexible time column)
validate_ohlcv_schema(data, require_asset_id=False, allow_flexible_time=True)

# Parse input to standardized format
feature_specs = _parse_feature_input(features)

Expand Down Expand Up @@ -403,16 +394,12 @@ def _execute_feature(
pass
else:
# Required parameter with no default and not in COLUMN_ARG_MAP
# This indicates incomplete metadata - try common defaults
if param_name in COMMON_PARAM_DEFAULTS:
keyword_params[param_name] = COMMON_PARAM_DEFAULTS[param_name]
else:
# Cannot proceed - need user to provide this parameter explicitly
raise ValueError(
f"Feature '{feature_name}' requires parameter '{param_name}' but it's not "
f"provided. Call with explicit parameters: "
f'compute_features(df, [{{"name": "{feature_name}", "{param_name}": value}}])'
)
raise ValueError(
f"Feature '{feature_name}' requires parameter '{param_name}' but it's not "
f"provided in metadata.parameters or call params. "
f'Use: compute_features(df, [{{"name": "{feature_name}", '
f'"params": {{"{param_name}": value}}}}])'
)

# Call the feature function
try:
Expand Down
1 change: 0 additions & 1 deletion src/ml4t/engineer/bars/run.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
# mypy: disable-error-code="misc,operator,assignment,arg-type"
"""Run bar sampler implementations.

AFML-compliant run bars per López de Prado Chapter 2.3.
Expand Down
1 change: 0 additions & 1 deletion src/ml4t/engineer/bars/vectorized.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
# mypy: disable-error-code="misc,operator,assignment,arg-type"
"""
Vectorized bar samplers using Polars for high performance.

Expand Down
1 change: 0 additions & 1 deletion src/ml4t/engineer/config/base.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
# mypy: disable-error-code="misc,no-any-return,type-arg"
# ruff: noqa: E721
"""Base configuration classes and shared utilities.

Expand Down
13 changes: 12 additions & 1 deletion src/ml4t/engineer/config/experiment.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
# mypy: disable-error-code="misc,no-any-return"
"""Experiment configuration loading utilities.

This module provides helpers for loading complete experiment configurations
Expand Down Expand Up @@ -209,10 +208,22 @@ def save_experiment_config(
exclude_none=True,
)

# Convert tuples to lists for YAML safe_load compatibility
output = _tuples_to_lists(output)

with open(path, "w") as f:
yaml.dump(output, f, default_flow_style=False, sort_keys=False)


def _tuples_to_lists(obj: Any) -> Any:
"""Recursively convert tuples to lists for YAML-safe serialization."""
if isinstance(obj, dict):
return {k: _tuples_to_lists(v) for k, v in obj.items()}
elif isinstance(obj, tuple | list):
return [_tuples_to_lists(item) for item in obj]
return obj


__all__ = [
"ExperimentConfig",
"load_experiment_config",
Expand Down
1 change: 0 additions & 1 deletion src/ml4t/engineer/config/feature_config.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
# mypy: disable-error-code="misc,call-arg,arg-type"
# ruff: noqa: UP006, UP045
"""Feature evaluation configuration (Modules A, B, C).

Expand Down
1 change: 0 additions & 1 deletion src/ml4t/engineer/config/labeling.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
# mypy: disable-error-code="misc,no-any-return,arg-type"
"""Labeling configuration with Pydantic v2 serialization.

This module provides config classes for ML labeling methods that extend BaseConfig,
Expand Down
4 changes: 3 additions & 1 deletion src/ml4t/engineer/config/preprocessing_config.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
# mypy: disable-error-code="misc,no-any-return,call-arg"
"""Preprocessing configuration with Pydantic v2 serialization.

This module provides config classes for preprocessing (scalers) that extend BaseConfig,
Expand Down Expand Up @@ -289,15 +288,18 @@ def create_scaler(self) -> BaseScaler | None:

if self.scaler == "standard":
return StandardScaler(
columns=self.columns,
with_mean=self.with_mean,
with_std=self.with_std,
)
elif self.scaler == "minmax":
return MinMaxScaler(
columns=self.columns,
feature_range=self.feature_range,
)
elif self.scaler == "robust":
return RobustScaler(
columns=self.columns,
with_centering=self.with_centering,
with_scaling=self.with_scaling,
quantile_range=self.quantile_range,
Expand Down
14 changes: 2 additions & 12 deletions src/ml4t/engineer/core/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,11 @@
DataError,
DataSchemaError,
DataValidationError,
ImplementationNotAvailableError,
IndicatorError, # Deprecated
InsufficientDataError,
IntegrationError,
InvalidArgumentError,
InvalidParameterError,
# Base exception
QuantLabTAError,
# Backward compatibility aliases
TechnicalAnalysisError,
ML4TEngineerError,
ValidationError,
)
from ml4t.engineer.core.registry import (
Expand Down Expand Up @@ -96,7 +91,7 @@
"FeatureRegistry",
"get_registry",
# Exceptions - Base
"QuantLabTAError",
"ML4TEngineerError",
# Exceptions - First-level (flat hierarchy)
"ConfigurationError",
"ValidationError",
Expand All @@ -107,11 +102,6 @@
"ComputationError",
"DataError",
"IntegrationError",
# Exceptions - Backward compatibility aliases
"TechnicalAnalysisError",
"IndicatorError", # Deprecated
"InvalidArgumentError",
"ImplementationNotAvailableError",
# Validation
"validate_lag",
"validate_list_length",
Expand Down
1 change: 0 additions & 1 deletion src/ml4t/engineer/core/decorators.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
# mypy: disable-error-code="misc,arg-type,assignment"
"""Feature registration decorators.

Simple decorator-based registration for features with zero overhead.
Expand Down
71 changes: 14 additions & 57 deletions src/ml4t/engineer/core/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
provide actionable error messages.

Exception Hierarchy (Flat - D06 Pattern):
QuantLabTAError (base)
ML4TEngineerError (base)
├── ConfigurationError # Configuration and setup errors
├── ValidationError # Input validation failures (also inherits ValueError)
├── InvalidParameterError # Invalid parameters to indicators
Expand All @@ -27,11 +27,10 @@

from __future__ import annotations

import warnings
from typing import Any


class QuantLabTAError(Exception):
class ML4TEngineerError(Exception):
"""
Base exception class for all ML4T Engineer errors.

Expand All @@ -44,7 +43,7 @@ class QuantLabTAError(Exception):
cause: Original exception if error was wrapped

Example:
>>> raise QuantLabTAError(
>>> raise ML4TEngineerError(
... "Operation failed",
... context={"operation": "compute_rsi", "reason": "insufficient_data"}
... )
Expand Down Expand Up @@ -98,7 +97,7 @@ def __repr__(self) -> str:
# =============================================================================


class ConfigurationError(QuantLabTAError):
class ConfigurationError(ML4TEngineerError):
"""
Configuration and setup errors.

Expand All @@ -119,11 +118,11 @@ class ConfigurationError(QuantLabTAError):
pass


class ValidationError(QuantLabTAError, ValueError):
class ValidationError(ML4TEngineerError, ValueError):
"""
Input validation failures.

Inherits from both QuantLabTAError (for library-specific catching)
Inherits from both ML4TEngineerError (for library-specific catching)
and ValueError (for standard Python parameter error handling).

Raised when:
Expand All @@ -143,7 +142,7 @@ class ValidationError(QuantLabTAError, ValueError):
pass


class InvalidParameterError(QuantLabTAError, ValueError):
class InvalidParameterError(ML4TEngineerError, ValueError):
"""
Invalid parameters provided to indicators.

Expand All @@ -167,7 +166,7 @@ class InvalidParameterError(QuantLabTAError, ValueError):
pass


class DataValidationError(QuantLabTAError):
class DataValidationError(ML4TEngineerError):
"""
Data validation failures.

Expand All @@ -188,7 +187,7 @@ class DataValidationError(QuantLabTAError):
pass


class DataSchemaError(QuantLabTAError):
class DataSchemaError(ML4TEngineerError):
"""
Schema validation failures.

Expand All @@ -209,7 +208,7 @@ class DataSchemaError(QuantLabTAError):
pass


class InsufficientDataError(QuantLabTAError):
class InsufficientDataError(ML4TEngineerError):
"""
Insufficient data for calculation.

Expand All @@ -229,7 +228,7 @@ class InsufficientDataError(QuantLabTAError):
pass


class ComputationError(QuantLabTAError):
class ComputationError(ML4TEngineerError):
"""
Calculation and numerical errors.

Expand All @@ -250,7 +249,7 @@ class ComputationError(QuantLabTAError):
pass


class DataError(QuantLabTAError):
class DataError(ML4TEngineerError):
"""
Data access and format errors.

Expand All @@ -271,7 +270,7 @@ class DataError(QuantLabTAError):
pass


class IntegrationError(QuantLabTAError):
class IntegrationError(ML4TEngineerError):
"""
External library integration errors.

Expand All @@ -292,50 +291,13 @@ class IntegrationError(QuantLabTAError):
pass


# =============================================================================
# Backward Compatibility Aliases
# =============================================================================


# Primary alias for historical naming
TechnicalAnalysisError = QuantLabTAError


class _DeprecatedIndicatorError(QuantLabTAError):
"""Deprecated intermediate class - use QuantLabTAError or specific errors."""

def __init__(
self,
message: str,
context: dict[str, Any] | None = None,
cause: Exception | None = None,
):
warnings.warn(
"IndicatorError is deprecated. Use QuantLabTAError or a specific "
"exception type (InsufficientDataError, ComputationError) instead.",
DeprecationWarning,
stacklevel=2,
)
super().__init__(message, context, cause)


# IndicatorError was an intermediate class - deprecated but kept for backward compat
IndicatorError = _DeprecatedIndicatorError

# InvalidArgumentError is an alias for InvalidParameterError
InvalidArgumentError = InvalidParameterError

# ImplementationNotAvailableError maps to IntegrationError
ImplementationNotAvailableError = IntegrationError


# =============================================================================
# Public API
# =============================================================================

__all__ = [
# Base exception
"QuantLabTAError",
"ML4TEngineerError",
# First-level exceptions (flat hierarchy)
"ConfigurationError",
"ValidationError",
Expand All @@ -346,9 +308,4 @@ def __init__(
"ComputationError",
"DataError",
"IntegrationError",
# Backward compatibility aliases
"TechnicalAnalysisError",
"IndicatorError", # Deprecated
"InvalidArgumentError",
"ImplementationNotAvailableError",
]
2 changes: 1 addition & 1 deletion src/ml4t/engineer/core/registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ class FeatureMetadata:
"""

name: str
func: Callable[..., pl.DataFrame | pl.LazyFrame]
func: Callable[..., pl.Expr | dict[str, pl.Expr] | pl.DataFrame | pl.LazyFrame]
category: str
description: str
formula: str = ""
Expand Down
Loading