48 """One Pine source or compiled artifact and its runtime inputs."""
53 dataset_ids: tuple[str, ...]
54 fixed_inputs: Mapping[str, JsonScalar]
55 strategy_overrides: Mapping[str, JsonScalar]
56 search_space: Mapping[str, ParameterSpec]
59@dataclass(frozen=True)
926def load_study_spec(path: str | Path, *, require_files: bool =
False) -> StudySpec:
927 """Load StudySpec v1 and resolve every filesystem path relative to its JSON file."""
929 spec_path = Path(path).expanduser().resolve()
931 source = spec_path.read_text(encoding=
"utf-8")
932 except OSError
as error:
937 document = json.loads(
939 object_pairs_hook=_reject_duplicate_keys,
940 parse_constant=_reject_json_constant,
942 except StudySpecError:
944 except json.JSONDecodeError
as error:
949 f
"invalid JSON at line {error.lineno}, "
950 f
"column {error.colno}: {error.msg}",
955 issues: list[ValidationIssue] = []
956 root = _object(document,
"$", issues)
971 schema_version = root.get(
"schema_version")
972 if schema_version != 1:
974 mode = root.get(
"mode")
975 if mode !=
"single_strategy":
977 ValidationIssue(
"$.mode",
"this loader currently supports single_strategy")
980 strategies = root.get(
"strategies")
981 strategy_raw: Any = {}
982 if not isinstance(strategies, list)
or len(strategies) != 1:
986 if isinstance(strategies, list)
and strategies:
987 strategy_raw = strategies[0]
989 strategy_raw = strategies[0]
990 strategy = _parse_strategy(strategy_raw, spec_path.parent, issues)
992 datasets_raw = root.get(
"datasets")
993 datasets: list[DatasetSpec] = []
994 if not isinstance(datasets_raw, list)
or not datasets_raw:
995 issues.append(
ValidationIssue(
"$.datasets",
"must be a non-empty array"))
998 _parse_dataset(value, index, spec_path.parent, issues)
999 for index, value
in enumerate(datasets_raw)
1001 dataset_ids = [dataset.id
for dataset
in datasets
if dataset.id]
1002 if len(set(dataset_ids)) != len(dataset_ids):
1003 issues.append(
ValidationIssue(
"$.datasets",
"dataset ids must be unique"))
1004 unknown_dataset_ids = sorted(set(strategy.dataset_ids) - set(dataset_ids))
1005 if unknown_dataset_ids:
1008 "$.strategies[0].datasets",
1009 "unknown dataset ids: " +
", ".join(unknown_dataset_ids),
1013 objective = _parse_objective(root.get(
"objective"), issues)
1014 sampler = _parse_sampler(root.get(
"sampler"), issues)
1015 _validate_candidate_policy(strategy, sampler, issues)
1016 execution = _parse_execution(root.get(
"execution"), issues)
1019 strategy_path = strategy.source
or strategy.artifact
1020 if strategy_path
is not None and not strategy_path.is_file():
1023 "$.strategies[0]", f
"file does not exist: {strategy_path}"
1026 for index, dataset
in enumerate(datasets):
1027 if not dataset.ohlcv.is_file():
1030 f
"$.datasets[{index}].ohlcv",
1031 f
"file does not exist: {dataset.ohlcv}",
1039 mode=
"single_strategy",
1041 datasets=tuple(datasets),
1042 objective=objective,
1044 execution=execution,
1045 spec_path=spec_path,