Skip to content

Repository files navigation

FreqAI strategies

Table of contents

QuickAdapter

Quick start

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.json

Adapt 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 --build

The 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.

Configuration tunables

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.

Backtest evaluation protocol

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.

What the backtest measures

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.

Design the comparison

  1. 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.
  2. 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.
  3. Respect label availability. Keep causal_mode enabled. A historical row is not usable for training until all observations needed for its labels and weights are known. Add each known_at_lookahead candle 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_mode alone 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.
  4. 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 separate freqai.identifier values and model, prediction and Optuna storage for each configuration/seed. --cache none bypasses backtest-result caching, not FreqAI model or prediction reuse.

Measure economics and uncertainty

  • Model costs and execution. Hold sizing, protections and execution rules constant unless they are the change under test. Set --fee explicitly, use --enable-protections when evaluating protections, and use downloaded detail candles with --timeframe-detail where 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_rmse and 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.

Confirm and preserve the evidence

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.

ReforceXY

Quick start

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.json

Adapt 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 --build

Supported models

PPO, MaskablePPO, RecurrentPPO, DQN, QRDQN

Configuration tunables

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.

Development

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 reforcexy

The 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 --write

Review 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.

Common workflows

List running compose services and the containers they created:

docker compose ps

Enter a running service:

# use the compose service name (e.g. "freqtrade")
docker compose exec freqtrade /bin/sh

View logs:

# service logs (compose maps service -> container(s))
docker compose logs -f freqtrade

# or follow a specific container's logs
docker logs -f freqtrade-quickadapter

Stop and remove the compose stack:

docker compose down

Automatically update docker images:

cd ReforceXY  # or quickadapter
cp ../scripts/docker-upgrade.sh .
./docker-upgrade.sh

The 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>&1

Note

Do 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.

About

Freqtrade FreqAI strategies

Resources

Stars

59 stars

Watchers

6 watching

Forks

Releases

Sponsor this project

Packages

Used by

Contributors

Languages