Quant.Strategy.Optimization (quant v0.1.0-alpha.1)

Parameter optimization engine for systematic strategy tuning.

This module provides vectorbt-like functionality for testing parameter combinations and finding optimal strategy configurations.

Features

  • Single and multi-parameter optimization
  • Parallel processing for efficient computation
  • Comprehensive result analysis and ranking
  • Walk-forward optimization for robust validation
  • Integration with all existing strategy types

Basic Usage

# Simple parameter sweep
{:ok, df} = Quant.Explorer.history("AAPL", provider: :yahoo_finance, period: "1y")
param_ranges = %{fast_period: 5..20, slow_period: 20..50}
{:ok, results} = Quant.Strategy.Optimization.run_combinations(df, :sma_crossover, param_ranges)

# Find best parameters
best = Quant.Strategy.Optimization.find_best_params(results, :total_return)

Parallel Processing

# Use all available cores
{:ok, results} = Quant.Strategy.Optimization.run_combinations_parallel(
  df, :sma_crossover, param_ranges, concurrency: System.schedulers_online()
)

Summary

Functions

Analyze parameter sensitivity across results.

Find the best parameter combination based on a specific metric.

Generate parameter heatmap data for visualization.

Run parameter combinations for a single strategy type.

Run parameter combinations using parallel processing.

Run parameter combinations using memory-efficient streaming.

Analyze parameter stability across different metrics.

Perform walk-forward optimization with rolling windows.

Types

optimization_options()

@type optimization_options() :: [
  initial_capital: float(),
  commission: float(),
  slippage: float(),
  concurrency: pos_integer(),
  progress_callback: function() | nil,
  store_backtest_data: boolean()
]

optimization_result()

@type optimization_result() :: {:ok, Explorer.DataFrame.t()} | {:error, term()}

param_ranges()

@type param_ranges() :: %{required(atom()) => Range.t() | [any()]}

strategy_type()

@type strategy_type() :: atom()

Functions

analyze_parameter_sensitivity(results, param_name)

@spec analyze_parameter_sensitivity(Explorer.DataFrame.t(), atom()) ::
  {:ok, map()} | {:error, term()}

Analyze parameter sensitivity across results.

Shows how changes in a specific parameter affect performance metrics.

Examples

# See how fast_period affects returns
sensitivity = analyze_parameter_sensitivity(results, :fast_period)

find_best_params(results, metric \\ :total_return)

@spec find_best_params(Explorer.DataFrame.t(), atom()) :: map() | nil

Find the best parameter combination based on a specific metric.

Parameters

  • results - DataFrame from optimization results
  • metric - Metric to optimize for (default: :total_return)

Available Metrics

  • :total_return - Total portfolio return
  • :sharpe_ratio - Risk-adjusted return
  • :sortino_ratio - Downside deviation adjusted return
  • :calmar_ratio - Return divided by max drawdown
  • :win_rate - Percentage of winning trades
  • :profit_factor - Gross profit divided by gross loss

Examples

# Find best total return
best = find_best_params(results, :total_return)

# Find best risk-adjusted return
best = find_best_params(results, :sharpe_ratio)

parameter_heatmap(results, x_param, y_param, metric)

@spec parameter_heatmap(Explorer.DataFrame.t(), atom(), atom(), atom()) ::
  {:ok, Explorer.DataFrame.t()} | {:error, term()}

Generate parameter heatmap data for visualization.

Creates a 2D heatmap showing how a performance metric varies across two parameter dimensions.

Parameters

  • results - Optimization results DataFrame
  • x_param - Parameter for X-axis
  • y_param - Parameter for Y-axis
  • metric - Performance metric to visualize

Examples

# Create heatmap of total return vs fast/slow periods
heatmap = parameter_heatmap(results, :fast_period, :slow_period, :total_return)

run_combinations(dataframe, strategy_type, param_ranges, opts \\ [])

Run parameter combinations for a single strategy type.

Tests all combinations of the provided parameter ranges and returns optimization results as a DataFrame.

Parameters

  • dataframe - Historical OHLCV data
  • strategy_type - Strategy type atom (e.g., :sma_crossover, :rsi_threshold)
  • param_ranges - Map of parameter names to ranges or lists of values
  • opts - Optimization options

Options

  • :initial_capital - Starting capital (default: 10000.0)
  • :commission - Trading commission rate (default: 0.001)
  • :slippage - Market slippage rate (default: 0.0005)
  • :store_backtest_data - Whether to store full backtest results (default: false)

Examples

