diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 7394a3a..deeaff0 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -18,7 +18,7 @@ updates: patterns: - "*" - - package-ecosystem: "pip" + - package-ecosystem: "uv" directory: "/" schedule: interval: "weekly" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 78f3e22..9d0f630 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,13 +19,13 @@ jobs: - name: Install uv uses: astral-sh/setup-uv@v8.2.0 with: - version: "latest" + version: "0.10.9" - name: Set up Python run: uv python install ${{ env.PYTHON_VERSION }} - name: Install dependencies - run: uv sync --dev + run: uv sync --python ${{ env.PYTHON_VERSION }} --dev - name: Run ruff check run: uv run ruff check src/ tests/ @@ -42,13 +42,13 @@ jobs: - name: Install uv uses: astral-sh/setup-uv@v8.2.0 with: - version: "latest" + version: "0.10.9" - name: Set up Python run: uv python install ${{ env.PYTHON_VERSION }} - name: Install dependencies - run: uv sync --dev + run: uv sync --python ${{ env.PYTHON_VERSION }} --dev - name: Run ty check run: uv run ty check @@ -67,7 +67,7 @@ jobs: - name: Install uv uses: astral-sh/setup-uv@v8.2.0 with: - version: "latest" + version: "0.10.9" - name: Set up Python ${{ matrix.python-version }} run: uv python install ${{ matrix.python-version }} @@ -83,7 +83,7 @@ jobs: sudo ldconfig - name: Install dependencies - run: uv sync --dev --extra ta + run: uv sync --python ${{ matrix.python-version }} --dev --extra ta - name: Run tests run: | @@ -113,7 +113,7 @@ jobs: - name: Install uv uses: astral-sh/setup-uv@v8.2.0 with: - version: "latest" + version: "0.10.9" - name: Set up Python run: uv python install ${{ env.PYTHON_VERSION }} diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 5fd45fe..08e4278 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -24,19 +24,19 @@ jobs: - name: Install uv uses: astral-sh/setup-uv@v8.2.0 with: - version: "latest" + version: "0.10.9" - name: Set up Python run: uv python install 3.12 - name: Install dependencies - run: uv sync --dev --extra docs + run: uv sync --python 3.12 --dev --extra docs - name: Build docs run: uv run mkdocs build --strict - name: Deploy to website repo - uses: cpina/github-action-push-to-another-repository@v1.7.3 + uses: cpina/github-action-push-to-another-repository@55306faa4ed53b815ae49e564af8cfb359d32ae2 # v1.7.3 env: SSH_DEPLOY_KEY: ${{ secrets.DOCS_DEPLOY_KEY }} with: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 04fc5a1..3f9c85e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -19,7 +19,7 @@ jobs: - name: Install uv uses: astral-sh/setup-uv@v8.2.0 with: - version: "latest" + version: "0.10.9" - name: Set up Python run: uv python install ${{ env.PYTHON_VERSION }} @@ -48,7 +48,7 @@ jobs: path: dist/ - name: Publish to PyPI - uses: pypa/gh-action-pypi-publish@v1.14.0 + uses: pypa/gh-action-pypi-publish@6733eb7d741f0b11ec6a39b58540dab7590f9b7d # v1.14.0 github-release: name: Create GitHub Release diff --git a/docs/images/ml4t_ecosystem_workflow_color.svg b/docs/images/ml4t_ecosystem_workflow_color.svg deleted file mode 100644 index 1a25431..0000000 --- a/docs/images/ml4t_ecosystem_workflow_color.svg +++ /dev/null @@ -1,106 +0,0 @@ - - - - - - - - - - - - - - The ML4T Library Ecosystem - - - One workflow for data, features, models, diagnostics, backtests, and deployment - - - - BUILD - - - VALIDATE AND DEPLOY - - - - - - - - - - - - - - - - - - ml4t-data - ml4t-engineer - ml4t-models - - Curated market datasets - Features, labels, targets - Forecasts, signals, weights - - providers • storage • updates - indicators • bars • transforms - latent factors • SDF • portfolios - - ml4t-diagnostic - ml4t-backtest - ml4t-live - - Signal quality and robustness - Execution-aware simulation - Shadow and live deployment - - IC • CPCV • tear sheets - risk rules • fills • artifacts - brokers • controls • monitoring - - - - - - - - - datasets - - - features - - - predictions / weights - - - validated signals - - - run artifacts - - - - iterate and redeploy - - - - - Shared handoffs - - - datasets - - feature matrices and labels - - predictions and weights - - validated signals - - backtest artifacts - diff --git a/src/ml4t/engineer/labeling/horizon_labels.py b/src/ml4t/engineer/labeling/horizon_labels.py index b1cea7f..ba80e66 100644 --- a/src/ml4t/engineer/labeling/horizon_labels.py +++ b/src/ml4t/engineer/labeling/horizon_labels.py @@ -355,14 +355,19 @@ def _trend_scanning_single_group( # Handle numerical issues continue - # Assign label based on trend direction - labels[i] = 1 if best_t > 0 else -1 + # Assign label only when a directional trend was found. + if best_t > 0: + labels[i] = 1 + elif best_t < 0: + labels[i] = -1 + else: + continue t_values[i] = best_t windows[i] = best_window # Add results to dataframe label_series = pl.Series("label", labels).fill_nan(None).cast(pl.Int8) - t_value_series = pl.Series("t_value", t_values) + t_value_series = pl.Series("t_value", t_values).fill_nan(None) window_series = pl.Series("optimal_window", windows).fill_nan(None).cast(pl.Int32) return data.with_columns([label_series, t_value_series, window_series]) diff --git a/tests/features/volatility/test_volatility_comprehensive.py b/tests/features/volatility/test_volatility_comprehensive.py index ef1158e..93b8326 100644 --- a/tests/features/volatility/test_volatility_comprehensive.py +++ b/tests/features/volatility/test_volatility_comprehensive.py @@ -884,7 +884,7 @@ def test_different_periods(self): u10, m10, l10 = bollinger_bands(close, period=10) u50, m50, l50 = bollinger_bands(close, period=50) - # Longer period has fewer NaN at start + # Longer period has more NaN values at the start. assert np.sum(np.isnan(m10)) < np.sum(np.isnan(m50)) # Both should have valid values diff --git a/tests/test_new_labeling_methods.py b/tests/test_new_labeling_methods.py index f6f7b76..8ed8a57 100644 --- a/tests/test_new_labeling_methods.py +++ b/tests/test_new_labeling_methods.py @@ -367,6 +367,21 @@ def test_null_handling_at_end(self, sample_prices): # Should have some nulls at the end assert null_count > 0 + def test_no_directional_trend_returns_null_labels(self): + """Test flat prices do not default to negative labels.""" + flat_prices = pl.DataFrame( + { + "timestamp": [datetime(2024, 1, 1) + timedelta(hours=i) for i in range(20)], + "close": [100.0] * 20, + } + ) + + result = trend_scanning_labels(flat_prices, min_window=5, max_window=10) + + assert result["label"].null_count() == result.height + assert result["t_value"].null_count() == result.height + assert result["optimal_window"].null_count() == result.height + class TestLabelingComparison: """Compare different labeling methods.""" diff --git a/uv.lock b/uv.lock index 583ebba..b04cf12 100644 --- a/uv.lock +++ b/uv.lock @@ -1423,7 +1423,7 @@ requires-dist = [ { name = "mkdocstrings", extras = ["python"], marker = "extra == 'docs'", specifier = ">=0.24.0" }, { name = "ml4t-specs", specifier = ">=0.1.0b0" }, { name = "numba", specifier = ">=0.57.0" }, - { name = "numpy", specifier = ">=1.24.0,<2.5" }, + { name = "numpy", specifier = ">=1.24.0,<2.6" }, { name = "pandas", specifier = ">=2.0.0" }, { name = "pandas-market-calendars", marker = "extra == 'all'", specifier = ">=4.0.0" }, { name = "pandas-market-calendars", marker = "extra == 'calendars'", specifier = ">=4.0.0" },