Raise coverage threshold to 95% and expand test coverage
- pyproject.toml: fail_under 80 → 95 - test_alpha: +79 lines - test_cli_workflow: +226 lines - test_derived: +121 lines - test_downloader_contracts: +169 lines - test_features: +16 lines - test_minute_downloader: +81 lines - test_portfolio: +208 lines
This commit is contained in:
@@ -5,10 +5,13 @@ from __future__ import annotations
|
||||
import textwrap
|
||||
from pathlib import Path
|
||||
|
||||
import click
|
||||
import pandas as pd
|
||||
from click.testing import CliRunner
|
||||
|
||||
from cli import cli
|
||||
import pipeline.derived.cli as derived_cli
|
||||
import pipeline.features.cli as features_cli
|
||||
from tests.helpers import (
|
||||
make_generated_daily_bars,
|
||||
make_generated_derived_features,
|
||||
@@ -426,6 +429,61 @@ def test_cli_error_paths_are_clear_for_bad_user_inputs(tmp_path):
|
||||
])
|
||||
assert "Symbol 'sh999999' not found" in alphaview_missing_symbol.output
|
||||
|
||||
alphaview_missing_column = _invoke_error(runner, [
|
||||
"alphaview",
|
||||
"--data-path", str(daily_path),
|
||||
"--alpha-path", str(positions_path),
|
||||
"--symbol", "sh600000",
|
||||
"--columns", "close,missing_bar_col",
|
||||
])
|
||||
assert "Bar columns not found: missing_bar_col" in alphaview_missing_column.output
|
||||
|
||||
alphaview_alpha = pd.DataFrame({
|
||||
"symbol_id": ["sh600000"],
|
||||
"date": [daily_bars["date"].min()],
|
||||
"alpha_name": ["toy_alpha"],
|
||||
"weight": [1.0],
|
||||
})
|
||||
alphaview_alpha_path = tmp_path / "alphaview_alpha.pq"
|
||||
alphaview_alpha.to_parquet(alphaview_alpha_path, index=False)
|
||||
alphaview_empty_range = _invoke_error(runner, [
|
||||
"alphaview",
|
||||
"--data-path", str(daily_path),
|
||||
"--alpha-path", str(alphaview_alpha_path),
|
||||
"--symbol", "sh600000",
|
||||
"--start-date", "2030-01-01",
|
||||
])
|
||||
assert "No rows in the requested date range" in alphaview_empty_range.output
|
||||
|
||||
empty_combo_paths = _invoke_ok(runner, [
|
||||
"combo", "combine",
|
||||
"--alpha-paths", " , ",
|
||||
"--combo-name", "empty",
|
||||
"--output-dir", str(tmp_path / "combos"),
|
||||
])
|
||||
assert "requires at least 1 path" in empty_combo_paths.output
|
||||
|
||||
|
||||
def test_cli_parser_helpers_cover_string_coercion_and_bad_params():
|
||||
assert derived_cli._parse_params(("n=7", "scale=2.5", "label=demo")) == {
|
||||
"n": 7,
|
||||
"scale": 2.5,
|
||||
"label": "demo",
|
||||
}
|
||||
assert features_cli._parse_params(("n=7", "scale=2.5", "label=demo")) == {
|
||||
"n": 7,
|
||||
"scale": 2.5,
|
||||
"label": "demo",
|
||||
}
|
||||
|
||||
for module in (derived_cli, features_cli):
|
||||
try:
|
||||
module._parse_params(("not-an-assignment",))
|
||||
except click.BadParameter as exc:
|
||||
assert "--param must be name=value" in str(exc)
|
||||
else:
|
||||
raise AssertionError("expected BadParameter")
|
||||
|
||||
|
||||
def test_cli_list_and_legacy_feature_paths(tmp_path):
|
||||
runner = CliRunner()
|
||||
@@ -519,6 +577,174 @@ def test_cli_list_and_legacy_feature_paths(tmp_path):
|
||||
assert "Unknown feature-type" in unknown_feature.output
|
||||
|
||||
|
||||
def test_cli_shortcuts_and_external_module_loading(tmp_path):
|
||||
runner = CliRunner()
|
||||
daily_bars = make_generated_daily_bars(n_sessions=8, include_missing=False)
|
||||
minute_bars = make_generated_minute_bars(daily_bars)
|
||||
daily_path = tmp_path / "daily_bars.pq"
|
||||
minute_path = tmp_path / "minute_bars.pq"
|
||||
daily_bars.to_parquet(daily_path, index=False)
|
||||
minute_bars.to_parquet(minute_path, index=False)
|
||||
|
||||
alpha_module = tmp_path / "listed_alpha.py"
|
||||
alpha_module.write_text(textwrap.dedent("""
|
||||
import pandas as pd
|
||||
from pipeline.alpha.base import BaseAlpha
|
||||
from pipeline.alpha.registry import register_alpha
|
||||
|
||||
@register_alpha
|
||||
class ListedAlpha(BaseAlpha):
|
||||
name = "listed_alpha_cli"
|
||||
|
||||
def __init__(self, scale: float = 1.0, label: str = "x"):
|
||||
self.scale = scale
|
||||
self.label = label
|
||||
|
||||
def signal(self, close: pd.DataFrame) -> pd.DataFrame:
|
||||
return close.pct_change(1, fill_method=None) * self.scale
|
||||
"""))
|
||||
derived_module = tmp_path / "listed_derived.py"
|
||||
derived_module.write_text(textwrap.dedent("""
|
||||
import pandas as pd
|
||||
from pipeline.derived.base import BaseDerivedData
|
||||
from pipeline.derived.registry import register_derived
|
||||
|
||||
@register_derived
|
||||
class ListedDerived(BaseDerivedData):
|
||||
name = "listed_derived_cli"
|
||||
|
||||
def __init__(self, scale: float = 1.0):
|
||||
self.scale = scale
|
||||
|
||||
def compute(self, daily=None, minute=None) -> pd.DataFrame:
|
||||
out = daily[["symbol_id", "date", "close"]].copy()
|
||||
out["listed_value"] = out.pop("close") * self.scale
|
||||
return out
|
||||
"""))
|
||||
derived_compute_module = tmp_path / "computed_derived.py"
|
||||
derived_compute_module.write_text(textwrap.dedent("""
|
||||
import pandas as pd
|
||||
from pipeline.derived.base import BaseDerivedData
|
||||
from pipeline.derived.registry import register_derived
|
||||
|
||||
@register_derived
|
||||
class ComputedDerived(BaseDerivedData):
|
||||
name = "computed_derived_cli"
|
||||
|
||||
def __init__(self, scale: float = 1.0):
|
||||
self.scale = scale
|
||||
|
||||
def compute(self, daily=None, minute=None) -> pd.DataFrame:
|
||||
out = daily[["symbol_id", "date", "close"]].copy()
|
||||
out["computed_value"] = out.pop("close") * self.scale
|
||||
return out
|
||||
"""))
|
||||
feature_module = tmp_path / "listed_feature.py"
|
||||
feature_module.write_text(textwrap.dedent("""
|
||||
import pandas as pd
|
||||
from pipeline.features.base import BaseFeature
|
||||
from pipeline.features.registry import register_feature
|
||||
|
||||
@register_feature
|
||||
class ListedFeature(BaseFeature):
|
||||
name = "listed_feature_cli"
|
||||
|
||||
def compute(self, daily=None, minute=None) -> pd.DataFrame:
|
||||
out = minute[["symbol_id", "date", "close"]].copy()
|
||||
out["date"] = pd.to_datetime(out["date"]).dt.normalize()
|
||||
out = out.groupby(["symbol_id", "date"], as_index=False)["close"].mean()
|
||||
return out.rename(columns={"close": "listed_feature_value"})
|
||||
"""))
|
||||
feature_compute_module = tmp_path / "computed_feature.py"
|
||||
feature_compute_module.write_text(textwrap.dedent("""
|
||||
import pandas as pd
|
||||
from pipeline.features.base import BaseFeature
|
||||
from pipeline.features.registry import register_feature
|
||||
|
||||
@register_feature
|
||||
class ComputedFeature(BaseFeature):
|
||||
name = "computed_feature_cli"
|
||||
|
||||
def compute(self, daily=None, minute=None) -> pd.DataFrame:
|
||||
out = minute[["symbol_id", "date", "close"]].copy()
|
||||
out["date"] = pd.to_datetime(out["date"]).dt.normalize()
|
||||
out = out.groupby(["symbol_id", "date"], as_index=False)["close"].mean()
|
||||
return out.rename(columns={"close": "computed_feature_value"})
|
||||
"""))
|
||||
|
||||
alpha_list = _invoke_ok(runner, [
|
||||
"alpha", "list",
|
||||
"--alpha-module", str(alpha_module),
|
||||
])
|
||||
assert "listed_alpha_cli" in alpha_list.output
|
||||
|
||||
alpha_dir = tmp_path / "alphas"
|
||||
reversal = _invoke_ok(runner, [
|
||||
"alpha", "reversal",
|
||||
"--data-path", str(daily_path),
|
||||
"--output-dir", str(alpha_dir),
|
||||
"--lookback", "3",
|
||||
])
|
||||
reversal_vol = _invoke_ok(runner, [
|
||||
"alpha", "reversal-vol",
|
||||
"--data-path", str(daily_path),
|
||||
"--output-dir", str(alpha_dir),
|
||||
"--lookback", "3",
|
||||
"--vol-window", "3",
|
||||
])
|
||||
external_alpha = _invoke_ok(runner, [
|
||||
"alpha", "compute",
|
||||
"--data-path", str(daily_path),
|
||||
"--alpha-type", "listed_alpha_cli",
|
||||
"--alpha-name", "listed_alpha_run",
|
||||
"--param", "scale=2.5",
|
||||
"--param", "label=demo",
|
||||
"--output-dir", str(alpha_dir),
|
||||
])
|
||||
|
||||
assert "Saved alpha:" in reversal.output
|
||||
assert "Saved alpha:" in reversal_vol.output
|
||||
assert "Saved alpha:" in external_alpha.output
|
||||
assert (alpha_dir / "reversal_3d.pq").exists()
|
||||
assert (alpha_dir / "reversal_vol_3d_3d.pq").exists()
|
||||
assert (alpha_dir / "listed_alpha_run.pq").exists()
|
||||
|
||||
derived_list = _invoke_ok(runner, [
|
||||
"derived", "list",
|
||||
"--derived-module", str(derived_module),
|
||||
])
|
||||
assert "listed_derived_cli" in derived_list.output
|
||||
derived_dir = tmp_path / "derived_external"
|
||||
derived_compute = _invoke_ok(runner, [
|
||||
"derived", "compute",
|
||||
"--daily-path", str(daily_path),
|
||||
"--derived-module", str(derived_compute_module),
|
||||
"--derived-type", "computed_derived_cli",
|
||||
"--derived-name", "listed_derived_run",
|
||||
"--param", "scale=3",
|
||||
"--output-dir", str(derived_dir),
|
||||
])
|
||||
assert "Saved derived data:" in derived_compute.output
|
||||
assert (derived_dir / "listed_derived_run.pq").exists()
|
||||
|
||||
feature_list = _invoke_ok(runner, [
|
||||
"feature", "list",
|
||||
"--feature-module", str(feature_module),
|
||||
])
|
||||
assert "listed_feature_cli" in feature_list.output
|
||||
feature_dir = tmp_path / "features_external"
|
||||
feature_compute = _invoke_ok(runner, [
|
||||
"feature", "compute",
|
||||
"--minute-path", str(minute_path),
|
||||
"--feature-module", str(feature_compute_module),
|
||||
"--feature-type", "computed_feature_cli",
|
||||
"--feature-name", "listed_feature_run",
|
||||
"--output-dir", str(feature_dir),
|
||||
])
|
||||
assert "Saved feature:" in feature_compute.output
|
||||
assert (feature_dir / "listed_feature_run.pq").exists()
|
||||
|
||||
|
||||
def test_cli_pqcat_row_modes(tmp_path):
|
||||
runner = CliRunner()
|
||||
daily_bars = make_generated_daily_bars(n_sessions=3, include_missing=False)
|
||||
|
||||
Reference in New Issue
Block a user