Spaces:
Sleeping
Sleeping
| import pandas as pd | |
| from scripts.optimize_factor_weights import ( | |
| WeightConfig, | |
| apply_weight_config, | |
| compute_event_metrics, | |
| load_stock_codes_file, | |
| optimize_weights, | |
| pass_decision, | |
| ) | |
| def _events(rows): | |
| return pd.DataFrame(rows) | |
| def test_apply_weight_config_can_flip_to_buy_with_bull_factor(): | |
| events = _events( | |
| [ | |
| { | |
| "stock": "2330", | |
| "date": "2025-04-01", | |
| "y_true": 1, | |
| "base_pred": 0, | |
| "p_buy": 0.42, | |
| "p_hold": 0.46, | |
| "p_sell": 0.12, | |
| "bull_score": 3.0, | |
| "bear_score": 0.0, | |
| } | |
| ] | |
| ) | |
| pred = apply_weight_config(events, WeightConfig(buy_bull_weight=0.02)) | |
| assert pred.tolist() == [1] | |
| def test_apply_weight_config_bear_gate_blocks_risky_buy(): | |
| events = _events( | |
| [ | |
| { | |
| "stock": "2330", | |
| "date": "2025-04-01", | |
| "y_true": 0, | |
| "base_pred": 1, | |
| "p_buy": 0.62, | |
| "p_hold": 0.31, | |
| "p_sell": 0.07, | |
| "bull_score": 0.0, | |
| "bear_score": 2.0, | |
| } | |
| ] | |
| ) | |
| pred = apply_weight_config(events, WeightConfig(buy_bear_gate=2.0)) | |
| assert pred.tolist() == [0] | |
| def test_compute_event_metrics_reports_precision_and_coverage(): | |
| events = _events( | |
| [ | |
| {"y_true": 1}, | |
| {"y_true": 0}, | |
| {"y_true": -1}, | |
| {"y_true": 1}, | |
| ] | |
| ) | |
| metrics = compute_event_metrics(events, pred=pd.Series([1, 0, -1, -1]).to_numpy()) | |
| assert metrics["accuracy"] == 75.0 | |
| assert metrics["direction_accuracy"] == 66.6667 | |
| assert metrics["buy_precision"] == 100.0 | |
| assert metrics["sell_precision"] == 50.0 | |
| assert metrics["coverage"] == 0.75 | |
| def test_optimize_weights_uses_earlier_events_then_validates_later_events(): | |
| rows = [] | |
| for i in range(6): | |
| risky_false_buy = i < 4 | |
| rows.append( | |
| { | |
| "stock": "2330", | |
| "date": f"2025-04-0{i + 1}", | |
| "y_true": 0 if risky_false_buy else 1, | |
| "base_pred": 1, | |
| "p_buy": 0.62 if risky_false_buy else 0.58, | |
| "p_hold": 0.31, | |
| "p_sell": 0.07 if risky_false_buy else 0.11, | |
| "bull_score": 0.0 if risky_false_buy else 3.0, | |
| "bear_score": 2.0 if risky_false_buy else 0.0, | |
| } | |
| ) | |
| for i in range(4): | |
| risky_false_buy = i < 2 | |
| rows.append( | |
| { | |
| "stock": "2330", | |
| "date": f"2025-04-1{i + 1}", | |
| "y_true": 0 if risky_false_buy else 1, | |
| "base_pred": 1, | |
| "p_buy": 0.62 if risky_false_buy else 0.58, | |
| "p_hold": 0.31, | |
| "p_sell": 0.07 if risky_false_buy else 0.11, | |
| "bull_score": 0.0 if risky_false_buy else 3.0, | |
| "bear_score": 2.0 if risky_false_buy else 0.0, | |
| } | |
| ) | |
| result = optimize_weights( | |
| _events(rows), | |
| optimize_ratio=0.6, | |
| min_signal_ratio=0.3, | |
| grid={ | |
| "buy_bear_gate": [None, 2.0], | |
| "buy_bull_weight": [0.0], | |
| "buy_bear_weight": [0.0], | |
| "sell_bear_weight": [0.0], | |
| "sell_bull_weight": [0.0], | |
| "sell_bull_gate": [None], | |
| "hold_bias": [0.0], | |
| }, | |
| ) | |
| assert result["best_config"]["buy_bear_gate"] == 2.0 | |
| assert result["split"]["tune_events"] == 6 | |
| assert result["split"]["validation_events"] == 4 | |
| assert result["validation"]["deltas"]["accuracy_delta_pp"] == 50.0 | |
| assert result["validation"]["deltas"]["buy_precision_delta_pp"] == 50.0 | |
| def test_pass_decision_requires_validation_improvement(): | |
| result = { | |
| "validation": { | |
| "baseline": {"signal_count": 4}, | |
| "candidate": {"signal_count": 4}, | |
| "deltas": {"accuracy_delta_pp": 3.0, "buy_precision_delta_pp": 0.0}, | |
| } | |
| } | |
| decision = pass_decision(result, min_accuracy_delta_pp=3.0, min_signal_ratio=0.6) | |
| assert decision["passed"] is True | |
| def test_precision_first_rejects_buy_count_expansion_and_prefers_precision(): | |
| rows = [] | |
| for i in range(10): | |
| rows.append( | |
| { | |
| "stock": "2330", | |
| "date": f"2025-04-{i + 1:02d}", | |
| "y_true": 1 if i in {0, 1, 2, 3} else 0, | |
| "base_pred": 1 if i in {0, 1} else 0, | |
| "p_buy": 0.52 if i in {0, 1} else 0.48, | |
| "p_hold": 0.50 if i not in {0, 1} else 0.44, | |
| "p_sell": 0.02, | |
| "bull_score": 3.0 if i in {0, 1, 2, 3, 4, 5} else 0.0, | |
| "bear_score": 2.0 if i in {4, 5} else 0.0, | |
| } | |
| ) | |
| result = optimize_weights( | |
| _events(rows), | |
| optimize_ratio=0.6, | |
| min_signal_ratio=0.3, | |
| objective="precision-first", | |
| max_buy_count_ratio=1.3, | |
| grid={ | |
| "buy_bear_gate": [None, 2.0], | |
| "buy_bull_weight": [0.0, 0.02], | |
| "buy_bear_weight": [0.0], | |
| "sell_bear_weight": [0.0], | |
| "sell_bull_weight": [0.0], | |
| "sell_bull_gate": [None], | |
| "hold_bias": [0.0], | |
| }, | |
| ) | |
| assert result["objective"] == "precision-first" | |
| assert result["max_buy_count_ratio"] == 1.3 | |
| assert result["best_config"]["buy_bull_weight"] == 0.0 | |
| def test_pass_decision_can_enforce_buy_count_ratio(): | |
| result = { | |
| "validation": { | |
| "baseline": {"signal_count": 10, "buy_count": 10}, | |
| "candidate": {"signal_count": 10, "buy_count": 14}, | |
| "deltas": {"accuracy_delta_pp": 5.0, "buy_precision_delta_pp": 0.0}, | |
| } | |
| } | |
| decision = pass_decision( | |
| result, | |
| min_accuracy_delta_pp=5.0, | |
| min_signal_ratio=0.7, | |
| max_buy_count_ratio=1.3, | |
| ) | |
| assert decision["passed"] is False | |
| assert decision["checks"]["buy_count_ratio"] is False | |
| assert decision["buy_count_ratio"] == 1.4 | |
| def test_load_stock_codes_file_accepts_popular_stock_payload(tmp_path): | |
| path = tmp_path / "popular.json" | |
| path.write_text( | |
| """ | |
| { | |
| "codes": ["2330", "0050", "2330"], | |
| "stocks": [{"code": "ignored"}] | |
| } | |
| """ | |
| ) | |
| assert load_stock_codes_file(path) == ["2330", "0050"] | |