Skip to content

Feat/pipeline optimizations - #30

Merged
LeFi8 merged 8 commits into
mainfrom
feat/pipeline-optimizations
Jun 25, 2026
Merged

Feat/pipeline optimizations#30
LeFi8 merged 8 commits into
mainfrom
feat/pipeline-optimizations

Conversation

@LeFi8

@LeFi8 LeFi8 commented Jun 24, 2026

Copy link
Copy Markdown
Owner

No description provided.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a cheap in-sample pre-screen to skip expensive statistical evaluations for poor candidates, and an Optuna-based numeric parameter optimization feature to tune strategy parameters on in-sample data. It also updates LLM prompts to display unified diffs of failed attempts and adds database schema updates and tests. The review feedback focuses on improving the performance of the Optuna search by pre-computing walk-forward windows and asset returns, securing YAML serialization with safe_dump, robustly handling NaN values in metric comparisons, and simplifying file cleanup with unlink(missing_ok=True).

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +218 to +222
def _in_sample_objective(
generate_signals_fn: Any,
flat_config: dict[str, Any],
prices: pd.DataFrame,
) -> tuple[float, float, pd.Series]:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To significantly improve the efficiency of the Optuna parameter search, we can avoid re-computing the walk-forward windows and asset returns on every single trial. Since prices and settings do not change during the optimization process, these values can be pre-computed once and passed into _in_sample_objective as optional arguments.

def _in_sample_objective(
    generate_signals_fn: Any,
    flat_config: dict[str, Any],
    prices: pd.DataFrame,
    _asset_returns: pd.DataFrame | None = None,
    _wf_windows: list[Any] | None = None,
) -> tuple[float, float, pd.Series]:

Comment thread src/autobacktest/evaluator/evaluate.py Outdated
Comment on lines +240 to +246
in_sample_idx, _ = partition_holdout_data(prices.index, holdout_years=settings.default_holdout_years)
wf_windows = generate_walk_forward_windows(in_sample_idx, train_years=5, test_years=1)
if not wf_windows:
return 0.0, 0.0, pd.Series(dtype=float)

wf_weights = _generate_wf_weights(prices, generate_signals_fn, flat_config, tickers, wf_windows)
_asset_returns = prices.pct_change().fillna(0.0)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Use the optional pre-computed _wf_windows and _asset_returns if provided, to bypass the expensive window generation and pct_change() calculations during iterative optimization trials.

    if _wf_windows is None:
        in_sample_idx, _ = partition_holdout_data(prices.index, holdout_years=settings.default_holdout_years)
        wf_windows = generate_walk_forward_windows(in_sample_idx, train_years=5, test_years=1)
    else:
        wf_windows = _wf_windows

    if not wf_windows:
        return 0.0, 0.0, pd.Series(dtype=float)

    wf_weights = _generate_wf_weights(prices, generate_signals_fn, flat_config, tickers, wf_windows)
    if _asset_returns is None:
        _asset_returns = prices.pct_change().fillna(0.0)

Comment on lines +111 to +113
original_sharpe, _, _ = _in_sample_objective(generate_signals_fn, flat_config, prices)
if pd.isna(original_sharpe):
original_sharpe = 0.0

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Pre-compute the walk-forward windows and asset returns once before starting the Optuna study, and pass them to _in_sample_objective to avoid redundant computations in every trial.

Suggested change
original_sharpe, _, _ = _in_sample_objective(generate_signals_fn, flat_config, prices)
if pd.isna(original_sharpe):
original_sharpe = 0.0
from autobacktest.config import settings
from autobacktest.evaluator.evaluate import partition_holdout_data, generate_walk_forward_windows
in_sample_idx, _ = partition_holdout_data(prices.index, holdout_years=settings.default_holdout_years)
wf_windows = generate_walk_forward_windows(in_sample_idx, train_years=5, test_years=1)
asset_returns = prices.pct_change().fillna(0.0)
original_sharpe, _, _ = _in_sample_objective(
generate_signals_fn, flat_config, prices, _asset_returns=asset_returns, _wf_windows=wf_windows
)
if pd.isna(original_sharpe):
original_sharpe = 0.0

Comment on lines +135 to +138
sh, _, _ = _in_sample_objective(generate_signals_fn, trial_config, prices)
if pd.isna(sh):
return -float("inf")
return float(sh)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Pass the pre-computed asset_returns and wf_windows to _in_sample_objective inside the Optuna objective function to prevent recalculating them on every trial.

Suggested change
sh, _, _ = _in_sample_objective(generate_signals_fn, trial_config, prices)
if pd.isna(sh):
return -float("inf")
return float(sh)
sh, _, _ = _in_sample_objective(
generate_signals_fn, trial_config, prices, _asset_returns=asset_returns, _wf_windows=wf_windows
)
if pd.isna(sh):
return -float("inf")
return float(sh)

params = optimized_config.get("params", {})
if isinstance(params, dict) and params:
yaml_dict["params"] = params
return yaml.dump(yaml_dict, default_flow_style=False, sort_keys=False)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-medium medium

Use yaml.safe_dump instead of yaml.dump to ensure secure and standard serialization of the configuration dictionary, avoiding potential execution of arbitrary Python objects.

Suggested change
return yaml.dump(yaml_dict, default_flow_style=False, sort_keys=False)
return yaml.safe_dump(yaml_dict, default_flow_style=False, sort_keys=False)

Comment thread src/autobacktest/orchestrator.py Outdated
Comment on lines +1185 to +1187
for p in [temp_py, temp_yaml]:
if p.exists():
p.unlink()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Use p.unlink(missing_ok=True) to simplify the cleanup logic and avoid potential race conditions where the file is deleted between the exists() check and the unlink() call.

            for p in [temp_py, temp_yaml]:
                p.unlink(missing_ok=True)

Comment thread src/autobacktest/orchestrator.py Outdated
orig_metric = _get_metric_value(winner["_report"], self.target_metric)
opt_metric = _get_metric_value(opt_report, self.target_metric)

if sel.accepted and cnf.accepted and opt_metric >= orig_metric:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

If either opt_metric or orig_metric is NaN, direct comparison using >= will evaluate to False. To make the comparison robust against NaN values, default them to -inf before comparing.

Suggested change
if sel.accepted and cnf.accepted and opt_metric >= orig_metric:
orig_val = orig_metric if not pd.isna(orig_metric) else -float("inf")
opt_val = opt_metric if not pd.isna(opt_metric) else -float("inf")
if sel.accepted and cnf.accepted and opt_val >= orig_val:

…puted walk-forward windows and asset returns
@LeFi8
LeFi8 merged commit b576d8a into main Jun 25, 2026
3 checks passed
@LeFi8
LeFi8 deleted the feat/pipeline-optimizations branch June 25, 2026 09:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant