///|
/// Search range for deterministic hyperparameter selection.
pub struct OptimizationRange {
lower : Double
upper : Double
steps : Int
}
///|
pub struct OptimizationResult {
parameter : Double
score : Double
baseline : Double
evaluations : Int
stable : Bool
}
///|
pub struct ModelSelectionResult {
name : String
score : Double
rank : Int
diagnostics : Array[Double]
}
///|
pub fn optimization_range(
lower : Double,
upper : Double,
steps : Int,
) -> OptimizationRange {
{ lower, upper, steps: if steps < 1 { 1 } else { steps } }
}
///|
pub fn optimization_grid(range : OptimizationRange) -> Array[Double] {
let result = []
if range.steps == 1 {
result.push(range.lower)
return result
}
for index = 0; index < range.steps; index = index + 1 {
let fraction = index.to_double() / (range.steps - 1).to_double()
result.push(range.lower + fraction * (range.upper - range.lower))
}
result
}
///|
pub fn optimization_best(
results : Array[OptimizationResult],
) -> OptimizationResult {
if results.length() == 0 {
return {
parameter: 0.0,
score: 0.0,
baseline: 0.0,
evaluations: 0,
stable: false,
}
}
let mut best = results[0]
for result in results {
if result.score > best.score {
best = result
}
}
best
}
///|
pub fn optimization_score_gain(result : OptimizationResult) -> Double {
result.score - result.baseline
}
///|
pub fn optimization_threshold(
data : Array[Double],
range : OptimizationRange,
) -> OptimizationResult {
let grid = optimization_grid(range)
let baseline = robust_signal_quality(data)
let results = []
for threshold in grid {
let cleaned = transform_outlier_replacement(data, threshold)
let score = robust_signal_quality(cleaned)
results.push({
parameter: threshold,
score,
baseline,
evaluations: 1,
stable: score >= baseline,
})
}
optimization_best(results)
}
///|
pub fn optimization_trim(
data : Array[Double],
range : OptimizationRange,
) -> OptimizationResult {
let grid = optimization_grid(range)
let baseline = abs_double(mean(data) - median(data))
let results = []
for probability in grid {
let trimmed = transform_trim(data, probability)
let score = 1.0 / (1.0 + abs_double(mean(trimmed) - median(trimmed)))
results.push({
parameter: probability,
score,
baseline,
evaluations: 1,
stable: trimmed.length() > 0,
})
}
optimization_best(results)
}
///|
pub fn optimization_window(
data : Array[Double],
windows : Array[Int],
threshold : Double,
) -> OptimizationResult {
let baseline = streaming_quality_score(data, 2, threshold)
let results = []
for window in windows {
let score = streaming_quality_score(data, window, threshold)
results.push({
parameter: window.to_double(),
score,
baseline,
evaluations: 1,
stable: score >= 0.5,
})
}
optimization_best(results)
}
///|
pub fn optimization_alpha(
data : Array[Double],
range : OptimizationRange,
) -> OptimizationResult {
let baseline = signal_noise_estimate(data)
let results = []
for alpha in optimization_grid(range) {
let residual = streaming_ewma_residuals(data, alpha)
let score = 1.0 / (1.0 + mad(residual))
results.push({
parameter: alpha,
score,
baseline: 1.0 / (1.0 + baseline),
evaluations: 1,
stable: residual.length() == data.length(),
})
}
optimization_best(results)
}
///|
pub fn optimization_forecast_method(
data : Array[Double],
horizon : Int,
) -> Array[ModelSelectionResult] {
let methods = ["naive", "mean", "median", "drift", "huber"]
let result = []
let split = if data.length() < horizon + 2 {
data.length() / 2
} else {
data.length() - horizon
}
let train = []
let holdout = []
for index = 0; index < split; index = index + 1 {
train.push(data[index])
}
for index = split; index < data.length(); index = index + 1 {
holdout.push(data[index])
}
let predictions = [
forecast_naive(train, holdout.length()),
forecast_mean_level(train, holdout.length()),
forecast_median_level(train, holdout.length()),
forecast_drift(train, holdout.length()),
forecast_huber_level(train, holdout.length()),
]
for index = 0; index < methods.length(); index = index + 1 {
let errors = []
let prediction = predictions[index]
let count = if prediction.length() < holdout.length() {
prediction.length()
} else {
holdout.length()
}
for cursor = 0; cursor < count; cursor = cursor + 1 {
errors.push(holdout[cursor] - prediction[cursor])
}
result.push({
name: methods[index],
score: -mean_absolute_error(holdout, prediction),
rank: 0,
diagnostics: [
mean(errors),
sample_stddev(errors),
errors.length().to_double(),
],
})
}
for left = 0; left < result.length(); left = left + 1 {
let mut rank = 0
for right = 0; right < result.length(); right = right + 1 {
if result[right].score > result[left].score {
rank += 1
}
}
result[left] = { ..result[left], rank, }
}
result
}
///|
pub fn optimization_best_forecast_method(
data : Array[Double],
horizon : Int,
) -> String {
let results = optimization_forecast_method(data, horizon)
if results.length() == 0 {
"none"
} else {
let mut best = results[0]
for result in results {
if result.score > best.score {
best = result
}
}
best.name
}
}
///|
pub fn optimization_model_scores(
data : Array[Double],
horizon : Int,
) -> Array[Double] {
let result = []
for item in optimization_forecast_method(data, horizon) {
result.push(item.score)
}
result
}
///|
pub fn optimization_ensemble_weights(scores : Array[Double]) -> Array[Double] {
let result = []
let mut total = 0.0
for score in scores {
let weight = if score < 0.0 { 0.0 } else { score }
result.push(weight)
total += weight
}
if total == 0.0 {
return result
}
for index = 0; index < result.length(); index = index + 1 {
result[index] = result[index] / total
}
result
}
///|
pub fn optimization_weighted_forecast(
predictions : Array[Array[Double]],
weights : Array[Double],
) -> Array[Double] {
let result = []
if predictions.length() == 0 {
return result
}
let count = predictions[0].length()
let normalized = optimization_ensemble_weights(weights)
for index = 0; index < count; index = index + 1 {
let mut value = 0.0
for model = 0; model < predictions.length(); model = model + 1 {
let weight = if model < normalized.length() {
normalized[model]
} else {
0.0
}
if index < predictions[model].length() {
value += weight * predictions[model][index]
}
}
result.push(value)
}
result
}
///|
pub fn optimization_forecast_ensemble(
data : Array[Double],
horizon : Int,
) -> Array[Double] {
let predictions = [
forecast_naive(data, horizon),
forecast_mean_level(data, horizon),
forecast_median_level(data, horizon),
forecast_drift(data, horizon),
forecast_huber_level(data, horizon),
]
optimization_weighted_forecast(predictions, [1.0, 1.0, 1.0, 1.0, 1.0])
}
///|
pub fn optimization_cluster_count(
data : Array[Double],
candidates : Array[Int],
) -> OptimizationResult {
let baseline = if data.length() == 0 {
0.0
} else {
cluster_stability(data, 2)
}
let results = []
for count in candidates {
let safe_count = if count < 2 { 2 } else { count }
let score = cluster_stability(data, safe_count)
results.push({
parameter: safe_count.to_double(),
score,
baseline,
evaluations: 1,
stable: score >= 0.5,
})
}
optimization_best(results)
}
///|
pub fn optimization_drift_threshold(
baseline : Array[Double],
current : Array[Double],
range : OptimizationRange,
) -> OptimizationResult {
let grid = optimization_grid(range)
let base = drift_report(baseline, current, drift_default_rule()).drift_score
let results = []
for threshold in grid {
let rule = drift_rule(threshold, threshold, threshold, threshold, 10)
let report = drift_report(baseline, current, rule)
let score = if report.drifted { 0.0 } else { 1.0 }
results.push({
parameter: threshold,
score,
baseline: 1.0 - base,
evaluations: 1,
stable: !report.drifted,
})
}
optimization_best(results)
}
///|
pub fn optimization_risk_confidence(
returns : Array[Double],
range : OptimizationRange,
) -> OptimizationResult {
let baseline = risk_snapshot(returns, risk_default_rule()).sharpe
let results = []
for confidence in optimization_grid(range) {
let rule = risk_rule(confidence, 0.0, 0.0, 1.0)
let snapshot = risk_snapshot(returns, rule)
results.push({
parameter: confidence,
score: snapshot.sharpe,
baseline,
evaluations: 1,
stable: snapshot.count == returns.length(),
})
}
optimization_best(results)
}
///|
pub fn optimization_stability(
results : Array[OptimizationResult],
tolerance : Double,
) -> Bool {
if results.length() < 2 {
return true
}
let best = optimization_best(results)
for result in results {
if abs_double(result.score - best.score) > tolerance {
return false
}
}
true
}
///|
pub fn optimization_result_vector(result : OptimizationResult) -> Array[Double] {
[
result.parameter,
result.score,
result.baseline,
result.evaluations.to_double(),
if result.stable {
1.0
} else {
0.0
},
optimization_score_gain(result),
]
}
///|
pub fn optimization_result_lines(result : OptimizationResult) -> Array[String] {
[
"parameter=" + result.parameter.to_string(),
"score=" + result.score.to_string(),
"baseline=" + result.baseline.to_string(),
"evaluations=" + result.evaluations.to_string(),
"stable=" + result.stable.to_string(),
"gain=" + optimization_score_gain(result).to_string(),
]
}
///|
pub fn optimization_result_string(result : OptimizationResult) -> String {
optimization_result_lines(result).join("\n")
}