|
PineForge HPO 0.1.0
Native hyperparameter optimization for PineForge strategies
|
StudySpec is the JSON configuration consumed by pineforge-hpo run. The implemented executable subset is intentionally narrow: single_strategy, one strategy, one referenced OHLCV dataset, an expression objective, and grid, seeded-random, adaptive dlib global, or native TPE sampling.
All strategy, artifact, and OHLCV paths are resolved relative to the StudySpec file, not the caller's current working directory.
Run it with:
| Field | Executable requirement |
|---|---|
schema_version | Must be 1. |
mode | Must be "single_strategy". |
strategies | Must contain exactly one strategy object. |
datasets | The executable path requires exactly one referenced dataset. |
objective | Must use kind="expression". |
sampler | grid, random, dlib_global, or tpe, plus its candidate policy. |
execution | Sequential or threaded native execution. |
Unknown fields, duplicate JSON keys, and JSON NaN/Infinity constants are rejected.
A strategy defines exactly one of:
or:
source uses the direct PineScript path:
artifact skips transpilation and compilation and is useful for an already validated plugin. It must remain beside its ArtifactBuilder manifest.json; the runner verifies the plugin hash and uses the manifest to validate every input name, type, bound, and option before execution. The Python distribution is called pineforge-codegen; the repository is pineforge-codegen-oss; the module is pineforge_codegen.
Other strategy fields:
id: non-empty identifier included in provenance;datasets: non-empty list of dataset IDs; the executable currently requires one;fixed_inputs: input titles and finite JSON scalar values applied on every trial;strategy_overrides: fixed runtime overrides applied on every trial;search_space: non-empty map of tunable Pine input titles.An input cannot appear in both fixed_inputs and search_space.
Candidate values and fixed_inputs are serialized and passed to strategy_set_input(). strategy_overrides are passed to strategy_set_override() before execution. This allows supported engine fields such as initial capital, commission, pyramiding, slippage, and quantity settings to change per study without retranspiling the Pine source.
The engine validates the final string representation. Invalid values produce a trial error; they must not silently fall back to Pine defaults.
low and high are inclusive integers. step must be positive.
Log-scaled integer, usable by every implemented sampler:
Log bounds must be strictly positive and step must be 1 (its default). Random, dlib global, and TPE use the transformed sampling interval [ln(low - 0.5), ln(high + 0.5)], retain observations at ln(value), and decode by rounding exp(z) to the nearest in-range integer. Grid still enumerates every integer in declaration order; log changes model geometry, not grid cardinality.
Stepped real, usable by all implemented samplers:
Continuous real, usable by random, dlib global, and TPE search:
sampler_default preserves this continuous form for random, dlib global, and TPE. Grid and both finite candidate policies require step whenever low != high. A fixed real with low == high contributes one value and does not require step.
Log-scaled real, usable by random, dlib global, and TPE search:
Both bounds must be strictly positive. A log real cannot declare step, so a non-constant log real is not a finite grid dimension and is rejected by without_replacement and exhaustive. A fixed log real remains a valid one-value finite dimension. Sampled values and native output remain in the original units even though adaptive models use z = ln(value) internally.
Grid order is false, then true.
choices must contain at least one finite JSON scalar. Choice order is significant for deterministic grid enumeration. String, integer, real, and boolean choice types are preserved in native trial JSON and must match the compiled Pine input type.
Conditional search spaces are not implemented. log is valid only on integer and real dimensions under the contracts above.
without_replacement and exhaustive require an exact finite Cartesian domain. The legal values of an integer or varying stepped-real dimension are
high is an inclusive upper bound but belongs to the lattice only when some integer k reaches it. For example, low=0, high=1, and step=0.3 yield 0, 0.3, 0.6, and 0.9, not an extra off-step endpoint. Fixed real dimensions contribute one value, booleans contribute two, and categorical dimensions contribute their declared number of choices. The domain cardinality is the checked product of these per-dimension counts; overflow or an undefined count is an initialization error for a finite policy.
The binary64 codec evaluates a real grid point with one fused multiply-add. It snaps only a final decoded value no more than one representable value above high; this makes decimal 0..0.3 step 0.1 contain the declared 0.3 while leaving 0..1 step 0.3 off-lattice. A stepped real may contain at most 2^53 ordinals. Construction rejects any real or categorical grid whose distinct ordinals serialize to the same strategy-ABI value.
Uniqueness applies to the complete parameter vector, not to individual fields. The coordinator reserves the vector before returning it to a worker, so pending requests participate in duplicate prevention.
The current run command accepts one dataset. The CSV loader expects normalized OHLCV rows with timestamps compatible with PineForge's public bar contract.
id: unique non-empty ID referenced by the strategy;ohlcv: CSV path relative to the StudySpec;input_tf: chart/input timeframe passed to run_backtest_full();script_tf: script timeframe; defaults to input_tf in the loader;chart_timezone: chart timezone; defaults to UTC.Multi-dataset train/validation/test roles and walk-forward folds are not executable yet.
The executable objective is an expression:
direction is maximize or minimize.
Supported expression syntax:
+, -, *, /, unary +, unary -;min(a, b), max(a, b), abs(a);<, <=, >, >=, ==, !=.Comparisons evaluate to 1.0 or 0.0. Constraint expressions are satisfied when non-zero.
Metric lookup supports:
metrics.all.<trade-stat>;metrics.longs.<trade-stat>;metrics.shorts.<trade-stat>;metrics.equity.<equity-stat>;report.total_trades, report.net_profit, selected processed-bar counters, and their short aliases.Missing/non-finite metrics and division by zero follow the configured reject policy and leave the trial auditable as an objective or constraint error. An unknown metric fails before the first backtest. Constraints always reject division by zero and non-finite operands; a non-finite final objective is never feasible or rankable even when non-finite intermediate objective evaluation is enabled.
The JSON loader recognizes a future kind="registered" shape, and the C++ library exposes ObjectiveFn<Observation> for custom functions. The executable CLI does not resolve registered objective names yet.
Every sampler requires an explicit non-negative seed and positive trials. The optional candidate_policy defaults to sampler_default. Only tpe accepts a non-empty config; other sampler kinds reject it instead of silently ignoring configuration.
| Value | Supported samplers | Validation and behavior |
|---|---|---|
sampler_default | All | Preserve native sampler behavior. Continuous real dimensions and repeated adaptive proposals remain possible. |
without_replacement | tpe, grid | Require a finite domain, reserve every complete vector at most once, and require trials <= domain cardinality. |
exhaustive | tpe, grid | Require a finite domain and trials == domain cardinality; every declared complete vector is proposed exactly once. |
candidate_policy is a sibling of kind, seed, trials, and config:
The finite policies reject random and dlib_global; they never silently change those samplers into grid or TPE. sampler_default is backward compatible with existing continuous TPE studies.
Grid search enumerates the Cartesian product in stable order. trials caps the number executed; a sufficiently large value exhausts the grid. seed is retained for a uniform reproducibility/provenance shape but does not alter grid order. without_replacement makes the already-unique grid contract explicit and requires the cap not to exceed cardinality. exhaustive requires it to equal cardinality.
Random search requires an explicit non-negative seed and a positive trial count. The native sampler uses a deterministic mt19937_64 sequence and avoids implementation-specific standard distributions. It supports only candidate_policy="sampler_default".
dlib_global wraps dlib's global_function_search through an adaptive ask/tell interface. The seed must be between 0 and 2147483647, inclusive. A fixed worker-sized batch is proposed, evaluated concurrently, and reported in trial-id order before the next batch is requested. The exact deterministic sequence is therefore scoped to the same seed, worker count, dlib version, and native toolchain.
dlib_global supports only candidate_policy="sampler_default".
Integer and stepped-real dimensions use integer indices; booleans use 0/1; categorical choices use their declaration-order index. A one-value dimension is removed from dlib's vector and restored in the candidate. Categorical indices impose an artificial ordinal geometry, so this is not equivalent to categorical TPE. Duplicate parameter sets may be proposed and are evaluated as separate trials.
dlib always maximizes internally; minimize objectives are sign-inverted. Failed and infeasible requests are abandoned because dlib has no native hard- constraint contract. Only genuine feasible objective values train the model or participate in reported best-trial selection. A future constraint-aware sampler may add explicit violation magnitudes instead of fabricated objective scores.
tpe is a native, single-objective Tree-structured Parzen Estimator sampler. It fits independent per-dimension marginals to good (l) and bad (g) completed observations. Numeric dimensions use bounded Gaussian mixtures; integer and stepped-real values use quantized bin-mass likelihoods. Boolean and categorical dimensions use prior-smoothed categorical probabilities. It draws ei_candidates joint candidates from l and selects the largest summed log(l / g) score.
The good-set target is:
and is clamped to leave at least one bad observation when the history permits. Before startup_trials usable observations, proposals use the seeded random path. Failed and constraint-violating trials are abandoned and train neither estimator.
Under sampler_default, TPE may propose the same complete vector more than once. Under either finite policy, every vector is atomically reserved before it is returned by ask(). Pending, completed, infeasible, and failed vectors stay in the seen set. An abandoned point remains ineligible for a later proposal even though it does not enter either density estimator.
Because expression constraints currently return only pass/fail, TPE does not learn the direction or magnitude of a violation. Studies with a tiny feasible region should encode validity into the search space where possible; otherwise the sampler may revisit infeasible regions. No fabricated penalty objective is inserted automatically.
TPE config fields are strict; unknown fields are rejected:
| Field | Default | Requirement |
|---|---|---|
startup_trials | 10 | Positive integer. |
ei_candidates | 24 | Integer in [1, 1000000]; the upper bound prevents an accidental near-infinite ask(). |
gamma_fraction | 0.1 | Finite number in (0, 1]. |
gamma_cap | 25 | Positive integer. |
prior_weight | 1.0 | Finite number greater than zero. |
constant_liar | true | Boolean. |
candidate_policy is not a TPE estimator setting and therefore belongs beside config, not inside it.
When constant_liar=true, parameters from outstanding requests are inserted only into the bad estimator. This discourages duplicate concurrent proposals without fabricating an objective value. The scheduler still reports completed results in trial-id order, so reproducibility is scoped to the same seed, worker count, sampler config, and native build.
TPE retains and refits the complete usable history. It is intended for bounded adaptive refinement, not 10^6-10^8 trial enumeration; use an external partitioned stateless search to reduce a mega-scale domain before starting a TPE study.
CMA-ES, evolutionary sampling, pruning-aware search, durable study storage, and multi-objective Pareto optimization are not currently available.
Threaded execution:
Sequential execution:
Every worker shares the immutable dataset and loaded plugin function table, but every trial creates and destroys its own strategy handle and report. A processes value is reserved in the schema for future cross-plugin portfolio execution; the current single-strategy CLI does not launch process workers. timeout_seconds and fail_fast are also reserved and currently rejected so they cannot appear to take effect without real cancellation semantics.
For dlib_global and tpe, workers is also the deterministic ask/tell batch size. Changing it can change later proposals even when the seed stays the same.
Candidate-policy and coverage fields are part of replay provenance alongside sampler implementation, sampler configuration, seed, worker count, artifact key, and search-space definition:
| Field | Contract |
|---|---|
candidate_policy | Requested sampler.candidate_policy. |
candidate_policy_implementation | Stable implementation identity; compare it when replaying a proposal sequence. |
search_space_finite | Whether the declared space has an exact finite cardinality. |
search_space_cardinality | Exact Cartesian cardinality for a finite space; absent/null when no finite value exists. |
trials_requested | Positive trial budget from StudySpec. |
trials_completed | Number of terminal trial records emitted. |
unique_candidates_attempted | Distinct complete parameter vectors represented by terminal trials. |
duplicate_proposals_skipped | Internal duplicate proposals rejected before trial execution; they do not consume trial budget. |
remaining_candidates | Finite cardinality minus unique attempted vectors; absent/null for a non-finite space. |
search_space_exhausted | Whether no unattempted vector remains in a finite space. |
stop_reason | Machine-readable reason that candidate generation stopped. |
full_parameter_coverage | Every vector in the declared finite domain has a terminal trial record. |
exhaustive_equivalent | Full parameter coverage and every trial status is ok or constraint_violation. |
Full parameter coverage means that every declared complete vector has a terminal trial record. It is deliberately weaker than objective coverage. A constraint violation or failed evaluation remains seen and can coexist with full parameter coverage, but a failure supplies no comparable objective.
Consequently, candidate_policy="exhaustive" does not alone certify equality with the best result of a successful grid run. exhaustive_equivalent remains false after any engine, objective, constraint-evaluation, serialization, or other trial error. When it is true, grid-best equivalence additionally assumes the same deterministic artifact, dataset, runtime settings, objective, and constraint policy. Adaptive TPE and grid may visit the same set in different orders, so intermediate best-so-far results need not match even when their final best result does.
study.json.transpile_full() supplies input title, type, default, and any literal bounds/step/options retained in the artifact manifest.search_space explicitly selects the tunable inputs.fixed_inputs are never sampled and cannot overlap a tunable input.