]> Piment Noir Git Repositories - freqai-strategies.git/log
freqai-strategies.git
2 days agochore(qa): refresh basedpyright diagnostic snapshots main
Jérôme Benoit [Sat, 10 Oct 2026 16:18:06 +0000 (18:18 +0200)] 
chore(qa): refresh basedpyright diagnostic snapshots

2 days agodocs: align configuration and reward analysis references
Jérôme Benoit [Sat, 10 Oct 2026 16:02:23 +0000 (18:02 +0200)] 
docs: align configuration and reward analysis references

2 days agochore(deps): update astral-sh/setup-uv action to v10.3.0 (#290)
renovate[bot] [Sat, 10 Oct 2026 15:10:49 +0000 (17:10 +0200)] 
chore(deps): update astral-sh/setup-uv action to v10.3.0 (#290)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
7 days agorefactor(quickadapter): split the position across one exit ladder (#289)
Jérôme Benoit [Mon, 5 Oct 2026 20:45:19 +0000 (22:45 +0200)] 
refactor(quickadapter): split the position across one exit ladder (#289)

* refactor(quickadapter): release the position in equal exit shares

* refactor(quickadapter): split the position across one exit ladder

* fix(quickadapter): preserve native lots in equal-share exits

* fix(quickadapter): retain raw antecedents for partial exits

* chore(quickadapter): bump version to 3.13.0-rc.15

7 days agofeat(quickadapter): configure the exit-stage fraction series (#286)
Jérôme Benoit [Mon, 5 Oct 2026 14:00:48 +0000 (16:00 +0200)] 
feat(quickadapter): configure the exit-stage fraction series (#286)

* feat(quickadapter): configure the exit-stage fraction series

* fix(quickadapter): use integer-power retracement ladder

* fix(quickadapter): correct the exit-stage ladder documentation and guards

* fix(quickadapter): bound the ladder base and index rungs by stage

* refactor(quickadapter): name the ladder after natr_multiplier_fraction

* test(quickadapter): restore partial exit regression guarantees

* test(quickadapter): avoid pinning diagnostic wording

* refactor(quickadapter): align take-profit extension terminology

* test(quickadapter): avoid pinning private diagnostic presentation

* feat(quickadapter): default final retracement fraction to 0.5

* refactor(quickadapter): consistently access static exit stage settings

* docs(quickadapter): keep exit fraction documentation in the configuration table

* chore(quickadapter): bump version to 3.13.0-rc.14

* fix(quickadapter): harmonize exit-pricing diagnostics

* docs(quickadapter): remove incidental startup log detail

* fix(quickadapter): report effective startup configuration

* fix(quickadapter): deduplicate startup horizon warnings

* docs(quickadapter): focus option descriptions on user behavior

7 days agochore(deps): update dependency basedpyright to v1.40.2 (#287)
renovate[bot] [Mon, 5 Oct 2026 11:09:48 +0000 (13:09 +0200)] 
chore(deps): update dependency basedpyright to v1.40.2 (#287)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
8 days agochore(deps): lock file maintenance (#277)
renovate[bot] [Sun, 4 Oct 2026 21:19:38 +0000 (23:19 +0200)] 
chore(deps): lock file maintenance (#277)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
8 days agochore(devcontainer): disable oh-my-opencode cache mounts
Jérôme Benoit [Sat, 3 Oct 2026 22:44:21 +0000 (00:44 +0200)] 
chore(devcontainer): disable oh-my-opencode cache mounts

8 days agochore(devcontainer): install OpenCode v2
Jérôme Benoit [Sat, 3 Oct 2026 22:37:39 +0000 (00:37 +0200)] 
chore(devcontainer): install OpenCode v2

9 days agochore(quickadapter): bump version to 3.13.0-rc.13
Jérôme Benoit [Sat, 3 Oct 2026 20:48:01 +0000 (22:48 +0200)] 
chore(quickadapter): bump version to 3.13.0-rc.13

9 days agodocs(reforcexy): apply prettier formatting to reference docs
Jérôme Benoit [Sat, 3 Oct 2026 20:39:58 +0000 (22:39 +0200)] 
docs(reforcexy): apply prettier formatting to reference docs

9 days agofix(reforcexy): enforce PBRS contracts and 70% coverage (#282)
Jérôme Benoit [Sat, 3 Oct 2026 20:08:47 +0000 (22:08 +0200)] 
fix(reforcexy): enforce PBRS contracts and 70% coverage (#282)

fix(reforcexy): enforce PBRS contracts and 70% coverage

9 days agofix(reforcexy): preserve native duration consumers and coverage provenance
Jérôme Benoit [Sat, 3 Oct 2026 19:09:31 +0000 (21:09 +0200)] 
fix(reforcexy): preserve native duration consumers and coverage provenance

9 days agochore: merge upstream/main preserving documentation and QA contracts
Jérôme Benoit [Sat, 3 Oct 2026 17:23:19 +0000 (19:23 +0200)] 
chore: merge upstream/main preserving documentation and QA contracts

9 days agofix(docs): align strategy guides and analysis reports (#285)
Jérôme Benoit [Sat, 3 Oct 2026 16:23:41 +0000 (18:23 +0200)] 
fix(docs): align strategy guides and analysis reports (#285)

Document effective QuickAdapter and ReforceXY behavior, supplied profiles and SDK boundaries. Clarify method-specific split defaults and standardized Yeo-Johnson labels. Align analyzer export availability and confidence-interval contracts, native template checks, evaluation and maintenance guidance.

Runtime algorithms and defaults remain unchanged. Quality and CodeQL pass on the reviewed head. The automated security review is unavailable because its service quota is exceeded.

9 days agofix(reforcexy): preserve native duration precision and finite gamma bounds
Jérôme Benoit [Fri, 2 Oct 2026 22:34:30 +0000 (00:34 +0200)] 
fix(reforcexy): preserve native duration precision and finite gamma bounds

10 days agochore: merge upstream/main preserving documentation semantics
Jérôme Benoit [Fri, 2 Oct 2026 20:34:57 +0000 (22:34 +0200)] 
chore: merge upstream/main preserving documentation semantics

10 days agofix(reforcexy): synchronize analytical reward boundary contracts
Jérôme Benoit [Fri, 2 Oct 2026 20:13:57 +0000 (22:13 +0200)] 
fix(reforcexy): synchronize analytical reward boundary contracts

10 days agofix(quickadapter): normalize FRAMA and cover exit contracts (#284)
Jérôme Benoit [Fri, 2 Oct 2026 19:11:25 +0000 (21:11 +0200)] 
fix(quickadapter): normalize FRAMA and cover exit contracts (#284)

Normalize FRAMA window ranges while preserving close, seed, historical window and zero-lag preprocessing. Add native custom_exit consumer contracts and fix the remaining index commentary. Refresh documentation, measured coverage and snapshot line positions without changing accepted diagnostics.

10 days agofix(reforcexy): close persisted reward and QA boundary findings
Jérôme Benoit [Fri, 2 Oct 2026 18:43:29 +0000 (20:43 +0200)] 
fix(reforcexy): close persisted reward and QA boundary findings

10 days agofix(reforcexy): enforce PBRS contracts and 70% coverage
Jérôme Benoit [Fri, 2 Oct 2026 16:37:42 +0000 (18:37 +0200)] 
fix(reforcexy): enforce PBRS contracts and 70% coverage

10 days agofix(docs): resolve coverage documentation merge conflict
Jérôme Benoit [Fri, 2 Oct 2026 15:10:34 +0000 (17:10 +0200)] 
fix(docs): resolve coverage documentation merge conflict

10 days agoMerge remote-tracking branch 'upstream/main' into unit-tests-reforcexy
Jérôme Benoit [Fri, 2 Oct 2026 15:09:21 +0000 (17:09 +0200)] 
Merge remote-tracking branch 'upstream/main' into unit-tests-reforcexy

# Conflicts:
# README.md

10 days agotest(quickadapter): strengthen reviewed consumer contracts (#283)
Jérôme Benoit [Fri, 2 Oct 2026 15:04:15 +0000 (17:04 +0200)] 
test(quickadapter): strengthen reviewed consumer contracts (#283)

10 days agofix(reforcexy): make the review findings bite, and drop what nothing calls
Jérôme Benoit [Fri, 2 Oct 2026 14:52:58 +0000 (16:52 +0200)] 
fix(reforcexy): make the review findings bite, and drop what nothing calls

An adversarial review of #282 found four defects that survived mutation
testing: code deleted, the suite stayed green. All four are now caught.

The strategy's four `do_predict == 1` conditions were independent, and only
the `enter_long` one was asserted — deleting the other three changed nothing
observable. The same held for `is_open=True` on the rejected-prediction
close: without it the proxy returns CLOSED trades too, so `exit_long` would
fire on an already-closed position. It is now observed through freqtrade's own
backtest collections rather than asserted as a call argument, which the repo
forbids. Four mutations, four reds.

The orderings guard tested a private copy of the shuffle comparator, so making
the installed one seed-insensitive left it green — the property the
three-seed job buys was unverifiable. It now drives `unittest.TestLoader` on a
probe class through the comparator the loader really holds, and asserts that
changing the seed changes the order and that repeating it does not.

The inheritance guard exempted whole modules, so a TestCase dropped into
test_coverage_floor.py ran with no restore and no red. It now exempts the one
class that observes the restore instead of testing it.

`source_pkgs`, `source_dirs` and `plugins` are legal `[run]` options the gate
neither banned nor reconciled: adding a second measurement root left it green.

Two eval-frequency tests survived deletion of the code they named. The clamp
test now exceeds the cap on the value-based branch, and the hyperopt test
pins two explicit outcomes rather than restating the formula. The remaining
non-positive-budget test says what it actually checks instead of claiming a
guard it cannot detect.

Also: removed `ohlcv_frame` and `rl_env` (no caller), the dead `hpo=` knob,
the `__FLOOR__` guard that guards a string no template produces, the repr and
exception-wording pins, a `len`-only date assertion, a forwarding `assertIs`,
and a `continue` that silently skipped a subTest iteration. The CI step and the
README now state what each floor guard really refuses — they are not
interchangeable, and this one is the stricter.

124 tests green under four orderings; gate exits 0 at 59.1%; ruff clean;
basedpyright snapshot unchanged.

10 days agoMerge branch 'main' into unit-tests-reforcexy
Jérôme Benoit [Fri, 2 Oct 2026 12:36:00 +0000 (14:36 +0200)] 
Merge branch 'main' into unit-tests-reforcexy

10 days agotest(reforcexy): cover the strategy's signals, validation and sizing
Jérôme Benoit [Fri, 2 Oct 2026 09:30:37 +0000 (11:30 +0200)] 
test(reforcexy): cover the strategy's signals, validation and sizing

RLAgentStrategy is the thinnest layer of the strategy and was the least
covered: 47.2%, with every symbol but `leverage` untouched. It is also
where a wrong answer costs money directly — an entry flag raised on a row
whose prediction the model has not published trades on a value that does
not exist yet.

The action literals are asserted as a cross-object equality with the enum
rather than as two independent literals, so both survive a freqtrade
renumbering only if they agree; both would still pass if each were simply
retyped.

Signal tests read the flags through a NaN-aware helper, because the
assignment creates the column and an untouched row arrives as a missing
float rather than a zero — asserting `== 0` would have encoded a shape
the strategy does not produce.

125 tests green; gate exits 0 at 59.1%, floor raised to 59 with its
measurement; green under three seeded method orderings.

10 days agofix(reforcexy): close the review findings on the gate and its harness
Jérôme Benoit [Fri, 2 Oct 2026 08:42:40 +0000 (10:42 +0200)] 
fix(reforcexy): close the review findings on the gate and its harness

Two of the guards could not fail on the defect they name.

The measured-file guard filtered registrations by the dotted prefix, which
discarded exactly the bare-stem duplicate its own comment called out as the
threat, so it stayed green with the shadow copy live. It now counts every
registration shape-agnostically, and a second guard rejects any measured file
registered under a bare stem. Both walks were also one level too high: they
scanned `ReforceXY/` and so matched the out-of-scope reward-space modules.

The shuffle guard asserted the identity of the comparator the harness had
just installed, which a constant comparator would satisfy. It now asserts the
ORDER the loader produces — alphabetical exactly when no seed is set, and not
alphabetical when one is — with the seeded comparator rebuilt rather than read
out of the module, so the seed is actually exercised.

Also from review: the hyperopt eval-frequency test was a fixed point that
certified a reduction it never performed, so deleting the reduction left it
green; the net-arch tests asserted a key set and a type where the names
claimed widths; the numpy restore compared one element of a five-tuple and
could not see an advanced stream position; the CI step was named for an
ordering it cannot permute, since unittest sorts module and class order
alphabetically and consults no hook for either; and the sampler-pin comment
claimed a guarantee two wholesale call sites void.

92 tests green under four orderings; gate exits 0 at 57.1%; ruff clean;
basedpyright snapshot unchanged.

10 days agotest(reforcexy): add the coverage gate and a shared QA harness
Jérôme Benoit [Fri, 2 Oct 2026 07:47:23 +0000 (09:47 +0200)] 
test(reforcexy): add the coverage gate and a shared QA harness

Brings ReforceXY to gate parity with QuickAdapter: a `.coveragerc`, a
coverage pin in the QA image, and the three modules that make the gate
auditable — a shared harness, a suite contract and a floor guard.

qa_support.py owns the mutable process globals and the fixture builders,
and installs the test-order shuffle. The suite contract pins import
identity: every measured file under exactly one dotted name, every test
module under its bare stem. That second guard is what closes the live
defect this change fixes — `test_training_observations.py` imported
`ReforceXY.tests.test_review_contracts`, registering a second module object
for a file discovery had already registered, so every reset in the harness
would have cleaned a module the code under test never touches. The three
existing modules are renamed to the file-derived `test_model_*` convention
and reparented onto the shared base.

The floor guard replaces QuickAdapter's text scan of coverage's default
exclusions with a differential parse using coverage's own parser, because
the scan over-approximates here (three `...` lines inside a docstring) and
is blind to the branch-level family, which removes 27% of this denominator
with no config key at all.

Measured 57.1% against a floor of 57. The agreed 70% target is not met:
`MINIMUM_FLOOR` is pinned to what was actually reached, not to the target,
because a floor the suite cannot clear is a control the project believes it
has and does not. The gap and the remaining rows are in the pull request.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
10 days agochore(quickadapter): raise coverage floor to 68%
Jérôme Benoit [Fri, 2 Oct 2026 01:00:14 +0000 (03:00 +0200)] 
chore(quickadapter): raise coverage floor to 68%

10 days agofix(quickadapter): address validated test review findings (#281)
Jérôme Benoit [Fri, 2 Oct 2026 00:03:34 +0000 (02:03 +0200)] 
fix(quickadapter): address validated test review findings (#281)

* fix(quickadapter): address validated test review findings

Replace incidental test assertions with independent consumer oracles and real callback and coverage-runner scenarios. Recover persisted holdout metadata causally and stabilize MMAD constant medians. Refresh the exact type snapshot, use a native drawer fixture, and correct FRAMA and coverage documentation without lowering the coverage floor.

* style(quickadapter): group NATR multiplier bounds

11 days agofix(quickadapter): report zigzag pivots as positions and close the coverage gate...
Jérôme Benoit [Thu, 1 Oct 2026 22:03:17 +0000 (00:03 +0200)] 
fix(quickadapter): report zigzag pivots as positions and close the coverage gate (#280)

* fix(quickadapter): pin the real fractal-dimension behaviour and de-duplicate the natr defaults

Two findings from the post-merge review.

`_fractal_dimension` was reported degenerate, and the report is right:
`HL1` and `HL2` each span a HALF of the window while `HL3` spans all of it,
so `HL1 + HL2 <= 2 * HL3`, `D <= 1`, and the clip returns exactly 1.0 on every
reachable input — measured, max 0.99994 over 300000 windows. `alpha` is
therefore identically `exp(-4.6 * 0) == 1.0` and `frama` reproduces `close`
after its seed.

I first rewrote it to Ehlers' sub-range formulation. That was wrong twice
over: `range(0, period, period // 2)` with period 16 still yields two
segments, so the dimension stayed 1.0, and the new code raised on any window
shorter than `period`. Both reverted. Reconstructing that recursion from memory
is not something to ship in a coverage pass.

What lands instead is the honest form. The docstring records the invariant and
the measurement, names the paper formulation that is deliberately NOT adopted,
and the tests now ASSERT the real behaviour: `frama` reproduces `close`, and
the dimension is exactly 1.0 on trending, noisy and plateaued windows alike. The
degeneracy becomes a pinned fact rather than a silent surprise — a change that
made the dimension vary would have to update a test instead of passing unnoticed.
The period guard now also refuses values below 2, which it previously did not.

The second finding: `natr_period` and `natr_multiplier` were written down three
times with no canonical source, which `.github/copilot-instructions.md` forbids
outright. All three now derive from `DEFAULT_LABEL_NATR_PERIOD` and the
existing `DEFAULT_MIN_LABEL_NATR_MULTIPLIER`. Values are unchanged (14, 9.0), so
no behaviour moves. Verified: mutating either canonical constant now turns the
suite red, where before all four spellings drifted together unnoticed.

The test added in `166d63c1` claimed a fixture "can see any of the three" of
the alpha constants. That was false — it is removed, replaced by the two
assertions above.

* test(quickadapter): make two data-split assertions discriminate

Both findings come from the post-merge review and both are now load-bearing.

The weight-recomposition test asserted only the lengths and mean == 1, so any
uniform vector passed, even though the production comment immediately above
names "silently uniformize" as the hazard it exists to prevent. It now asserts
the RATIOS: base is a strict ramp and the label weights are None, so a
recomposition is a pure rescale and the weights must reproduce the base
ratios, which np.ones cannot do. Verified: replacing the validation-split
composition with np.ones now fails with "Mismatched elements: 5 / 6".

The NaN-skip test could not discriminate the guard it names, because its
fixture keeps a clean second column whose value coincides either way. A new
case uses the only emitted column carrying a NaN, where the caller must get
None and fall back to the position-based purge; without the guard the NaN
reaches astype(np.int64) and raises IntCastingNaNError. Verified killed.

One caveat on how the first was verified: the repo holds several
_compose_train_weights_with_support calls at the same indentation, so the
first mutation attempt replaced the wrong call site and appeared to survive.
It only died once the mutant was anchored on the full statement.

* test(quickadapter): bound the inner validation count

The `test_size >= n_train_rows` guard was never reached: relaxing it to `>`
left all 967 tests green. It is the refusal an operator sees when the inner
validation tail is as large as the training rows remaining after the outer
holdout, so losing it turns a clear DependencyException into a numpy error
far from the cause.

The new case exercises the boundary itself — exactly eight rows, and one more —
because `>=` versus `>` is only distinguishable there. Verified killed.

* test(quickadapter): close two ways the coverage gate could be widened silently

The suite contract derived its production module list from two FLAT globs while
the measured tree is `quickadapter/user_data` recursively. The file comment
claims deriving from the tree "keeps this contract true when a source file is
added", which was false for any file outside those two directories: it reached
the coverage denominator and was subject to no contract at all. It now walks
the tree, and derives each module name from its own location — bare name under
`strategies`, dotted path anywhere else. Verified: a flat module added at
`user_data/helpers_probe.py` that fails on import now errors the whole
collection, where before nothing looked at it.

The floor test banned `omit` and nothing else. `omit` is only the first way to
shrink the denominator: `exclude_lines`, `exclude_also` and the partial_*
family are the same lever under other names. Measured on coverage 7.16.2,
exclude_lines, exclude_also and partial_branches are all [report]-only — it
REJECTS them under [run] — so the guard now checks the full set in the section
that honours them, and the contract test checks them in both. Verified:
adding `exclude_also = ^\s*raise\b` to [report] turns the floor test red.

* test(quickadapter): make the fallback warning carry its own diagnosis

Under support_policy=fallback there is no exception for the operator to read, so
the WARNING is the entire diagnostic — but the test only used assertLogs, which
proves THAT something was logged, not WHAT. Dropping a reason from the sparse
support gate still left the suite green on this path.

It now asserts the single record names the threshold that actually failed and not
one that merely happened to trip alongside it. Verified: removing the
effective_sample_size reason turns three tests red, this one included.

Also refreshes the recorded coverage measurement to 69.3 percent (floor 67, gate
exit 0).

* fix(quickadapter): report zigzag pivots as positions, not index labels

`ZigzagResult.indices` was documented at Utils.py:725 as "positions of detected
pivots in series" and consumed that way by `compute_label_weights`, which casts to
int and bounds the result by the row count. It was produced as `df.index.tolist()`
— index LABELS. The two coincide only on a zero-based RangeIndex, which is what
every fixture in this suite used, so the mismatch was invisible.

On what FreqAI actually passes it is not cosmetic. Reproduced in the QA image:

  RangeIndex(0..n)        -> OK, 2 non-zero weights
  RangeIndex(500..500+n)  -> 5/5 pivots dropped, every weight silently 0.0
  DatetimeIndex           -> TypeError: int() argument ... not 'Timestamp'

So any config with `freqai.label_weighting.strategy` other than "none" crashes
`set_freqai_targets` on the first candle of every pair under live and backtest,
and degrades to zero label weighting — with only a warning — on any offset
integer index. The bug predates this suite; the suite is what hid it.

`_zigzag` now appends `pos`, the series assignment uses `.iloc`, and both sites
carry the reason. All three index shapes now agree exactly.

The suite could not have caught this on its own, so two tests were added that can:
one asserts the pivot positions and the label values are invariant across a
shifted, a datetime and a string index; the other drives `compute_label_weights`
end to end and requires a non-zero weight at every pivot. One existing assertion
read `label.series.loc[position]`, which only worked because labels and positions
coincided, and now uses `.iloc`. Verified: reintroducing `df.index[pos]` fails
five tests.

* fix(quickadapter): correct the frama degeneracy claim and pin the period guard

The `_fractal_dimension` docstring asserted two things that are false, and I wrote
them. It claimed "the clip therefore returns exactly 1.0 on every path, so alpha
is identically exp(-4.6 * 0) == 1.0 and frama reproduces close".

The inequality holds — HL1 and HL2 each span a half window while HL3 spans all of
it, so D <= 1 — but the conclusion does not. `np.clip(D, 1.0, 2.0)` is a no-op
for a value already at or above the lower bound, so it passes a ratio of
1.0000000000000002 straight through. Measured on a deterministic ten-bar window:
D = 1.0000000000000002, alpha = 0.999999999999999. `frama` reproduces `close` to
within a few ULP, not bit-exactly, and a short-circuit on `alpha == 1.0` would
never fire.

The docstring now says what is actually true, states the deterministic
counterexample, and says explicitly that nothing downstream may treat alpha as a
literal 1.0. The test named `..._is_exactly_one_on_every_window` asserted
`== 1.0` on three hand-picked windows that happen to land exactly on 1.0, so it
was pinning a rounding accident rather than the contract; it now asserts within
rounding, and a new test carries the counterexample that makes it honest.

Separately, the `period < 2` clause of the guard had no test. The existing case
only drives odd periods, which the evenness clause already catches, so the clause
could be dropped with a green suite — and dropping it hands the caller numpy's
"zero-size array to reduction operation maximum which has no identity" instead of
the documented domain error. Zero is even, so it is the only value that exercises
the clause on its own; that is now the case.

One note on the mutant: rewriting the clause to `period < 1` leaves the suite
green, and that mutant is equivalent, not surviving — period 1 is odd and already
refused. The clause is only killable by removing it, which turns two tests red.

* test(quickadapter): close the four remaining ways the coverage gate goes inert

The gate was declared trustworthy and none of it was enforced. Each of the four
below was verified by mutation, and each now turns the suite red.

A `# pragma: no cover` in a production file. coverage.py always applies a
default `exclude_list` matching that pattern, so it is the source-side twin of
the `omit` key guarded two tests above, and the source side was open. Measured:
one pragma on `_normalize_final_take_profit_state` moved QuickAdapterV3.py from
58.8 % to 60.2 % and the total from 69.3 % to 69.6 %, with every test green. At
2.3 points of headroom the move is repeatable at every uncovered `def`.

The `fail_under` value. `.coveragerc` and README.md:528 both say "raise only,
never lower"; `fail_under = 5` passed everything. The floor test now pins the
lower bound and leaves raising it free, which is the direction the policy allows.

The provenance annotation. It had to contain the word "measured"; it did not have
to contain a date, and `# measured whenever someone remembered` passed. It now
has to record a well-formed date and a real percentage, and that percentage has
to clear the floor it justifies — a floor of 70 annotated with a 69.3 %
measurement is a gate that cannot pass.

Whether the CI step propagates the result. `exit $status` is the only thing
turning a failing `coverage report` into a failing job, no test read the
workflow, and deleting that one line left the whole class green. Verified: with
`fail_under = 99` the step exits 2 with the line and 0 without it.

One residual, deliberate: an old but well-formed date such as 1970-01-01 still
passes. Refusing a date older than N months would make the suite fail on a
repository nobody has touched, which is a time bomb rather than a gate. The
annotation has to be checkable; whether it is current stays a reviewer's call.

* test(quickadapter): pin the volume-weighted efficiency ratio and realign the README

`volume_weighted_efficiency_ratio` is the sixth COMBINED_METRIC feeding label
importance, and it was the only one with no numeric assertion: every check either
mirrored `efficiency_ratio` or expected 1.0, which holds for ANY positive
weighting. The W_LEGS fixture is monotone, so its ratio is 1.0 under every
convention and cannot discriminate. Shifting the volume window by one candle —
same length, different slice — left all 968 tests green.

The new case uses a slow uptrend carrying a fast oscillation, so the legs are
short and genuinely non-monotone and the ratio drops well below 1.0. It
restates the documented convention as an oracle over every leg: each
close-to-close step is weighted by the volume of the candle it lands on, and the
pivot bar at the start of the leg is excluded from the total. It then asserts
that the one-candle shift a mutation introduces gives a different answer, so the
convention is load-bearing rather than an identity. Verified: the window shift
now turns three tests and one error red.

Worth recording, because it cost time: I first tried six explicit shapes for the
fixture and every one gave exactly 1.0, which looked like proof the metric was
constant and therefore dead. It is not — across 3401 legs of random walks, 2941
differ from 1.0 with a wide spread. Zigzag legs are usually monotone, so a
sampled fixture is the only way to reach the non-trivial branch.

The README paragraph the previous commit edited was 99 columns, the widest prose
line in a 190 KB file whose prose wraps at 79; the widest before was 84. Rewrapped.
The coverage-gate paragraph also claimed the floor test refuses "any omit", which
stopped being true two commits ago, and understated what is now enforced.

* test(quickadapter): name a fractal-dimension test after what it asserts, align docstrings

`test_a_framed_gap_stops_the_fractal_dimension_at_the_window_edge` was named for
a gap and a window edge its fixture cannot contain — it feeds a constant frame, so
the only branch it reaches is the zero half-range short-circuit. The name read as
if the early return were covered from a second direction, which it is not.

Renamed to the assertion it actually makes: a constant window yields a FINITE
fractal dimension rather than log(0) and a -inf alpha. That guard is real and
distinct from the neighbouring constant-series case, so the test stays; the
overlapping tail assertion went, and a NaN check was added to match the name.

Twenty of twenty-eight test modules carried the "; requires the Freqtrade QA
image." clause and eight did not, which is the kind of drift the repo rules call
incoherent. All twenty-eight now carry it.

Left alone on purpose: `test_prediction_history.py` and
`test_regressor_prediction_history.py` are near-synonym names, and the new one is
the better of the two — the pre-existing module is what reads as the odd one out.
Renaming it would break a stable public path for a naming preference.

* test(quickadapter): guard the positional pivot contract at the strategy level

The previous commit proved the fix by hand — driving the real strategy path with
a DatetimeIndex frame and with a shifted integer index, both of which now give
184 labels and 23 non-zero extrema weights. That evidence was not kept, and the
suite still could not catch a regression: every fixture in the suite builds `date`
as a COLUMN and leaves a zero-based RangeIndex, which is exactly the shape that
hides the bug.

This runs `set_freqai_targets` on the real strategy with the index set both ways
and requires identical labels and identical weights, plus the absolute counts so
that a column of zeros or an all-positive column cannot satisfy it. Note the
weight count is 195, not 23: 23 of those sit on pivots and the rest are the
non-causal baseline fill.

Verified: restoring `.loc` at Utils.py:846 turns eight tests red, and appending
`df.index[pos]` turns five red.

* test(quickadapter): close the gaps the second review tour found

Every item below was confirmed by mutation before and after. Three were defects in
the previous tour's own work, and are called out as such.

The production default had no coverage at all. `_make_train_test_split_datasets`
is what a stock configuration runs — `causal_mode` defaults to True, the split
method defaults to `train_test_split`, `config-template.json` ships
`"test_size": 0.333` — and no test called it. Every split test drove
`_add_validation_split` or `test_size: 0` instead, so the whole causal purge block
ran zero times and both of its boundary comparisons were unguarded: relaxing `<`
to `<=` on the label horizon, or on the label's known_at position, kept the suite
green. Four cases now drive the outer split and pin each boundary from both sides,
including the counterpart with `causal_mode` off so they cannot pass for the wrong
reason. Both off-by-one mutants are killed.

The monotonic pivot watermark had an untested term. `add_pivot` takes the max of
four, and dropping `confirmed_at_pos` left the suite green while publishing labels
before their confirmation candle on 41 of 1500 seeded frames, by up to 96 candles.
The existing case cannot tell the term from `natr_warmup_end_pos` because on its
fixture the two coincide. A flat stretch followed by a trend separates them.

`compose_label_lookahead` had no test whatsoever. Its right-edge clamp was a row
wide in the wrong direction without anything noticing.

Three of these are mine. `test_the_workflow_propagates_the_coverage_result` scoped
its `exit $status` search on an unindented `- name:`, which never occurs in the
file, so it ran to end-of-file and a decoy token anywhere later satisfied it; it
now scopes on the step's own indent. It also never checked that the workflow points
at `.coveragerc` at all, so deleting one `--env` line left coverage with no
configuration, measuring the standard library and exiting 0. And the pragma guard's
comment claimed the tree was free of coverage's default exclusions when
`if TYPE_CHECKING:` is present in Utils.py; the guard now counts all three
DEFAULT_EXCLUDE patterns and pins the legitimate one.

Also: `include` was missing from the exclusion-key list, so one `[report] include`
line cut the denominator from 6217 statements to 2717 silently. `precision` was
checked for presence but never for value, and coverage's predicate is
`round(total, precision) < fail_under`, so `precision = 0` let a run half a point
below the floor pass.

And four tests asserted nothing: a tautological `assertLess` over a slice that
made it true by construction, now a behavioural test that the renamed section
reaches its keys through the second hop; a round-trip-only flat-config test that
any invertible pipeline satisfies, now asserting the resolved standardization and
normalization; a memoisation test that could not see a leak because each probe
rotates the timestamp and wipes the cache, now asserting after every refusal; and
`assertIsNotNone(import_module(...))`, which never returns None, deleted.

* docs(quickadapter): correct the coverage-gate citations and complete the guard list

Two comments pointed at README.md:528 for the "raise only, never lower" policy,
which is on line 532; 528 is the start of the surrounding paragraph. A line-number
citation into a file the same change edits is worth getting right.

The README paragraph listing what the floor test refuses named nine of the guards
and omitted three, one of which matters: it never said that `fail_under` drifting
into the inert `[run]` section is refused, so a maintainer reading it could "fix" a
red build by moving the line and believe the gate still holds. The list now covers
all sixteen, including the ones added since it was written.

Recorded coverage moves to 69.6 percent (floor 67, gate exit 0), from the same
configuration as the 69.3 percent it replaces.

* docs(quickadapter): correct the documented failure mode of an inline env prefix

README.md:518 said that prefixing `COVERAGE_RCFILE` and `COVERAGE_FILE` on a
single command makes `coverage report` "read no configuration and exit 0 while
measuring nothing". Reproduced in the QA image on a clean checkout: it prints
`No data to report.` and exits 1. The second command looks for the data file in
its default location, finds none, and fails.

The advice to export both variables is right, and the failure is loud rather than
silent — it cannot ship a green gate on a measurement that never happened — so the
text now says that instead. What it cannot do is send someone looking for a silent
zero exit that does not happen.

* test(quickadapter): pin how the two causal masks combine, and the flat-candle refusal

Both survivors were in tests added two commits ago, found by the third tour.

The causal purge applies two masks and the first version of that class proved
each one separately while never proving they combine: the horizon case carried no
availability column, the availability case used a zero horizon, so `keep_mask &=`
never ran with both constraints live. Rewriting the conjunction as an assignment
discards the horizon mask and kept the suite green. The new case exercises both
directions, because either alone is satisfied by a mask that simply overwrites the
other — the horizon removing rows the availability mask keeps, and the availability
mask removing a row the horizon kept.

I first wrote that case backwards, choosing a row the horizon mask keeps and
asserting it was purged. The reviewer caught it before it was committed. The
lesson is recorded in the test: pinning constraints one at a time says nothing
about how they combine.

The flat-candle threshold refusal was never checked against `_candle_threshold_cache`.
Every later probe rotates the timestamp on purpose, so a refusal that memoised was
wiped by the next invalidation and unobservable at the end of the test. It is now
asserted while that frame's signature is still live. Verified: inserting a memo in
that branch — with either `0.0` or `np.nan` — turns two subtests red, where before
both survived.

* test(quickadapter): reach the validation modes and weighting metrics that no test did

A block that never executes is worse than a weak assertion, because it reads as
coverage. The non-finite scalar case put the warn and none modes inside the same
`with` block as the raising call, so the raise on the first line unwound the block
and neither branch ever ran. The test name claimed all three modes. Split into
three, and the two mutants the reviewer measured — removing the non-finite
warning, and turning its `return None` into `return 0.0` — now both fail.

The per-metric weighting strategies had no test at all. Only `uniform` was ever
selected, so `weights = np.asarray(metrics[strategy])` never ran and replacing it
with `np.ones` left the suite green while every configured swing importance became
uniform. The amplitude strategy is reachable from a real config and is the
alternative the shipped template documents. The new cases pin the metric on the
pivots and the ratio the training-sample-weight consumer actually sees, since
the mean and positivity hold either way.

The reversal decay test asserted only that the decayed maximum is at least the
decayed minimum. That ordering follows from the earlier rejection of `lo > hi`,
so it holds for any input and says nothing about the decay; dropping the decay on
the maximum, or squaring the factor, both survived. The historical maximum is now
pinned at [1.0, 0.5, 0.25].

The eval weight test asserted a unit mean, which a binarisation of the label
weights also satisfies. It now pins the ratios.

And the migration table has two-hop chains that a structural "a chain exists"
assertion plus one behavioural chain did not cover: `threshold_outlier` reaching
`outlier_quantile`, `extrema_fraction` reaching `keep_fraction`, and a key under
the renamed `predictions_extrema` section reaching `threshold_method`. Sorting the
table by old path — which strands all three — now fails.

* test(quickadapter): guard the CI conditions that dispatch the gate, and a logger leak

Three passes closed the ways the coverage CONFIGURATION could be made inert. This
closes the level above it: whether the step runs at all. Flipping the QuickAdapter
matrix entry to `coverage: false`, or the step's own `if:` to a constant, left every
other test green while CI stopped running the gate — and a job that skips the
coverage step is indistinguishable from a green one. The matrix and the step
condition are now parsed rather than grepped, so an entry that loses its key, or an
`if:` that stops referencing the matrix, fails here too. All three mutants die.

`recorded_warnings` set its logger to NOTSET and never restored the level. Two
distinct problems, and only the second is fixed by restoring it. NOTSET makes the
logger inherit the root level, and a record the logger has already filtered out
never reaches a private handler: with the root at ERROR, all 45 cases in that file
failed on empty sinks. The capture level is now set explicitly, and the previous
level is restored on exit so later tests do not inherit it. Verified: 89 failures
before, 0 after.

* test(quickadapter): cover the chronological split, whose whole body never ran

`_make_timeseries_split_datasets` is reachable through the documented
`data_split_parameters.method='timeseries_split'`, and coverage reported 0 of its
62 statements executed. Its own availability comparison at
QuickAdapterRegressorV3.py:3108 could go from strict to inclusive, or be deleted,
with the suite green — a training row whose label is first knowable on the first
test row would be admitted into the fit.

It is a sibling of the block already pinned, not a subclass of it. Inheriting
looked like the economical choice and was wrong: the parent's expectations are
written around a ten-row test tail starting at position 30, where this last fold
is five rows starting at 35, and five inherited tests failed asserting the wrong
geometry. Each carries its own fixtures now, and the docstring records why.

The boundary needed measuring rather than assuming. `gap=2` removes the two rows
before the test fold from the training set BEFORE the availability comparison, so
the last row that comparison can act on is 32, not 34: measured train ends at 32
and test is 35..39. A lookahead placed on 33 or 34 is indistinguishable from no
lookahead at all. The cases now walk the last train row across availability at
32, 33 and 34 — all strictly before the cutoff — and exactly at 35, which is
purged. Verified: relaxing `<` to `<=` turns two tests red.

Coverage moves to 70.5 percent (floor 67, gate exit 0).

* test(quickadapter): pin the requested threshold method and the take-profit lower bound

Two survivors from the third tour, both in tests written before this review.

A range check cannot tell delegation from substitution. Every skimage threshold
method lands inside [-1, 1] whatever it computes, so forcing `threshold_func` to
Otsu inside the consumer left the whole suite green while `mean` silently became
Otsu's -0.9485 instead of -0.55 — an entry gate using a method the config never
asked for. The full tuple is now pinned per method. The whole table, not just the
differing entry: on this fixture `yen` coincides with `otsu` and `li` with `mean`,
so pinning a single method would leave the substitution open from the other side.
Verified: rerouting every method to Otsu, and rerouting only `yen` to `mean`, each
turn six subtests red.

The closed-interval case for the exit distances called only the stoploss helper,
so the take-profit endpoints were untested. Relaxing its valid condition to a
strict lower bound, or returning None for a zero fraction before the range check,
each left the suite green. Both helpers are now called at both endpoints. The upper
endpoint was already covered by sibling tests; the lower one is only observable
here, and the strict-lower mutant now dies.

11 days agotest(quickadapter): cover the strategy business logic and gate coverage at 67% (...
Jérôme Benoit [Thu, 1 Oct 2026 11:00:08 +0000 (13:00 +0200)] 
test(quickadapter): cover the strategy business logic and gate coverage at 67% (#279)

Adds a unit-test suite for the QuickAdapter strategy covering the testable business logic of all five source modules, and an absolute coverage gate enforced in CI.

Branch coverage of quickadapter/user_data goes from 23.9% to 69.2% under the shipped configuration, with an enforced floor of 67%. The before column is main's single test file run against the same config, so both share one denominator; without include_namespace_packages the unexecuted trees vanish from the report entirely and the gate measures only what happened to be imported.

Layout is one file per business concern over a shared qa_support helper. The suite contract derives every production module name from the tree and refuses a second import identity for a strategies module, because a dotted import would shadow the bare one production uses and carry its own copy of the module globals.

Each file was checked against injected mutants of its own source; an independent adversarial review found four survivors the per-commit notes had mislabelled as equivalent or dead, all now covered and verified in both directions.

12 days agofix(reforcexy): import standalone cloudpickle (#278)
Jérôme Benoit [Tue, 29 Sep 2026 23:04:04 +0000 (01:04 +0200)] 
fix(reforcexy): import standalone cloudpickle (#278)

* fix(reforcexy): import standalone cloudpickle

* docs: remove redundant cloudpickle note

2 weeks agochore(deps): lock file maintenance (#272)
renovate[bot] [Fri, 25 Sep 2026 13:46:47 +0000 (15:46 +0200)] 
chore(deps): lock file maintenance (#272)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2 weeks agofix(freqai): prevent future continuations and align RL trade accounting (#275)
Jérôme Benoit [Wed, 23 Sep 2026 23:57:08 +0000 (01:57 +0200)] 
fix(freqai): prevent future continuations and align RL trade accounting (#275)

* fix(freqai): bound continuation and correct RL accounting

* fix(reforcexy): preserve second-level continuation and fee extrema

* docs: reconcile strategy guides and invariant ownership

* docs: complete development table of contents

* fix(reward): preserve profitable synthetic paths at high fees

* style(docs): format Markdown with Prettier

* fix: harden reward analysis and runtime regressions

* docs: describe ReforceXY leverage bounds

* refactor(ci): consolidate strategy QA jobs

* docs: explain strategy PYTHONPATH in QA

* fix(reforcexy): report marginal unmasked action probabilities

* style(docs): apply prettier to coverage mapping

* fix(reforcexy): clarify diagnostics and deduplicate leverage warnings

* fix(ci): restore Ruff import grouping

* chore(quickadapter): bump version to 3.13.0-rc.12

2 weeks agofix(freqai): preserve restart state correctly
Jérôme Benoit [Wed, 23 Sep 2026 13:10:53 +0000 (15:10 +0200)] 
fix(freqai): preserve restart state correctly

Squash merge PR #274 after all quality and CodeQL checks passed.

2 weeks agochore(deps): update astral-sh/setup-uv action to v10.2.0 (#273)
renovate[bot] [Tue, 22 Sep 2026 11:32:12 +0000 (13:32 +0200)] 
chore(deps): update astral-sh/setup-uv action to v10.2.0 (#273)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
3 weeks agochore(deps): update dependency scipy-stubs to v1.18.1.1 (#271)
renovate[bot] [Mon, 21 Sep 2026 12:14:14 +0000 (14:14 +0200)] 
chore(deps): update dependency scipy-stubs to v1.18.1.1 (#271)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
3 weeks agodocs(reforcexy): align test manifest formatting
Jérôme Benoit [Sun, 20 Sep 2026 14:36:10 +0000 (16:36 +0200)] 
docs(reforcexy): align test manifest formatting

3 weeks agofix(reforcexy): correct audit findings (#270)
Jérôme Benoit [Sun, 20 Sep 2026 14:31:38 +0000 (16:31 +0200)] 
fix(reforcexy): correct audit findings (#270)

* fix(reforcexy): correct audit findings

* fix(reforcexy): preserve invalid PBRS provenance

* fix(reforcexy): warn on invalid exit modes

3 weeks agodocs(reforcexy): align manifest table formatting
Jérôme Benoit [Sun, 20 Sep 2026 11:25:15 +0000 (13:25 +0200)] 
docs(reforcexy): align manifest table formatting

3 weeks agofix(reforcexy): enforce analysis provenance contracts (#269)
Jérôme Benoit [Sun, 20 Sep 2026 11:19:06 +0000 (13:19 +0200)] 
fix(reforcexy): enforce analysis provenance contracts (#269)

* fix(reforcexy): enforce analysis provenance contracts

* refactor(reforcexy): harmonize manifest parameter domains

3 weeks agostyle: clarify QuickAdapter smoothing comments
Jérôme Benoit [Sat, 19 Sep 2026 23:36:40 +0000 (01:36 +0200)] 
style: clarify QuickAdapter smoothing comments

3 weeks agorefactor: centralize QuickAdapter support policies
Jérôme Benoit [Sat, 19 Sep 2026 23:09:41 +0000 (01:09 +0200)] 
refactor: centralize QuickAdapter support policies

3 weeks agorefactor: document QuickAdapter option indexes
Jérôme Benoit [Sat, 19 Sep 2026 22:50:23 +0000 (00:50 +0200)] 
refactor: document QuickAdapter option indexes

* refactor: document QuickAdapter option indexes

* style: harmonize QuickAdapter index comments

* fix: remove index annotation from label docstring

* fix: remove duplicate QuickAdapter index comments

* fix: remove repeated smoothing annotation

* fix: remove duplicate wrap annotation

* refactor: deduplicate QuickAdapter label annotations

* fix: complete QuickAdapter index annotations

3 weeks agochore: update Serena project configuration
Jérôme Benoit [Sat, 19 Sep 2026 21:41:11 +0000 (23:41 +0200)] 
chore: update Serena project configuration

3 weeks agofix(reforcexy): align training, inference and reward analysis contracts
Jérôme Benoit [Sat, 19 Sep 2026 21:36:04 +0000 (23:36 +0200)] 
fix(reforcexy): align training, inference and reward analysis contracts

* fix(reforcexy): account for portfolio transition returns

* fix(reforcexy): align training inference and reward analysis contracts

* fix(reforcexy): resolve statistical reporting and type-narrowing findings

- BH correction filters undefined p-values out of the finite family;
  non-applicable tests keep significant flags None and NaN adjusted values.
- Bootstrap percentile CIs validate finite ordered bounds only; constants
  keep exact degenerate bounds in both diagnostic modes without widening.
- Distribution diagnostics mark constant columns as N/A (skewness/kurtosis
  None, normality tests and Q-Q omitted) and reject fabricated fallbacks.
- Explicit independent_observations contract across hypothesis tests,
  bootstrap, shift metrics and report sections; trajectory report is
  descriptive (no inferential p-values/CIs unless declared independent).
- Distribution shift computes exact ks_2samp statistics on raw values;
  KS p-value reported only for independent observations.
- PBRS certification requires ordered-episode evidence: local identity
  F = gamma*next - prev, continuity, discounted boundary residual;
  non-canonical configurations are never certified from raw sums.
- Report renders actual bootstrap resample count, independent sections
  and N/A constant rows; transforms rename bounded asinh to softsign_sqrt
  without aliases; loss-potential bound docs corrected.
- Narrow Optional callback/env/attr types and force float arrays so the
  BasedPyright snapshot holds 142 diagnostics; ruff clean.

* docs(reforcexy): align HPO continuation contract with merged behavior

* fix(reforcexy): harden inference contracts and reward accounting

Isolate deployed policy on continuation, account open positions at
terminal liquidation, guarantee Python bool terminated, handle OHLC
drop without holdout, prune no-learning DQN trials, preserve replay
buffer and chronology across restarts and splits, propagate complete
observation validity, reset temporal caches on discontinuities, fix
terminal neutral potential in analysis, align history events with
transitions, enforce loss amplification threshold, honor simulated
unrealized PnL, keep idle duration past threshold, reject missing
real_episodes files and unknown overrides. Transport inference dates
through dk.data_dictionary with explicit length alignment guard.

* fix(reforcexy): address review follow-ups

* refactor(reforcexy): consolidate constants and helpers

* fix(reforcexy): close review follow-ups

* fix(reforcexy): address final review findings

* fix(reforcexy): apply final review corrections

* fix(reforcexy): harmonize diagnostic messages

* fix(reforcexy): clarify remaining diagnostics

* fix(reforcexy): align simulated pnl trajectories

* refactor(reforcexy): align replay pair terminology

* refactor(reforcexy): require explicit entry pnl

* fix: remove ReforceXY legacy compatibility paths

* fix: address PBRS review findings

3 weeks agostyle(reforcexy): harmonize all log/error message prefixes to '<Component> [<scope...
Jérôme Benoit [Fri, 18 Sep 2026 16:32:23 +0000 (18:32 +0200)] 
style(reforcexy): harmonize all log/error message prefixes to '<Component> [<scope>]:' (#264)

Exhaustive audit of ReforceXY.py messages: 144/148 now follow the
dominant '<Component> [<scope>]:' convention.

Ported (QuickAdapter PR #261) messages reprefixed:
- Training [%s]: cannot continue / continuing deployment / RL
  preprocessing / model-on info log / warmup (Live [%s]:).
- Training banners converted to 'Training [%s]: starting|completed'
  (were asymmetric '----Starting/done training----' separators).

Pre-existing non-scoped messages reprefixed:
- Repair [global]: FreqAI prediction repair synchronous-drawer guard.
- Journal [%s]: corrupt-journal quarantine warning.
- Hyperopt [global]: best-params lock/path filesystem invariants.

Remaining 4 are by-design, not user-facing message-convention items:
- 3 internal 'persisted ...' ValueErrors are immediately re-raised by
  the conforming 'Training [%s]: cannot continue ...' DependencyException
  wrapper; prefixing them would double-scope the surfaced text.
- FileExistsError(path) carries no literal message.

Line-count neutral: BasedPyright ReforceXY snapshot unaffected.

3 weeks agofix(reforcexy): port applicable QuickAdapter PR #261 fixes (#263)
Jérôme Benoit [Fri, 18 Sep 2026 15:23:00 +0000 (17:23 +0200)] 
fix(reforcexy): port applicable QuickAdapter PR #261 fixes (#263)

* fix(reforcexy): port applicable QuickAdapter PR #261 fixes

- Align prediction history to requested candle dates and exact strategy
  indexes (recorded/produced provenance, dedupe, exact-index attach).
- Freeze deployment feature coordinates for continual learning with
  independent cache/disk state restoration and explicit rejection of
  legacy or incompatible artifacts.
- Keep raw OHLC environment prices out of feature preprocessing; cold
  HPO trials use fresh candidate pipelines while the final continuation
  reuses the frozen deployment pipeline on the raw split.
- Return the current finite evaluation best checkpoint (best_model.zip)
  with final-policy fallback.
- Optional per-pair produced-observation action statistics with session
  warmup, numeric coercion and population standard deviation.
- Refresh BasedPyright snapshot; 2 argument-type errors resolved.

* fix(reforcexy): align resumed gamma and invalidate incompatible HPO state

* docs(reforcexy): clarify continuation and objective compatibility

* docs(reforcexy): condense continuation guidance

* fix(reforcexy): preserve replay buffer on best checkpoint selection

* chore(reforcexy): refresh basedpyright snapshot positions

3 weeks agofix(quickadapter): resolve initial and comprehensive review findings (#261)
Jérôme Benoit [Fri, 18 Sep 2026 14:36:25 +0000 (16:36 +0200)] 
fix(quickadapter): resolve initial and comprehensive review findings (#261)

* fix(quickadapter): resolve six initial review findings

* fix(quickadapter): preserve nonnegative trade volatility

* fix(quickadapter): align prediction history and guard deployment state

* docs(quickadapter): align prose with current behavior

* chore(quickadapter): refresh basedpyright snapshot positions

* fix(quickadapter): remove unreleased selection schema increments

* docs(quickadapter): remove redundant versioning commentary

* docs(quickadapter): clarify schema version release boundary

* fix(quickadapter): slice label weights before normalization

* style(quickadapter): harmonize log message capitalization

* fix(quickadapter): validate original weights before projection fallback

* docs(quickadapter): clarify weight validation and normalization order

* chore(quickadapter): bump release candidate to 3.13.0-rc.10

3 weeks agoMerge pull request #260 from jerome-benoit/docs/quickadapter-backtest-protocol
Jérôme Benoit [Fri, 18 Sep 2026 11:59:41 +0000 (13:59 +0200)] 
Merge pull request #260 from jerome-benoit/docs/quickadapter-backtest-protocol

docs(quickadapter): clarify backtest evaluation protocol

3 weeks agodocs(quickadapter): correct Freqtrade 2026.8 source anchors
Jérôme Benoit [Fri, 18 Sep 2026 11:42:39 +0000 (13:42 +0200)] 
docs(quickadapter): correct Freqtrade 2026.8 source anchors

3 weeks agodocs(quickadapter): clarify backtest evaluation protocol
Jérôme Benoit [Thu, 17 Sep 2026 22:43:54 +0000 (00:43 +0200)] 
docs(quickadapter): clarify backtest evaluation protocol

3 weeks agoMerge pull request #259 from jerome-benoit/fix/forcexy-fit-live-predictions-validation
Jérôme Benoit [Thu, 17 Sep 2026 21:53:17 +0000 (23:53 +0200)] 
Merge pull request #259 from jerome-benoit/fix/forcexy-fit-live-predictions-validation

fix(reforcexy): validate optional prediction statistics window

3 weeks agoMerge branch 'main' into fix/forcexy-fit-live-predictions-validation
Jérôme Benoit [Thu, 17 Sep 2026 21:49:24 +0000 (23:49 +0200)] 
Merge branch 'main' into fix/forcexy-fit-live-predictions-validation

3 weeks agoMerge pull request #258 from jerome-benoit/fix/quickadapter-review-contracts
Jérôme Benoit [Thu, 17 Sep 2026 21:48:30 +0000 (23:48 +0200)] 
Merge pull request #258 from jerome-benoit/fix/quickadapter-review-contracts

fix(quickadapter): repair reviewed training and selection contracts

3 weeks agofix(reforcexy): validate optional prediction statistics window
Jérôme Benoit [Thu, 17 Sep 2026 21:32:32 +0000 (23:32 +0200)] 
fix(reforcexy): validate optional prediction statistics window

3 weeks agodocs(quickadapter): remove operational constraints section
Jérôme Benoit [Thu, 17 Sep 2026 21:00:42 +0000 (23:00 +0200)] 
docs(quickadapter): remove operational constraints section

3 weeks agodocs(quickadapter): clarify helper contracts and comments
Jérôme Benoit [Thu, 17 Sep 2026 20:55:36 +0000 (22:55 +0200)] 
docs(quickadapter): clarify helper contracts and comments

3 weeks agorefactor(quickadapter): canonicalize format_dict style literals
Jérôme Benoit [Thu, 17 Sep 2026 20:36:56 +0000 (22:36 +0200)] 
refactor(quickadapter): canonicalize format_dict style literals

3 weeks agorefactor(quickadapter): replace duplicated domain string literals with canonical...
Jérôme Benoit [Thu, 17 Sep 2026 20:23:14 +0000 (22:23 +0200)] 
refactor(quickadapter): replace duplicated domain string literals with canonical constants

3 weeks agofix(quickadapter): keep selection schema 3 within the pull request
Jérôme Benoit [Thu, 17 Sep 2026 19:37:39 +0000 (21:37 +0200)] 
fix(quickadapter): keep selection schema 3 within the pull request

3 weeks agodocs(quickadapter): document selection normalization bounds and schema
Jérôme Benoit [Thu, 17 Sep 2026 19:31:37 +0000 (21:31 +0200)] 
docs(quickadapter): document selection normalization bounds and schema

3 weeks agofix(quickadapter): harden selection normalization and config bounds
Jérôme Benoit [Thu, 17 Sep 2026 19:31:19 +0000 (21:31 +0200)] 
fix(quickadapter): harden selection normalization and config bounds

3 weeks agodocs(quickadapter): drop stale selection schema label
Jérôme Benoit [Thu, 17 Sep 2026 18:54:09 +0000 (20:54 +0200)] 
docs(quickadapter): drop stale selection schema label

3 weeks agofix(quickadapter): invalidate stale label selection state
Jérôme Benoit [Thu, 17 Sep 2026 18:44:00 +0000 (20:44 +0200)] 
fix(quickadapter): invalidate stale label selection state

3 weeks agofix(quickadapter): enforce reviewed training and trading contracts
Jérôme Benoit [Thu, 17 Sep 2026 18:06:55 +0000 (20:06 +0200)] 
fix(quickadapter): enforce reviewed training and trading contracts

3 weeks agofix(quickadapter): isolate fitted scalers before and after PCA
Jérôme Benoit [Thu, 17 Sep 2026 17:38:37 +0000 (19:38 +0200)] 
fix(quickadapter): isolate fitted scalers before and after PCA

3 weeks agofix(quickadapter): preserve zero power mean exponent in label selection
Jérôme Benoit [Thu, 17 Sep 2026 17:20:36 +0000 (19:20 +0200)] 
fix(quickadapter): preserve zero power mean exponent in label selection

_validate_power_mean_p returns 0.0 for p=0 (a finite float accepted by
README), and the falsy fallback silently rewrote it to 1.0, turning the
documented generalized-mean p=0 (geometric) into arithmetic. Use an
explicit None check for the fallback; map-based metrics are unchanged.

Verified in the QA image (Freqtrade 2026.8): p=0 now matches
geometric_mean selection (winner 1, identical distances), while p=None
keeps the arithmetic default.

3 weeks agoMerge pull request #257 from jerome-benoit/renovate/all-minor-patch
Jérôme Benoit [Wed, 16 Sep 2026 13:10:32 +0000 (15:10 +0200)] 
Merge pull request #257 from jerome-benoit/renovate/all-minor-patch

chore(deps): update dependency pandas-stubs to v3.0.5.260914

3 weeks agochore(basedpyright): drop resolved reward_space_analysis ExtensionArray.size diagnostic
Jérôme Benoit [Wed, 16 Sep 2026 13:03:45 +0000 (15:03 +0200)] 
chore(basedpyright): drop resolved reward_space_analysis ExtensionArray.size diagnostic

pandas-stubs 3.0.5.260914 resolves the previously-recorded
reportAttributeAccessIssue on ExtensionArray.size at
reward_space_analysis.py:2720, so the accepted ReforceXY snapshot no
longer matches current diagnostics. Remove the stale entry to unblock
the BasedPyright (ReforceXY) required check.

3 weeks agochore(deps): update dependency pandas-stubs to v3.0.5.260914
renovate[bot] [Tue, 15 Sep 2026 00:22:28 +0000 (00:22 +0000)] 
chore(deps): update dependency pandas-stubs to v3.0.5.260914

4 weeks agoMerge pull request #256 from jerome-benoit/fix/quickadapter-rpc-custom-data-cleanup
Jérôme Benoit [Sun, 13 Sep 2026 15:48:07 +0000 (17:48 +0200)] 
Merge pull request #256 from jerome-benoit/fix/quickadapter-rpc-custom-data-cleanup

fix(freqai): preserve prediction history and release annotation sessions

4 weeks agochore(quickadapter): bump version to 3.13.0-rc.9
Jérôme Benoit [Sun, 13 Sep 2026 15:37:02 +0000 (17:37 +0200)] 
chore(quickadapter): bump version to 3.13.0-rc.9

4 weeks agofix(freqai): align prediction repair across both models
Jérôme Benoit [Sun, 13 Sep 2026 15:17:32 +0000 (17:17 +0200)] 
fix(freqai): align prediction repair across both models

4 weeks agofix(quickadapter): preserve recorded predictions during history repair
Jérôme Benoit [Sun, 13 Sep 2026 15:03:12 +0000 (17:03 +0200)] 
fix(quickadapter): preserve recorded predictions during history repair

4 weeks agofix(quickadapter): release annotation RPC custom-data sessions
Jérôme Benoit [Sun, 13 Sep 2026 14:40:42 +0000 (16:40 +0200)] 
fix(quickadapter): release annotation RPC custom-data sessions

4 weeks agoMerge pull request #255 from jerome-benoit/renovate/all-minor-patch
Jérôme Benoit [Sat, 12 Sep 2026 12:10:57 +0000 (14:10 +0200)] 
Merge pull request #255 from jerome-benoit/renovate/all-minor-patch

chore(deps): update dependency matplotlib to v3.11.2

4 weeks agochore(deps): update dependency matplotlib to v3.11.2
renovate[bot] [Sat, 12 Sep 2026 02:09:48 +0000 (02:09 +0000)] 
chore(deps): update dependency matplotlib to v3.11.2

4 weeks agoMerge pull request #254 from jerome-benoit/feat/renovate-basedpyright-snapshot-version
Jérôme Benoit [Fri, 11 Sep 2026 16:56:29 +0000 (18:56 +0200)] 
Merge pull request #254 from jerome-benoit/feat/renovate-basedpyright-snapshot-version

fix(qa): tolerate tool version drift in snapshot comparison

4 weeks agofix(qa): do not commit check tests
Jérôme Benoit [Fri, 11 Sep 2026 16:29:50 +0000 (18:29 +0200)] 
fix(qa): do not commit check tests

4 weeks agoMerge branch 'main' into feat/renovate-basedpyright-snapshot-version
Jérôme Benoit [Fri, 11 Sep 2026 16:19:47 +0000 (18:19 +0200)] 
Merge branch 'main' into feat/renovate-basedpyright-snapshot-version

4 weeks agofix(qa): tolerate tool version drift in snapshot comparison
Jérôme Benoit [Fri, 11 Sep 2026 16:13:16 +0000 (18:13 +0200)] 
fix(qa): tolerate tool version drift in snapshot comparison

Compare snapshots with basedpyrightVersion normalized out: a tool upgrade that changes no diagnostics no longer fails the check. The recorded version stays in snapshots and --write output as provenance, is still validated nonempty on both sides, still appears in the failure diff, and both versions are reported in the success message. Diagnostics, file inventory and schema stay byte-exact, so changed output still fails closed. Covers scripts/test_check_basedpyright.py (stdlib unittest): version-only drift matches, diagnostic change mismatches, empty version and missing key rejected.

4 weeks agofix(renovate): anchor managed version values to digit-start
Jérôme Benoit [Fri, 11 Sep 2026 16:13:11 +0000 (18:13 +0200)] 
fix(renovate): anchor managed version values to digit-start

Require managed version values to start with a digit in both Dockerfile ARG and compose matchStrings. Kills the false-positive class where any *_version key with a non-version scalar (e.g. latest, stable) becomes a pypi lookup. Verified no-op on the current corpus: identical 26-capture set before/after.

4 weeks agoMerge pull request #253 from jerome-benoit/renovate/all-minor-patch
Jérôme Benoit [Fri, 11 Sep 2026 14:27:57 +0000 (16:27 +0200)] 
Merge pull request #253 from jerome-benoit/renovate/all-minor-patch

chore(deps): update all non-major dependencies

4 weeks agochore(qa): refresh BasedPyright snapshots for 1.40.1
Jérôme Benoit [Fri, 11 Sep 2026 14:16:24 +0000 (16:16 +0200)] 
chore(qa): refresh BasedPyright snapshots for 1.40.1

Repin accepted snapshots for the basedpyright 1.40.0 -> 1.40.1 bump. Diagnostics are unchanged in both projects (242 quickadapter / 159 reforcexy) — only the version marker moves. Snapshots generated with the same --pull docker build as CI.

4 weeks agochore(deps): update all non-major dependencies
renovate[bot] [Fri, 11 Sep 2026 02:58:56 +0000 (02:58 +0000)] 
chore(deps): update all non-major dependencies

4 weeks agoMerge pull request #245 from jerome-benoit/renovate/lock-file-maintenance
Jérôme Benoit [Wed, 9 Sep 2026 13:15:40 +0000 (15:15 +0200)] 
Merge pull request #245 from jerome-benoit/renovate/lock-file-maintenance

chore(deps): lock file maintenance

4 weeks agoMerge branch 'main' into renovate/lock-file-maintenance
Jérôme Benoit [Wed, 9 Sep 2026 13:12:02 +0000 (15:12 +0200)] 
Merge branch 'main' into renovate/lock-file-maintenance

4 weeks agoMerge pull request #252 from jerome-benoit/renovate/all-minor-patch
Jérôme Benoit [Wed, 9 Sep 2026 13:11:46 +0000 (15:11 +0200)] 
Merge pull request #252 from jerome-benoit/renovate/all-minor-patch

chore(deps): update dependency optunahub to v0.5.0

4 weeks agochore(deps): lock file maintenance
renovate[bot] [Wed, 9 Sep 2026 13:08:08 +0000 (13:08 +0000)] 
chore(deps): lock file maintenance

4 weeks agochore(deps): update dependency optunahub to v0.5.0
renovate[bot] [Wed, 9 Sep 2026 13:07:42 +0000 (13:07 +0000)] 
chore(deps): update dependency optunahub to v0.5.0

4 weeks agoMerge pull request #251 from jerome-benoit/style/docker-blank-line
Jérôme Benoit [Wed, 9 Sep 2026 13:07:23 +0000 (15:07 +0200)] 
Merge pull request #251 from jerome-benoit/style/docker-blank-line

style(docker): keep blank line after build args

4 weeks agostyle(docker): keep blank line after build args
Jérôme Benoit [Wed, 9 Sep 2026 13:02:17 +0000 (15:02 +0200)] 
style(docker): keep blank line after build args

4 weeks agoMerge pull request #249 from jerome-benoit/feat/lightgbm-install-version
Jérôme Benoit [Wed, 9 Sep 2026 13:00:03 +0000 (15:00 +0200)] 
Merge pull request #249 from jerome-benoit/feat/lightgbm-install-version

feat(docker): manage lightgbm installation and version

4 weeks agofeat(docker): manage lightgbm installation and version
Jérôme Benoit [Wed, 9 Sep 2026 12:44:02 +0000 (14:44 +0200)] 
feat(docker): manage lightgbm installation and version

4 weeks agoMerge branch 'main' into feat/xgboost-install-version
Jérôme Benoit [Wed, 9 Sep 2026 12:42:19 +0000 (14:42 +0200)] 
Merge branch 'main' into feat/xgboost-install-version

4 weeks agofeat(docker): manage xgboost installation and version
Jérôme Benoit [Wed, 9 Sep 2026 12:30:51 +0000 (14:30 +0200)] 
feat(docker): manage xgboost installation and version

Pin xgboost as a managed build dependency, harmonized with the existing catboost/ngboost pattern: ARG xgboost_version in quickadapter/Dockerfile, matching build arg in docker-compose.yml, and automatic updates via the existing renovate custom regex manager (pypi datasource).

The freqtrade stable_freqai base image ships xgboost 3.2.0; this pins 3.4.1 so the QA and production images track the latest release independently of base image lag. ReforceXY is untouched: it does not use xgboost. BasedPyright snapshots unchanged (242/159 diagnostics verified with the rebuilt QA images).

4 weeks agoMerge pull request #247 from jerome-benoit/renovate/optuna-5.x
Jérôme Benoit [Wed, 9 Sep 2026 12:23:15 +0000 (14:23 +0200)] 
Merge pull request #247 from jerome-benoit/renovate/optuna-5.x

chore(deps): update dependency optuna to v5