Add dynamic exit plan search diagnostics
This commit is contained in:
@@ -14,6 +14,7 @@ if str(TRAINING_ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(TRAINING_ROOT))
|
||||
|
||||
from trader_training.onnx_export import LinearHead, export_heads
|
||||
from trader_training.dynamic_exit_search import search_dynamic_exit_plans
|
||||
from trader_training.entry_feature_screen import _bucket_edges, _screen_edge_column
|
||||
from trader_training.io_utils import read_json, write_json
|
||||
from trader_training.labels import ENTRY_LABEL_METHOD, _path_stats_for_group, build_entry_labels
|
||||
@@ -56,6 +57,65 @@ class TrainingContractTest(unittest.TestCase):
|
||||
self.assertEqual(-np.inf, edges[0])
|
||||
self.assertEqual(np.inf, edges[-1])
|
||||
|
||||
def test_dynamic_exit_search_writes_plan_diagnostics(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
data_root = Path(tmp)
|
||||
run_root = data_root / "trader-v4" / "runs" / "unit-dynamic-exit"
|
||||
feature_path = run_root / "feature" / "feature_frame.parquet"
|
||||
replay_path = run_root / "replay" / "replay_1m.parquet"
|
||||
config_path = data_root / "label_config.json"
|
||||
feature_path.parent.mkdir(parents=True)
|
||||
replay_path.parent.mkdir(parents=True)
|
||||
|
||||
times = pd.date_range("2026-01-01", periods=7, freq="min", tz="UTC")
|
||||
pd.DataFrame(
|
||||
{
|
||||
"sample_id": [f"s{i}" for i in range(4)],
|
||||
"symbol": ["BTC-USDT-PERP"] * 4,
|
||||
"event_time": times[:4],
|
||||
"open_time_ms": [0, 60_000, 120_000, 180_000],
|
||||
"split_id": ["tune_inner", "validation_locked", "latest_stress", "fit_inner"],
|
||||
"walk_forward_fold": [0, 0, 0, 0],
|
||||
"data_quality_flag": ["OK", "OK", "OK", "OK"],
|
||||
}
|
||||
).to_parquet(feature_path, index=False)
|
||||
pd.DataFrame(
|
||||
{
|
||||
"event_time": times,
|
||||
"open_time_ms": [0, 60_000, 120_000, 180_000, 240_000, 300_000, 360_000],
|
||||
"symbol": ["BTC-USDT-PERP"] * 7,
|
||||
"open": [100.0] * 7,
|
||||
"high": [100.0, 100.12, 100.22, 100.24, 100.24, 100.24, 100.24],
|
||||
"low": [100.0, 100.00, 100.00, 100.00, 100.00, 100.00, 100.00],
|
||||
"close": [100.0, 100.10, 100.18, 100.20, 100.20, 100.20, 100.20],
|
||||
"spread_bps": [1.0] * 7,
|
||||
}
|
||||
).to_parquet(replay_path, index=False)
|
||||
write_json(config_path, {"entry": {"min_expected_net_edge_bps": 3.0}})
|
||||
|
||||
search_dynamic_exit_plans(
|
||||
Namespace(
|
||||
data_root=data_root,
|
||||
run_id="unit-dynamic-exit",
|
||||
feature_path=feature_path,
|
||||
replay_path=replay_path,
|
||||
label_config_path=config_path,
|
||||
cost_config_path=None,
|
||||
horizons=(3,),
|
||||
targets=(10.0,),
|
||||
stops=(5.0,),
|
||||
trailing_stops=(4.0,),
|
||||
second_target_multipliers=(2.0,),
|
||||
take1_ratios=(0.5,),
|
||||
take2_ratios=(0.25,),
|
||||
)
|
||||
)
|
||||
|
||||
result = read_json(run_root / "dynamic-exit-search" / "dynamic_exit_search_result.json")
|
||||
self.assertEqual("DYNAMIC_TRAILING_V1", result["best_plan"]["plan_method"])
|
||||
self.assertEqual(1, result["candidate_count"])
|
||||
self.assertTrue((run_root / "dynamic-exit-search" / "dynamic_exit_search_report.md").is_file())
|
||||
|
||||
def test_split_builder_uses_locked_validation_contract(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
data_root = Path(tmp)
|
||||
|
||||
Reference in New Issue
Block a user