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
5 changes: 3 additions & 2 deletions src/ml4t/engineer/config/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,9 +190,10 @@ def validate_fully(self) -> list[str]:
return []
except Exception as e:
# Parse validation errors from Pydantic
if hasattr(e, "errors") and callable(getattr(e, "errors", None)):
errors_method = getattr(e, "errors", None)
if callable(errors_method):
errors = []
for error in e.errors(): # type: ignore[operator]
for error in errors_method():
loc = ".".join(str(x) for x in error["loc"])
msg = error["msg"]
errors.append(f"{loc}: {msg}")
Expand Down
7 changes: 4 additions & 3 deletions src/ml4t/engineer/dataset.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@

from collections.abc import Generator, Iterator
from dataclasses import dataclass, field
from typing import TYPE_CHECKING, Any, Protocol
from typing import TYPE_CHECKING, Any, Protocol, cast

import numpy as np
import polars as pl
Expand Down Expand Up @@ -274,8 +274,9 @@ def set_scaler(self, scaler: BaseScaler | PreprocessingConfig | None) -> MLDatas
>>> builder.set_scaler(PreprocessingConfig.robust())
"""
# Handle PreprocessingConfig by creating the scaler
if hasattr(scaler, "create_scaler"):
self._scaler = scaler.create_scaler() # type: ignore[union-attr]
create_scaler = getattr(scaler, "create_scaler", None)
if callable(create_scaler):
self._scaler = cast("BaseScaler | None", create_scaler())
else:
self._scaler = scaler
return self
Expand Down
8 changes: 5 additions & 3 deletions src/ml4t/engineer/features/ml/percentile_rank_features.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,11 @@ def percentile_rank_features(
# Count how many close in the window are less than or equal to current value
# This properly calculates percentile rank within each rolling window
rank = feature.rolling_map(
lambda x: ((x[-1] > x[:-1]).sum() + 0.5 * (x[-1] == x[:-1]).sum()) / len(x) * 100
if len(x) > 0
else None,
lambda x: (
((x[-1] > x[:-1]).sum() + 0.5 * (x[-1] == x[:-1]).sum()) / len(x) * 100
if len(x) > 0
else None
),
window_size=window,
)
ranks[f"rank_{window}"] = rank
Expand Down
18 changes: 8 additions & 10 deletions src/ml4t/engineer/features/utils/ma_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@
Maps TA-Lib MA type codes to ml4t.engineer implementations.
"""

from typing import cast

import numpy as np
import numpy.typing as npt

Expand Down Expand Up @@ -64,27 +62,27 @@ def apply_ma(
beyond period. Use KAMA (matype=6) as an adaptive alternative.
"""
if matype == 0:
return cast(NDArrayFloat, sma_numba(close, period))
return sma_numba(close, period)
elif matype == 1:
return cast(NDArrayFloat, ema_numba(close, period))
return ema_numba(close, period)
elif matype == 2:
return cast(NDArrayFloat, wma_numba(close, period))
return wma_numba(close, period)
elif matype == 3:
return cast(NDArrayFloat, dema_numba(close, period))
return dema_numba(close, period)
elif matype == 4:
return cast(NDArrayFloat, tema_numba(close, period))
return tema_numba(close, period)
elif matype == 5:
return cast(NDArrayFloat, trima_numba(close, period))
return trima_numba(close, period)
elif matype == 6:
return cast(NDArrayFloat, kama_numba(close, timeperiod=period))
return kama_numba(close, timeperiod=period)
elif matype == 7:
raise ValueError(
"MAMA (matype=7) is not supported. "
"MAMA requires additional parameters (fastlimit, slowlimit). "
"Use KAMA (matype=6) as an adaptive alternative."
)
elif matype == 8:
return cast(NDArrayFloat, t3_numba(close, period))
return t3_numba(close, period)
else:
raise ValueError(
f"Invalid matype={matype}. Must be 0-8 (excluding 7). See docstring for valid MA types."
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,11 @@ def volatility_percentile_rank(
# Calculate rolling percentile rank
# Use rolling_map to properly calculate percentile rank within each lookback window
rank = vol.rolling_map(
lambda x: ((x[-1] > x[:-1]).sum() + 0.5 * (x[-1] == x[:-1]).sum()) / len(x) * 100
if len(x) > 0
else None,
lambda x: (
((x[-1] > x[:-1]).sum() + 0.5 * (x[-1] == x[:-1]).sum()) / len(x) * 100
if len(x) > 0
else None
),
window_size=lookback,
)

Expand Down
6 changes: 3 additions & 3 deletions src/ml4t/engineer/preprocessing.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,13 @@
from __future__ import annotations

from abc import ABC, abstractmethod
from enum import Enum
from enum import StrEnum
from typing import Any

import polars as pl


class ScalerMethod(str, Enum):
class ScalerMethod(StrEnum):
"""Scaling method options."""

STANDARD = "standard" # Z-score: (x - mean) / std
Expand Down Expand Up @@ -489,7 +489,7 @@ def _apply_transform(self, X: pl.DataFrame) -> pl.DataFrame:
# =============================================================================


class TransformType(str, Enum):
class TransformType(StrEnum):
"""Transform types supported by PreprocessingPipeline.

These align with ml4t.diagnostic.integration.engineer_contract.TransformType.
Expand Down
Loading