# Test SMA crossover periods
param_ranges = %{fast_period: 5..15, slow_period: 20..40}
{:ok, results} = run_combinations(df, :sma_crossover, param_ranges)

# Test with custom options
{:ok, results} = run_combinations(df, :sma_crossover, param_ranges,
  initial_capital: 50_000.0,
  commission: 0.002
)

run_combinations_parallel(dataframe, strategy_type, param_ranges, opts \\ [])

@spec run_combinations_parallel(
  Explorer.DataFrame.t(),
  strategy_type(),
  param_ranges(),
  optimization_options()
) :: optimization_result()

Run parameter combinations using parallel processing.

Same as run_combinations/4 but processes parameter combinations concurrently for improved performance.

Additional Options

  • :concurrency - Number of parallel tasks (default: System.schedulers_online())
  • :progress_callback - Function called with progress percentage
  • :timeout - Timeout per parameter combination in ms (default: 30_000)

Examples

# Use all CPU cores
{:ok, results} = run_combinations_parallel(df, :sma_crossover, param_ranges,
  concurrency: System.schedulers_online()
)

# With progress tracking
progress_fn = fn progress -> IO.puts("Progress: " <> Integer.to_string(progress) <> "%") end
{:ok, results} = run_combinations_parallel(df, :sma_crossover, param_ranges,
  progress_callback: progress_fn
)

run_combinations_stream(dataframe, strategy_type, param_ranges, opts \\ [])

@spec run_combinations_stream(
  Explorer.DataFrame.t(),
  strategy_type(),
  param_ranges(),
  optimization_options()
) :: Enumerable.t()

Run parameter combinations using memory-efficient streaming.

Processes massive parameter spaces (10,000+ combinations) in chunks without loading all results into memory simultaneously. Returns a stream of result chunks for further processing.

Parameters

  • dataframe - Historical OHLCV data
  • strategy_type - Strategy type atom
  • param_ranges - Map of parameter names to ranges or lists
  • opts - Streaming options

Streaming Options

  • :chunk_size - Combinations per chunk (default: 100)
  • :concurrency - Parallel tasks per chunk (default: 4)
  • :timeout - Timeout per chunk in ms (default: 30_000)
  • :memory_limit - Max memory usage in MB (default: 500)

Returns

Stream of {:ok, chunk_results_df} or {:error, reason}

Examples

# Stream massive parameter space
param_ranges = %{period: 1..100, threshold: 0.01..0.10//0.01}  # 10,000 combos
stream = run_combinations_stream(df, :sma_crossover, param_ranges, chunk_size: 50)

# Process results incrementally
best_result =
  stream
  |> Stream.filter(fn {:ok, _chunk} -> true; _ -> false end)
  |> Stream.map(fn {:ok, chunk} -> Results.find_best_params(chunk) end)
  |> Enum.max_by(fn result -> result.total_return end)

# Export results as they come
stream
|> Stream.each(fn {:ok, chunk} -> export_chunk_to_csv(chunk) end)
|> Stream.run()

stability_analysis(results, metric, threshold \\ 0.1)

@spec stability_analysis(Explorer.DataFrame.t(), atom(), float()) ::
  {:ok, map()} | {:error, term()}

Analyze parameter stability across different metrics.

Identifies parameter combinations that perform consistently well across multiple performance metrics.

Parameters

  • results - Optimization results
  • metric - Primary metric to analyze
  • threshold - Stability threshold (default: 0.1)

Examples

# Find stable parameter combinations
stability = stability_analysis(results, :total_return, 0.15)

walk_forward_optimization(dataframe, strategy_type, param_ranges, opts \\ [])

@spec walk_forward_optimization(
  Explorer.DataFrame.t(),
  strategy_type(),
  param_ranges(),
  keyword()
) ::
  optimization_result()

Perform walk-forward optimization with rolling windows.

Tests parameter stability over time by optimizing on a training window and testing on a subsequent period, then rolling forward.

Parameters

  • dataframe - Long-term historical data
  • strategy_type - Strategy to optimize
  • param_ranges - Parameter ranges to test
  • opts - Walk-forward options

Walk-Forward Options

  • :training_window - Size of training period in days (default: 252)
  • :testing_window - Size of testing period in days (default: 63)
  • :step_size - Days to step forward each iteration (default: 21)
  • :min_trades - Minimum trades required for valid result (default: 5)

Examples

# Annual training, quarterly testing, monthly steps
{:ok, wf_results} = walk_forward_optimization(df, :sma_crossover, param_ranges,
  training_window: 252,
  testing_window: 63,
  step_size: 21
)