Spaces:
Running
Running
| import json | |
| from scripts.render_factor_weight_grid_report import generate_html_report, render_report | |
| def _grid_payload(): | |
| candidate = { | |
| "config": {"buy_bull_weight": 0.03, "buy_bear_weight": -0.01}, | |
| "passed": True, | |
| "gate": { | |
| "checks": { | |
| "accuracy_delta": True, | |
| "buy_precision_delta": True, | |
| "signal_ratio": True, | |
| "buy_count_ratio": True, | |
| }, | |
| "signal_ratio": 1.04, | |
| "buy_count_ratio": 1.25, | |
| }, | |
| "validation": { | |
| "metrics": { | |
| "accuracy": 41.16, | |
| "direction_accuracy": 46.27, | |
| "buy_precision": 50.46, | |
| "sell_precision": 39.83, | |
| "signal_count": 2671, | |
| "coverage": 0.7949, | |
| }, | |
| "deltas": { | |
| "accuracy_delta_pp": 2.83, | |
| "direction_accuracy_delta_pp": 2.09, | |
| "buy_precision_delta_pp": 1.24, | |
| "sell_precision_delta_pp": 0.79, | |
| "signal_count_delta": 111, | |
| "coverage_delta": 0.033, | |
| }, | |
| }, | |
| } | |
| return { | |
| "experiment": "factor_weight_grid_search", | |
| "generated_at": "2026-05-18T00:00:00+00:00", | |
| "stocks": ["2330", "0050"], | |
| "factors": {"bull_column": "oldwang_bull_score", "bear_column": "oldwang_bear_score"}, | |
| "grid_preset": "wide", | |
| "result": { | |
| "split": { | |
| "tune_events": 10, | |
| "validation_events": 8, | |
| "tune_date_range": ["2025-01-01", "2025-06-01"], | |
| "validation_date_range": ["2025-06-02", "2025-12-31"], | |
| }, | |
| "grid_size": 63504, | |
| "thresholds": { | |
| "min_accuracy_delta_pp": 2.0, | |
| "min_buy_precision_delta_pp": 0.0, | |
| "min_signal_ratio": 0.7, | |
| "max_buy_count_ratio": 1.3, | |
| }, | |
| "baseline": { | |
| "validation": { | |
| "accuracy": 38.33, | |
| "direction_accuracy": 44.18, | |
| "buy_precision": 49.23, | |
| "sell_precision": 39.04, | |
| "signal_count": 2560, | |
| "coverage": 0.7619, | |
| } | |
| }, | |
| "passing_count": 139, | |
| "best_passing": candidate, | |
| "top_passing": [candidate], | |
| "top_overall": [candidate], | |
| }, | |
| } | |
| def test_generate_html_report_contains_validation_details(): | |
| html = generate_html_report( | |
| _grid_payload(), | |
| popular_payload={ | |
| "source": "yahoo_rank", | |
| "count": 2, | |
| "fallback_used": True, | |
| "stocks": [{"rank": 1, "code": "2330", "name": "台積電", "source": "yahoo_rank"}], | |
| }, | |
| raw_json_path="docs/result.json", | |
| command="venv/bin/python scripts/search_factor_weight_grid.py ...", | |
| ) | |
| assert "Factor Weight Grid Search Report" in html | |
| assert "Grid Size" in html | |
| assert "63504" in html | |
| assert "oldwang_bull_score" in html | |
| assert "buy_bull_weight" in html | |
| assert "台積電" in html | |
| assert "venv/bin/python" in html | |
| def test_render_report_writes_html(tmp_path): | |
| input_json = tmp_path / "grid.json" | |
| popular_json = tmp_path / "popular.json" | |
| output_html = tmp_path / "report.html" | |
| input_json.write_text(json.dumps(_grid_payload(), ensure_ascii=False)) | |
| popular_json.write_text( | |
| json.dumps({"source": "fixture", "stocks": [{"code": "2330", "rank": 1}]}, ensure_ascii=False) | |
| ) | |
| output = render_report(input_json=input_json, popular_json=popular_json, output_html=output_html) | |
| assert output.exists() | |
| assert "Top Passing Candidates" in output.read_text() | |
| def test_generate_html_report_includes_external_factor_metadata(): | |
| payload = _grid_payload() | |
| payload["experiment"] = "external_factor_weight_grid_search" | |
| payload["external_factors"] = { | |
| "csv_paths": ["data/external/chipk_scores.csv", "data/external/crowd_scores.csv"], | |
| "stock_column": "stock", | |
| "date_column": "date", | |
| "bull_column": "bull_score", | |
| "bear_column": "bear_score", | |
| "combine": "sum", | |
| "merge_summary": {"event_count": 100, "matched_event_count": 80, "coverage": 0.8}, | |
| "promotion_note": "validation-only", | |
| } | |
| html = generate_html_report(payload) | |
| assert "External Factor Source" in html | |
| assert "chipk_scores.csv" in html | |
| assert "80 / 100" in html | |
| assert "validation-only" in html | |