51 """One Pine source or compiled artifact and its runtime inputs."""
56 dataset_ids: tuple[str, ...]
57 fixed_inputs: Mapping[str, JsonScalar]
58 strategy_overrides: Mapping[str, JsonScalar]
59 search_space: Mapping[str, ParameterSpec]
62@dataclass(frozen=True)
1125 path: str | Path, *, require_files: bool =
False, continuation: bool =
False
1127 """Load StudySpec v1 and resolve every filesystem path relative to its JSON file."""
1129 spec_path = Path(path).expanduser().resolve()
1131 source = spec_path.read_text(encoding=
"utf-8")
1132 except OSError
as error:
1135 ).with_failure(
"hpo_input_file_invalid", {})
from error
1137 document = json.loads(
1139 object_pairs_hook=_reject_duplicate_keys,
1140 parse_constant=_reject_json_constant,
1142 except StudySpecError:
1144 except json.JSONDecodeError
as error:
1149 f
"invalid JSON at line {error.lineno}, "
1150 f
"column {error.colno}: {error.msg}",
1155 issues: list[ValidationIssue] = []
1156 root = _object(document,
"$", issues)
1172 schema_version = root.get(
"schema_version")
1173 if schema_version != 1:
1175 mode = root.get(
"mode")
1176 if mode !=
"single_strategy":
1178 ValidationIssue(
"$.mode",
"this loader currently supports single_strategy")
1181 strategies = root.get(
"strategies")
1182 strategy_raw: Any = {}
1183 if not isinstance(strategies, list)
or len(strategies) != 1:
1187 if isinstance(strategies, list)
and strategies:
1188 strategy_raw = strategies[0]
1190 strategy_raw = strategies[0]
1191 strategy = _parse_strategy(strategy_raw, spec_path.parent, issues)
1193 symbol_feeds = root.get(
"symbol_feeds")
1194 if isinstance(symbol_feeds, str)
and symbol_feeds:
1195 symbol_feeds = (spec_path.parent / Path(symbol_feeds).expanduser()).resolve()
1196 elif symbol_feeds
is not None and not isinstance(symbol_feeds, dict):
1199 "$.symbol_feeds",
"must be an index path or inline index object"
1203 datasets_raw = root.get(
"datasets")
1204 datasets: list[DatasetSpec] = []
1205 if not isinstance(datasets_raw, list)
or not datasets_raw:
1206 issues.append(
ValidationIssue(
"$.datasets",
"must be a non-empty array"))
1209 _parse_dataset(value, index, spec_path.parent, issues)
1210 for index, value
in enumerate(datasets_raw)
1212 dataset_ids = [dataset.id
for dataset
in datasets
if dataset.id]
1213 if len(set(dataset_ids)) != len(dataset_ids):
1214 issues.append(
ValidationIssue(
"$.datasets",
"dataset ids must be unique"))
1215 unknown_dataset_ids = sorted(set(strategy.dataset_ids) - set(dataset_ids))
1216 if unknown_dataset_ids:
1219 "$.strategies[0].datasets",
1220 "unknown dataset ids: " +
", ".join(unknown_dataset_ids),
1224 objective = _parse_objective(root.get(
"objective"), issues)
1225 sampler = _parse_sampler(root.get(
"sampler"), spec_path.parent, issues)
1226 _validate_candidate_policy(strategy, sampler, issues, continuation=continuation)
1227 execution = _parse_execution(root.get(
"execution"), issues)
1228 if sampler.kind ==
"candidates" and execution.pruner !=
"none":
1233 "$.execution.pruner",
"must be none when sampler.kind is candidates"
1238 sampler.kind ==
"tpe"
1239 and execution.batch_lag == 1
1240 and sampler.config
is not None
1241 and not sampler.config.constant_liar
1245 "$.execution.batch_lag",
1246 "lag-one TPE requires sampler.config.constant_liar=true",
1251 sampler.kind ==
"tpe"
1252 and execution.schedule ==
"rolling"
1253 and sampler.config
is not None
1254 and not sampler.config.constant_liar
1260 "$.execution.schedule",
1261 "rolling TPE requires sampler.config.constant_liar=true",
1266 strategy_path = strategy.source
or strategy.artifact
1267 if strategy_path
is not None and not strategy_path.is_file():
1270 "$.strategies[0]", f
"file does not exist: {strategy_path}"
1273 listed = sampler.candidates_file
1274 if listed
is not None and not listed.is_file():
1279 "$.sampler.config.candidates_file",
1280 "candidate list file does not exist",
1283 for index, dataset
in enumerate(datasets):
1285 dataset.ohlcv_bin
if dataset.ohlcv_bin
is not None else dataset.ohlcv
1287 source_key =
"ohlcv_bin" if dataset.ohlcv_bin
is not None else "ohlcv"
1288 if source
is not None and not source.is_file():
1291 f
"$.datasets[{index}].{source_key}",
1292 f
"file does not exist: {source}",
1300 mode=
"single_strategy",
1302 datasets=tuple(datasets),
1303 objective=objective,
1305 execution=execution,
1306 spec_path=spec_path,
1307 symbol_feeds=symbol_feeds,