Change the timezone according to your location in
docker-compose.yml.
From the repository root, configure, build and start the QuickAdapter container:
cd quickadapter
cp user_data/config-template.json user_data/config.jsonAdapt the configuration to your needs: edit user_data/config.json to set your
exchange API keys and tune the freqai section.
The API server is disabled by default. Before enabling it, replace its username, password, JWT secret and WebSocket token: the template ships public placeholders that provide no protection until changed. Keep the Compose port bound to localhost unless access is protected by a VPN or SSH tunnel.
Then build and start the container:
docker compose up -d --buildThe build intentionally follows Freqtrade's current stable_freqai image and
resolves some dependencies at build time. Record the resolved image digest and
dependency versions for reproducible evaluations, as required by the protocol
below.
| Path | Runtime fallback | Type / Range | Description |
|---|---|---|---|
| Protections | |||
| custom_protections.trade_duration_candles | 72 | int >= 1 | Estimated trade duration in candles. Scales protections stop duration candles and trade limit. |
| custom_protections.lookback_period_fraction | 0.5 | float (0,1] | Fraction of Freqtrade's fit_live_predictions_candles used to calculate lookback_period_candles for MaxDrawdown and StoplossGuard protections. |
| custom_protections.cooldown.enabled | true | bool | Enable/disable CooldownPeriod protection. |
| custom_protections.cooldown.stop_duration_candles | 4 | int >= 1 | Number of candles to wait before allowing new trades after a trade is closed. |
| custom_protections.drawdown.enabled | true | bool | Enable/disable MaxDrawdown protection. |
| custom_protections.drawdown.max_allowed_drawdown | 0.2 | float (0,1) | Maximum allowed drawdown. |
| custom_protections.stoploss.enabled | true | bool | Enable/disable StoplossGuard protection. |
| Leverage | |||
| leverage | proposed_leverage |
float [1.0, max_leverage] | Leverage. Fallback to proposed_leverage for the pair. |
| Exit pricing | |||
| exit_pricing.trade_natr_method | moving_average |
enum {moving_average,quantile_interpolation,weighted_average} |
Trade NATR (Normalized Average True Range) aggregation method used to derive stoploss and take-profit distances. |
| exit_pricing.final_take_profit_retracement_fraction | 0.25 | float (0,1] | Fraction of the final take-profit target distance used as the frozen trailing retracement distance after the final target arms the exit. The final exit tracks the best subsequent per-candle rate and exits only after this material adverse move; elapsed stagnation alone does not exit. Plot annotations show only the current trail boundary from the candle that established it; earlier boundaries are not retained. |
| Reversal confirmation | |||
| reversal_confirmation.lookback_period_candles | 0 | int >= 0 | Prior confirming candles; 0 = none. With confirmation enabled, unmeasurable history rejects entries, while a valid current exit may still reduce exposure. |
| reversal_confirmation.decay_fraction | 0.5 | float (0,1] | Geometric per-candle volatility adjusted reversal threshold relaxation factor. |
| reversal_confirmation.min_natr_multiplier_fraction | 0.0095 | float [0,1] | Lower bound fraction (< upper bound) for volatility adjusted reversal threshold. |
| reversal_confirmation.max_natr_multiplier_fraction | 0.0125 | float [0,1] | Upper bound fraction (> lower bound) for volatility adjusted reversal threshold. |
| Regressor model | |||
| freqai.regressor | xgboost |
enum {xgboost,lightgbm,histgradientboostingregressor,ngboost,catboost} |
Machine learning regressor algorithm. |
| freqai.continual_learning | false | bool | Continue XGBoost, LightGBM, or CPU CatBoost training from the previously deployed model, so its booster grows at every retrain; delete trained models to reset. Under test_size two-stage selection, HPO and the pre-refit selection model cold-start and only the final refit continues, growing by the selection model's round count (see test_size). GPU CatBoost and other regressors cold-start instead. |
| Model training parameters | |||
| freqai.model_training_parameters.gpu_vram_gb | 80 | int > 0 | Available GPU VRAM (GB) for CatBoost, not total. Any positive value is floored to the nearest supported tier <= value (tiers 8, 10, 12, 16, 24, 32, 40, 48, 64, 80; values below 8 use tier 8). Constrains depth, border_count, and max_ctr_complexity ranges. |
| Data split parameters | |||
| freqai.data_split_parameters.method | train_test_split |
enum {train_test_split,timeseries_split} |
Data splitting strategy. train_test_split for sequential split, timeseries_split for chronological split with configurable gap. |
| freqai.data_split_parameters.test_size | 0.1 | float [0,1) | int >= 0 | null | Outer holdout size; 0 disables the holdout (single-stage fit, train_test_split only). The same parameter reserves the chronological tail of the remaining training rows as inner validation for HPO and early stopping; a fractional value is relative to those remaining rows, not the original window. The holdout is predicted once and reported as weighted holdout_rmse in the original label scale; it measures the cold-started pre-refit selection model, not the refitted deployed model. null (sklearn dynamic sizing) applies only to timeseries_split; train_test_split requires a float or int; inner validation then falls back to 0.1. |
| freqai.data_split_parameters.n_splits | 5 | int >= 2 | Controls train/test proportions for timeseries_split (higher = larger train set). |
| freqai.data_split_parameters.gap | 0 | int >= 0 | Samples to exclude between train/test for timeseries_split. 0 auto-derives the gap (source and lower-bound rule depend on causal_mode; see causal_mode). Not used by train_test_split. |
| freqai.data_split_parameters.max_train_size | null | int >= 1 | null | Maximum training set size for timeseries_split. When set, creates a sliding window instead of expanding train set. null = no limit. |
| Label smoothing | |||
| freqai.label_smoothing.method | gaussian |
enum {none,gaussian,kaiser,kaiser_bessel_derived,triang,smm,sma,savgol,gaussian_filter1d} |
Label smoothing method (smm=median, sma=mean, savgol=Savitzky–Golay). |
| freqai.label_smoothing.window_candles | 5 | int >= 1 | Requested smoothing window in candles. Runtime raises values below 3; Gaussian, Kaiser, triangular, SMM and SMA use the next odd length, kaiser_bessel_derived uses the next even length, and savgol uses an odd length greater than polyorder. none does not smooth. For gaussian_filter1d, this value only gates series shorter than the requested window; sigma defines the kernel. |
| freqai.label_smoothing.beta | 8.0 | float > 0 | Shape parameter for kaiser and kaiser_bessel_derived kernels. |
| freqai.label_smoothing.polyorder | 3 | int >= 0 | Polynomial order for savgol smoothing. |
| freqai.label_smoothing.mode | mirror |
savgol: enum {mirror,constant,nearest,wrap,interp}; gaussian_filter1d: enum {mirror,constant,nearest,wrap} |
Boundary mode for savgol and gaussian_filter1d; ignored otherwise. |
| freqai.label_smoothing.sigma | 1.0 | float > 0 | Gaussian sigma for gaussian_filter1d smoothing. |
| Label weighting | |||
| freqai.label_weighting.strategy | none |
enum {none,uniform,amplitude,amplitude_threshold_ratio,volume_rate,speed,efficiency_ratio,volume_weighted_efficiency_ratio,combined} |
Label weighting metric: none (none), uniform unit weight on every detected pivot (uniform), swing amplitude (amplitude), swing amplitude / median volatility-threshold ratio (amplitude_threshold_ratio), swing volume per candle (volume_rate), swing speed (speed), swing efficiency ratio (efficiency_ratio), swing volume-weighted efficiency ratio (volume_weighted_efficiency_ratio), or combined metrics aggregation (combined). Switching between none and any other strategy requires deleting trained models to realign training emphasis. |
| freqai.label_weighting.metric_coefficients | {} | dict[str, finite float > 0] | Per-metric coefficients for combined strategy. Keys: amplitude, amplitude_threshold_ratio, volume_rate, speed, efficiency_ratio, volume_weighted_efficiency_ratio. Invalid entries are ignored; when none remain, all metrics are selected with coefficient 1.0. |
| freqai.label_weighting.aggregation | arithmetic_mean |
enum {arithmetic_mean,geometric_mean,harmonic_mean,quadratic_mean,weighted_median,softmax} |
Metric aggregation method for combined strategy. arithmetic_mean=(Σ(w·m)/Σ(w)), geometric_mean=(∏(m^w))^(1/Σw), harmonic_mean=Σ(w)/(Σ(w/m)), quadratic_mean=(Σ(w·m²)/Σ(w))^(1/2), weighted_median=Q₀.₅(m,w), softmax=Σ(m·s_i) where s_i=w_i·exp(m_i/T)/Σ(w_j·exp(m_j/T)). |
| freqai.label_weighting.softmax_temperature | 1.0 | float > 0 | Temperature T for softmax aggregation, controls distribution sharpness. |
| freqai.label_weighting.fill_method | zero |
enum {zero,epsilon,gaussian,epsilon_gaussian} |
Off-pivot weighting scheme. zero hard-zeros off-pivot rows; epsilon applies the epsilon floor fill_epsilon * <fill_epsilon_baseline>(pivot_weights); gaussian applies per-pivot Gaussian bumps; epsilon_gaussian sums the epsilon floor and the gaussian bumps. Pivot rows take the max of their raw weight and the off-pivot field at their index (no-op for zero). Under causal_mode=true the epsilon baseline is computed causally (see causal_mode). Switching away from zero may require retuning tree-leaf regularization (min_child_weight, lambda) and resetting any prior Optuna study. Changing this parameter requires deleting trained models. |
| freqai.label_weighting.fill_epsilon | 0.000001 | float [0,1] | Off-pivot fraction of the pivot baseline. Ignored when fill_method not in {epsilon,epsilon_gaussian}. |
| freqai.label_weighting.fill_epsilon_baseline | mean |
enum {mean,median} |
Pivot baseline statistic. mean tracks central tendency; median is robust against pivot-weight skew. Ignored when fill_method not in {epsilon,epsilon_gaussian}. |
| freqai.label_weighting.fill_sigma_candles | 25.0 | float >= 0.5 | Gaussian standard deviation in candles for the per-pivot bumps. Acts as the upper bound on per-pivot sigma when fill_bandwidth == "knn". Lower bound 0.5 prevents severe underflow in the Gaussian tail. Under causal_mode=true the bumps use a finite support ceil(4 * fill_sigma_candles) (see causal_mode). Ignored when fill_method not in {gaussian,epsilon_gaussian}. |
| freqai.label_weighting.fill_sigma_min_candles | 0.5 | float >= 0.5 | Lower bound on per-pivot sigma in candles when fill_bandwidth == "knn". Clipped to fill_sigma_candles when larger. Ignored when fill_method not in {gaussian,epsilon_gaussian} or fill_bandwidth != "knn". |
| freqai.label_weighting.fill_bandwidth | fixed |
enum {fixed,knn} |
Per-pivot Gaussian bandwidth selector. fixed applies a constant fill_sigma_candles to every pivot. knn adapts each pivot's sigma to local pivot density via sigma_p = clip(fill_bandwidth_alpha * d_k(p), fill_sigma_min_candles, fill_sigma_candles) where d_k(p) is the index distance to the k-th nearest pivot neighbor (Loftsgaarden and Quesenberry; Silverman, §5.2). Mitigates the crushing of weaker pivots by stronger neighbors in dense clusters. Ignored when fill_method not in {gaussian,epsilon_gaussian}. |
| freqai.label_weighting.fill_bandwidth_neighbors | 1 | int >= 1 | k for the k-nearest-neighbor bandwidth selector. Ignored when fill_method not in {gaussian,epsilon_gaussian} or fill_bandwidth != "knn". |
| freqai.label_weighting.fill_bandwidth_alpha | 0.5 | float > 0 | Multiplicative factor on the k-th neighbor distance. Smaller values produce sharper, more separated Gaussians; larger values approach the fixed behavior. Ignored when fill_method not in {gaussian,epsilon_gaussian} or fill_bandwidth != "knn". |
| freqai.label_weighting.support_policy | fallback |
enum {fallback,raise} |
Policy when active label weighting fails support checks (evaluated on the training rows surviving upstream filtering). raise aborts the fit; fallback logs a WARNING and uses sanitized base sample weights for that fit. Eval (test/val) weights bypass this policy and fall back only when label support collapses; shape or alignment errors remain fatal. |
| freqai.label_weighting.min_pivot_equivalent_count | 3 | int >= 1 | Minimum number of surviving pivot-equivalent label weights required after filtering. Pivot-equivalent rows are weights at least 10% of the surviving maximum label weight. |
| freqai.label_weighting.min_positive_label_weight_fraction | 0.01 | float [0,1] | Minimum fraction of filtered training rows with finite positive label weights. |
| freqai.label_weighting.min_effective_sample_size | 3.0 | float >= 1 | Minimum Kish effective sample size of the final composed training weights. |
| Label pipeline | |||
| freqai.label_pipeline.standardization | none |
enum {none,zscore,robust,mmad,power_yj} |
Standardization method applied to labels before normalization. none=w, zscore=(w-μ)/σ, robust=(w-median)/(Q₃-Q₁), mmad=(w-median)/(MAD·k), power_yj=YJ(w). |
| freqai.label_pipeline.robust_quantiles | [0.25, 0.75] | list[float] where 0 <= Q1 < Q3 <= 1 | Quantile range for robust standardization, Q1 and Q3. |
| freqai.label_pipeline.mmad_scaling_factor | 1.4826 | float > 0 | Scaling factor for MMAD standardization. |
| freqai.label_pipeline.normalization | maxabs |
enum {maxabs,minmax,sigmoid,none} |
Normalization method applied to labels. maxabs=w/max(|w|), minmax=low+(w-min)/(max-min)·(high-low), sigmoid=2·σ(scale·w)-1, none=w. |
| freqai.label_pipeline.minmax_range | [-1.0, 1.0] | list[float], low < high | Target range for minmax normalization, min and max. |
| freqai.label_pipeline.sigmoid_scale | 1.0 | float > 0 | Scale parameter for sigmoid normalization, controls steepness. |
| freqai.label_pipeline.gamma | 1.0 | float (0,10] | Contrast exponent applied to labels after normalization: >1 emphasizes extrema, values between 0 and 1 soften. |
| Feature parameters | |||
| freqai.feature_parameters.label_period_candles | min/max midpoint | int >= 1 | Zigzag labeling NATR period. |
| freqai.feature_parameters.label_horizon_candles | label_period_candles |
int >= 1 | Conservative fixed purge horizon in candles: the magnitude of the causal guards' purge and of the default timeseries_split gap (see causal_mode for how the guards consume it). When unset, falls back to label_period_candles. |
| freqai.feature_parameters.causal_mode | true | bool | Causal split-guard master toggle. When true (default): (1) rejects data_split_parameters.shuffle=true, feature_parameters.shuffle_after_split=true, and feature_parameters.reverse_train_test_order=true (two of these rejections are independent of this toggle: timeseries_split rejects shuffle_after_split structurally, and an active holdout test_size != 0 rejects all three at evaluation); (2) for timeseries_split, auto-sets gap=label_horizon_candles when gap is unset or 0 and rejects an explicit gap<label_horizon_candles; (3) for train_test_split, applies the same fixed label_horizon_candles purge around the train/test boundary; (4) both split methods additionally drop any train row whose label-aware availability reaches the test boundary; (5) label weighting becomes causal: the epsilon baseline at each row uses only pivot weights available with that row's label. false is deprecated: the causal split-guard rejections are lifted, but the toggle-independent ones remain (timeseries_split still rejects shuffle_after_split, and an active holdout still rejects all three at evaluation); timeseries_split gap auto-sets from label_period_candles, and Gaussian fills keep unbounded tails. |
| freqai.feature_parameters.min_label_period_candles | 12 | int >= 1 | Minimum labeling NATR period used for reversals labeling HPO. |
| freqai.feature_parameters.max_label_period_candles | 24 | int >= 1 | Maximum labeling NATR period used for reversals labeling HPO. |
| freqai.feature_parameters.label_natr_multiplier | min/max midpoint | float > 0 | Zigzag labeling NATR multiplier. |
| freqai.feature_parameters.min_label_natr_multiplier | 9.0 | float > 0 | Minimum labeling NATR multiplier used for reversals labeling HPO. |
| freqai.feature_parameters.max_label_natr_multiplier | 12.0 | float > 0 | Maximum labeling NATR multiplier used for reversals labeling HPO. |
| freqai.feature_parameters.label_frequency_candles | auto |
int [2, 10000] | auto |
Reversals labeling frequency. auto = max(2, 2 * number of whitelisted pairs). |
| freqai.feature_parameters.label_weights | uniform | list of 7 finite floats >= 0; sum > 0 | Per-objective weights for trial selection methods, normalized internally. Objectives: (1) number of detected reversals, (2) median swing amplitude, (3) median (swing amplitude / median volatility-threshold ratio), (4) median swing volume per candle, (5) median swing speed, (6) median swing efficiency ratio, (7) median swing volume-weighted efficiency ratio. |
| freqai.feature_parameters.label_p_order | null | minkowski: finite float > 0; power_mean: finite float; null otherwise |
Lp exponent for parameterized distance metrics. Used by minkowski distance (default 2.0) and power_mean distance (default 1.0). The KNN power_mean aggregation exponent is configured by label_density_aggregation_param. Ignored by other metrics. |
| freqai.feature_parameters.label_method | compromise_programming |
enum {compromise_programming,topsis,kmeans,kmeans2,knn,medoid} |
HPO label Pareto front trial selection method. kmedoids is unavailable in the current Python 3.14 image. |
| freqai.feature_parameters.label_distance_metric | euclidean |
enum {euclidean,minkowski,chebyshev,cityblock,sqeuclidean,seuclidean,mahalanobis,harmonic_mean,geometric_mean,arithmetic_mean,quadratic_mean,cubic_mean,power_mean,weighted_sum} |
Distance metric for compromise_programming and topsis methods. Invalid values warn and fall back to euclidean. |
| freqai.feature_parameters.label_cluster_metric | euclidean |
enum {euclidean,minkowski,chebyshev,cityblock,sqeuclidean,seuclidean,mahalanobis} |
Distance metric for kmeans and kmeans2. Invalid values warn and fall back to euclidean. |
| freqai.feature_parameters.label_cluster_selection_method | topsis |
enum {compromise_programming,topsis} |
Cluster selection method for clustering-based label methods. |
| freqai.feature_parameters.label_cluster_trial_selection_method | topsis |
enum {compromise_programming,topsis} |
Best cluster trial selection method for clustering-based label methods. |
| freqai.feature_parameters.label_density_metric | method-dependent | enum {euclidean,minkowski,chebyshev,cityblock,sqeuclidean,seuclidean,mahalanobis} |
Distance metric for knn and medoid methods. Invalid values warn and fall back to the method's natural default (minkowski for knn, euclidean for medoid). |
| freqai.feature_parameters.label_density_aggregation | power_mean |
enum {power_mean,quantile,min,max} |
Aggregation method for KNN neighbor distances. |
| freqai.feature_parameters.label_density_n_neighbors | 5 | int >= 1 | Number of neighbors for KNN. |
| freqai.feature_parameters.label_density_aggregation_param | aggregation-dependent | power_mean: finite float; quantile: float [0,1]; null otherwise |
Tunable for KNN neighbor distance aggregation: Lp exponent (power_mean) or quantile value (quantile). |
| freqai.feature_parameters.scaler | minmax |
enum {minmax,maxabs,standard,robust} |
Feature scaling method. minmax=MinMaxScaler, maxabs=MaxAbsScaler, standard=StandardScaler, robust=RobustScaler. Changing this parameter requires deleting trained models. |
| freqai.feature_parameters.range | [-1.0, 1.0] | list[float], low < high | Target range for minmax scaler, min and max. Changing this parameter requires deleting trained models. |
| Label prediction | |||
| freqai.label_prediction.method | thresholding |
enum {none,thresholding} |
Prediction method. none disables threshold computation, thresholding enables adaptive threshold calculation. |
| freqai.label_prediction.selection_method | rank_extrema |
enum {rank_extrema,rank_peaks,partition} |
Extrema selection method. rank_extrema ranks extrema values, rank_peaks ranks detected peak values, partition uses sign-based partitioning. |
| freqai.label_prediction.threshold_method | mean |
enum {mean,isodata,li,minimum,otsu,triangle,yen,median,soft_extremum} |
Thresholding method for prediction thresholds. |
| freqai.label_prediction.soft_extremum_alpha | 12.0 | float >= 0 | Alpha for soft_extremum threshold method. |
| freqai.label_prediction.outlier_quantile | 0.999 | float (0,1) | Quantile threshold for predictions outlier filtering. |
| freqai.label_prediction.keep_fraction | 0.0075 | float (0,1] | Fraction of extrema used for thresholds. 1 uses all, lower values keep only most significant. Applies to rank_extrema and rank_peaks; ignored for partition. |
| Optuna / HPO | |||
| freqai.optuna_hyperopt.enabled | false | bool | Enables regressor and dynamic label HPO. |
| freqai.optuna_hyperopt.sampler | tpe |
enum {tpe,auto} |
HPO sampler algorithm for hp namespace. tpe uses TPESampler with multivariate, group, and constant_liar (when multiple workers), auto uses AutoSampler. |
| freqai.optuna_hyperopt.label_sampler | auto |
enum {auto,tpe,nsgaii,nsgaiii} |
HPO sampler algorithm for multi-objective label namespace. nsgaii uses NSGAIISampler, nsgaiii uses NSGAIIISampler. |
| freqai.optuna_hyperopt.storage | file |
enum {file,sqlite} |
HPO storage backend. |
| freqai.optuna_hyperopt.continuous | true | bool | Continuous HPO. Forced for both namespaces in backtest and hyperopt, resetting the study on each optimization. |
| freqai.optuna_hyperopt.warm_start | true | bool | Warm start HPO with previous best value(s). Persisted values are loaded and saved only in live and dry-run modes; non-live runs reuse only values produced earlier in the same run. |
| freqai.optuna_hyperopt.n_startup_trials | 15 | int >= 0 | HPO startup trials. |
| freqai.optuna_hyperopt.n_trials | 50 | int >= 1 | Maximum HPO trials. |
| freqai.optuna_hyperopt.n_jobs | 1 | int >= 1 | Parallel HPO workers. |
| freqai.optuna_hyperopt.timeout | 7200 | int >= 0 | HPO wall-clock timeout in seconds. |
| freqai.optuna_hyperopt.label_candles_step | 1 | int >= 1 | Step for Zigzag NATR period label search space. |
| freqai.optuna_hyperopt.space_reduction | false | bool | Enable/disable hp search space reduction based on previous best parameters. |
| freqai.optuna_hyperopt.space_fraction | 0.4 | float [0,1] | Fraction of the hp search space to use with space_reduction. Lower values create narrower search ranges around the best parameters. |
| freqai.optuna_hyperopt.min_resource | 3 | int >= 1 | Minimum resource per HyperbandPruner rung. |
| freqai.optuna_hyperopt.seed | 1 | int [0, 4294967295] | HPO RNG seed used by the Optuna samplers and label-candle shuffling. |
| freqai.optuna_hyperopt.reset_label_study_on_schema_mismatch | true | bool | Reset a persisted label study when its selection schema is missing, invalid, or incompatible. true performs a destructive reset, deleting the study before recreating it; false preserves its trials and stored metadata, permits caller-managed reuse in memory, and does not persist selected params until the schema is reconciled. Both fail closed: an inspection error, or (under true) a deletion error, aborts study creation. Has no effect when continuous=true or outside live/dry-run modes, where studies are always reset. |
| freqai.optuna_hyperopt.vary_model_seed_by_trial | true | bool | Add trial.number to each regressor's configured model seed (or its default seed of 1) during HPO. true samples model randomness across trials; false evaluates every trial and the final fit with the same model seed. This does not change freqai.optuna_hyperopt.seed. |
The label_weighting, label_smoothing, label_pipeline and
label_prediction sections accept either the flat paths listed above or a
per-label format using default and columns.<glob>. Do not mix both formats in
one section: once default or columns is present, sibling flat keys are
ignored with a warning. Matching column patterns are applied from least to most
specific; equally specific patterns follow declaration order, so the later one
wins.
Evaluate a proposed change against the current configuration on the same unseen market history. Judge portfolio performance after costs, not training loss. This procedure does not establish that the current defaults are optimal.
In Freqtrade 2026.8, the native backtest constructs each pair's
rolling predictions before replaying enabled fit_live_predictions() updates
(training loop, replay loop). It exercises
rolling model fits, threshold replay and strategy decisions, but a label-HPO
update during replay cannot affect an already-trained model. Testing that live
feedback requires a chronological runner that interleaves training, prediction
and state updates, or a forward dry-run. This repository provides no such runner;
do not present native-backtest results as validation of the complete live loop.
QuickAdapter predicts smoothed Zigzag morphology, not returns. holdout_rmse
measures the selection model's weighted error on the original label scale,
on a holdout within the training window, before any deployment refit. An empty
holdout, including one emptied by causal purging, yields holdout_rmse=inf
(unavailable). With method=train_test_split, test_size=0 disables internal
validation and final refit, not later rolling predictions; timeseries_split
does not accept zero. Use RMSE to diagnose prediction quality, not profitability.
- Fix the question before inspecting results. Specify the incumbent, candidate change, pair universe, evaluation dates, training/prediction window lengths, HPO budget, seeds and costs. Choose a primary economic metric, a minimum worthwhile improvement and acceptable risk limits. Record all tried configurations, including failures. Reserve a final chronological period for confirmation; once used to revise the strategy, it is no longer unseen.
- Reproduce the information available at each decision. Train on earlier data and compare both configurations on identical subsequent timestamps. Account for listing/delisting dates and missing candles; selecting only today's surviving pairs biases historical results. Fit preprocessing and select features/model hyperparameters inside each training window, using time-ordered inner validation. Keep scoring windows outside model selection. Threshold calibration must use only predictions available at that time.
- Respect label availability. Keep
causal_modeenabled. A historical row is not usable for training until all observations needed for its labels and weights are known. Add eachknown_at_lookaheadcandle offset to its row position in the unsliced window; use the latest availability across labels and weights. Audit it against each split cutoff, rejecting unknown or out-of-frame availability.causal_modealone is not proof of this invariant. Allow for additional publication/execution delays where relevant. Purging removes overlapping label information; an embargo excludes training samples immediately after a validation block when a split uses future training data (López de Prado). Prefer earlier-only training here, not an arbitrary universal embargo duration. - Isolate the change and its state. Start with fixed label/model parameters
when comparing a component; evaluate tuning separately if it is part of the
proposed behavior. Dynamic label HPO optimizes morphology in
fit_live_predictions(), not held-out trading returns: judge its choices on subsequent economic results using the live-loop evaluation above. With validation enabled, QuickAdapter cold-starts regressor trials and the selection model; inherited models are reserved for deployment refit. Use separatefreqai.identifiervalues and model, prediction and Optuna storage for each configuration/seed.--cache nonebypasses backtest-result caching, not FreqAI model or prediction reuse.
- Model costs and execution. Hold sizing, protections and execution rules
constant unless they are the change under test. Set
--feeexplicitly, use--enable-protectionswhen evaluating protections, and use downloaded detail candles with--timeframe-detailwhere feasible. Compare plausible base and adverse cost scenarios, including spread, slippage, impact and funding/borrow costs where applicable. Freqtrade's candle assumptions do not establish realistic fills or capacity; non-fee execution effects need a separate model. A dry-run checks forward behavior, not actual exchange fills. - Report portfolio outcomes. Compare net return, maximum drawdown, exposure,
turnover and trade count, with results by period and long/short side. State the
equity convention and sampling interval. Closed-trade balance omits unrealized
losses: use equity including open positions for portfolio drawdown, or label
the reported balance-based measure and its limitation. Do not average window
drawdowns. Report prediction coverage, failed windows,
holdout_rmseand training latency alongside economics. Do not discard failed runs to improve averages. Cash/buy-and-hold provide context, not a replacement for the incumbent. - Separate market uncertainty from training randomness. Repeat stochastic fits/searches with the same planned seed list for both configurations and report the paired differences, not just the best run. Seeds reuse the same market history; they are not independent market samples. There is no universal sufficient seed count. Record sampler/model seeds and parallelism; a fixed seed alone does not guarantee identical HPO or GPU results.
- Match inference to the data. For uncertainty in mean performance, compare aligned portfolio returns at a stated frequency. A paired block bootstrap can preserve temporal dependence by resampling the same time blocks for both configurations (Politis and Romano). State the effect, interval method, confidence level, block-length choice and sensitivity to it. Justify the dependence/stationarity assumptions; neither extra seeds nor more bootstrap draws compensate for short history or regime changes. Maximum drawdown is path-dependent: an interval for mean return is not its risk bound. Report results as inconclusive when the data cannot support the intended claim.
- Account for strategy selection. Repeatedly choosing the best backtest inflates apparent performance (Bailey et al.). If making significance claims across candidates, define the comparison family and use valid dependence-aware tests with a multiple-testing correction such as Holm's procedure; correction cannot repair invalid underlying p-values. Report effect sizes and uncertainty, not only significance. Keep drawdown and cost sensitivity visible rather than reducing the decision to a single score.
Run lookahead analysis and recursive analysis to investigate leakage and startup sensitivity. Use adequate history for every informative timeframe and a separate disposable FreqAI identifier for each analysis, with no existing model directory. Both commands delete the selected identifier's model directory during analysis. Never use retained or live-run identifiers. Exempt only confirmed target-construction flags; investigate feature and signal differences. Clean results cover only the paths exercised, not the absence of all leakage.
Evaluate the frozen candidate on the reserved period, then check forward behavior
in dry-run. Adopt it only if the evidence supports the planned economic and risk
criteria; otherwise retain the incumbent and distinguish rejection from
insufficient evidence. Archive a timestamped run manifest with commits, resolved
image/dependency versions, configuration/data hashes, commands, identifiers,
seeds, HPO histories, split cutoffs, costs and results. The Docker base tag moves;
record the image digest, not just stable_freqai.
Change the timezone according to your location in
docker-compose.yml.
From the repository root, configure, build and start the ReforceXY container:
cd ReforceXY
cp user_data/config-template.json user_data/config.jsonAdapt the configuration to your needs: edit user_data/config.json to set your
exchange API keys and tune the freqai section.
Then build and start the container:
docker compose up -d --buildPPO, MaskablePPO, RecurrentPPO, DQN, QRDQN
The documented list of model tunables is at the top of the ReforceXY.py file.
The rewarding logic and tunables are documented in the reward space analysis.
Run repository quality checks from the repository root:
Ruff does not need the Freqtrade runtime or project dependencies:
uvx ruff@latest check .
uvx ruff@latest format --check .BasedPyright must run inside the matching Freqtrade QA image. The repository wrapper records the sorted repository-relative identities of every configured Python source, requires that inventory to match BasedPyright's analyzed-file count, and compares it with every emitted diagnostic field—including an optional rule and source range—against the project's exact snapshot. Build each QA target and mount the checkout read-only:
# QuickAdapter
docker build --pull --target qa --tag freqai-strategies-quickadapter-qa quickadapter
docker run --rm \
--mount "type=bind,src=$PWD,dst=/workspace,readonly" \
--entrypoint python \
freqai-strategies-quickadapter-qa \
/workspace/scripts/check_basedpyright.py --project quickadapter
# ReforceXY
docker build --pull --target qa --tag freqai-strategies-reforcexy-qa ReforceXY
docker run --rm \
--mount "type=bind,src=$PWD,dst=/workspace,readonly" \
--entrypoint python \
freqai-strategies-reforcexy-qa \
/workspace/scripts/check_basedpyright.py --project reforcexyThe check fails when a configured source identity or diagnostic is added, removed,
moved, or changed, or when the analyzed-file count differs from the source
inventory. Each project's direct include entries must be normalized,
non-overlapping relative file or directory paths; glob syntax and symbolic links
are rejected. Snapshot updates are deliberate writable operations in the matching
QA image. For example:
docker run --rm \
--mount "type=bind,src=$PWD,dst=/workspace" \
--entrypoint python \
freqai-strategies-quickadapter-qa \
/workspace/scripts/check_basedpyright.py --project quickadapter --writeReview the generated .basedpyright/diagnostics.json diff. Use the ReforceXY image
and --project reforcexy for its snapshot. The writer preserves existing file
permissions and uses mode 0644 when creating a missing snapshot. Snapshot targets
must be regular files; symbolic links and other special files are rejected. The
wrapper rejects direct host and wrong-image execution so Freqtrade imports and
dependency versions remain exact.
The BasedPyright and type-stub versions are pinned in each project's
.devcontainer/requirements-dev.txt. The Freqtrade base images intentionally
follow their rolling stable_freqai and stable_freqairl tags, so record the
resolved image digests when a reproducible audit is required.
List running compose services and the containers they created:
docker compose psEnter a running service:
# use the compose service name (e.g. "freqtrade")
docker compose exec freqtrade /bin/shView logs:
# service logs (compose maps service -> container(s))
docker compose logs -f freqtrade
# or follow a specific container's logs
docker logs -f freqtrade-quickadapterStop and remove the compose stack:
docker compose downAutomatically update docker images:
cd ReforceXY # or quickadapter
cp ../scripts/docker-upgrade.sh .
./docker-upgrade.shThe script checks for new Freqtrade image versions on Docker Hub, rebuilds and restarts containers if updates are found, sends Telegram notifications (if configured), and cleans up unused images.
Configuration and environment variables:
| Variable | Default | Description |
|---|---|---|
| FREQTRADE_CONFIG | ./user_data/config.json |
Freqtrade configuration file path |
| LOCAL_DOCKER_IMAGE | reforcexy-freqtrade |
Local image name |
| REMOTE_DOCKER_IMAGE | freqtradeorg/freqtrade:stable_freqairl |
Freqtrade image to track for updates |
Cronjob setup (daily check at 3:00 AM):
0 3 * * * cd /path/to/freqai-strategies/ReforceXY && ./docker-upgrade.sh >> user_data/logs/docker-upgrade.log 2>&1Do not expect any support of any kind on the Internet. Nevertheless, PRs implementing documentation, bug fixes, cleanups or sensible features will be discussed and might get merged.