تحليل الهضبة: كيفية التمييز بين النقطة المثلى المتينة والإفراط في التخصيص
المقالة السادسة في سلسلة "اختبارات رجعية بلا أوهام"
قمت بتشغيل study.optimize()، ووجد Optuna مجموعة معلمات بنسبة PnL +87%. أنت متحمس وتستعد لإطلاق الاستراتيجية في الإنتاج. بعد أسبوعين من التداول الحي، PnL حول الصفر. ماذا حدث؟
وجد المحسّن طرف إبرة في فضاء المعلمات. المعلمات مضبوطة بشكل مثالي على التسلسل التاريخي للصفقات — لكن أدنى انحراف في ظروف السوق يدمر البنية بأكملها. هذا هو الإفراط في التخصيص الكلاسيكي، وكان يمكن اكتشافه قبل الإطلاق.
في المقالة السابقة قارنّا بين النزول المتناسق والتحسين البايزي وأوضحنا لماذا يجد Optuna النقطة المثلى بكفاءة أعلى. اليوم — الخطوة التالية: كيفية التأكد من أن النقطة المثلى المكتشفة متينة، وليست نتيجة للتخصيص على الضوضاء.
لماذا إيجاد "أفضل" المعلمات هو نصف العمل فقط
محسّن يتنقل عبر مشهد معلمات شاسع متعدد الأبعاد بحثاً عن النقطة المثلى الحقيقية
تحسين معلمات الاستراتيجية هو بحث عن القيمة القصوى في فضاء متعدد الأبعاد. المشكلة أن القيم القصوى تأتي بنوعين:
-
هضبة — منطقة واسعة ومسطحة حيث تكون PnL مرتفعة باستمرار عبر تغيرات المعلمات. حتى لو تحولت ظروف السوق بنسبة 10-20%، ستستمر الاستراتيجية في تحقيق الأرباح.
-
قمة حادة — ذروة ضيقة حيث تكون PnL مرتفعة فقط عند القيمة الدقيقة للمعلمة. تحويل خطوة واحدة يُسقط الربحية. هذا هو بشبه مؤكد إفراط في التخصيص: وجد المحسّن قطعة أثرية من البيانات التاريخية، وليس نمطاً مستقراً.
تشبيه من تسلق الجبال: الهضبة هي سهل جبلي يمكنك المشي عليه بأمان. القمة الحادة هي طرف إبرة لا يمكنك إلا التوازن عليها.
القمة الحادة مقابل الهضبة المسطحة — الحدس البصري
اليسار: هضبة متينة (جبل طاولة واسع بمنحدرات لطيفة). اليمين: قمة حادة هشة (طرف إبرة محاطة بوديان عميقة)
تخيل خريطة كنتورية حيث المحاور هي معلمتان للاستراتيجية واللون يمثل PnL. يمكن التمييز بين نمطين بصرياً بسهولة:
هضبة (نقطة مثلى متينة):
- مساحات واسعة بنفس اللون
- انتقالات سلسة بين مستويات PnL
- خطوط كنتورية متباعدة
- الانحراف عن النقطة المثلى بنسبة +/-20% يغير PnL بما لا يزيد عن 10%
تخيل خريطة حرارية: في المركز — مستطيل أصفر ساطع بحجم ثلث الخريطة تقريباً. اللون يتحول تدريجياً إلى البرتقالي ثم الأحمر نحو الحواف. النقطة المثلى ليست نقطة، بل منطقة.
قمة حادة (إفراط في التخصيص):
- بقعة ساطعة ضيقة محاطة بألوان باردة
- انتقالات حادة: انهيار بجوار النقطة المثلى مباشرة
- خطوط كنتورية مضغوطة في حلقات ضيقة
- انحراف بنسبة +/-5% يُسقط PnL بنسبة 50% أو أكثر
تخيل نفس الخريطة الحرارية، لكن في المركز — نقطة صفراء صغيرة محاطة فوراً بالأزرق والبنفسجي. مجموعة واحدة "صحيحة" من المعلمات.
تحليل حساسية المعلمات
مخططات الشرائح التي توضح كيف تعتمد PnL على قيم المعلمات الفردية — النطاقات العريضة تدل على المتانة، والتجمعات الضيقة تدل على الهشاشة
التحليل أحادي البعد: PnL مقابل معلمة واحدة
أبسط نهج — تثبيت جميع المعلمات عدا واحدة ورؤية كيف تعتمد PnL على قيمتها. يوفر Optuna plot_slice لهذا الغرض:
import optuna
from optuna.visualization import plot_slice
study = optuna.create_study(direction="maximize")
study.optimize(objective, n_trials=500)
fig = plot_slice(study, params=["htf_entry_sell", "ltf_momentum", "stop_loss_pct"])
fig.show()
ما يجب البحث عنه في مخطط الشريحة:
- معلمة متينة: سحابة النقاط تشكل نطاقاً أفقياً واسعاً بالقرب من النقطة المثلى. أفضل التجارب موزعة على نطاق واسع من قيم المعلمة.
- معلمة هشة: أفضل التجارب مركزة في نطاق ضيق. تحويل المعلمة بخطوة أو خطوتين — وتنهار الربحية.
التحليل ثنائي البعد: المخططات الكنتورية (الخرائط الحرارية)
المخطط الكنتوري يوضح تفاعل معلمتين في آن واحد. هذه هي الأداة الرئيسية لتحليل الهضبة، لأن المعلمات نادراً ما تعمل بشكل مستقل — عتبات الدخول والخروج، والأطر الزمنية، وأحجام المراكز مترابطة.
from optuna.visualization import plot_contour
fig = plot_contour(study, params=["htf_entry_sell", "htf_exit_buy"])
fig.show()
المخطط الكنتوري لزوج معلمات متين يبدو كخريطة طبوغرافية لسهل تلالي: خطوط كنتورية سلسة واسعة، ومساحات كبيرة بنفس اللون. المخطط الكنتوري لزوج هش — كخريطة لمخروط بركاني: حلقات متحدة المركز ضيقة حول نقطة واحدة.
لاستراتيجية بها 12 معلمة فصل، ينتج عن ذلك مخططاً كنتورياً زوجياً. لا يتعين عليك دراستها جميعاً — ابدأ بالمعلمات التي صنفها Optuna على أنها الأكثر أهمية.
التحليل متعدد الأبعاد: ترتيب أهمية المعلمات
يمكن لـ Optuna تقدير مساهمة كل معلمة في دالة الهدف:
from optuna.visualization import plot_param_importances
fig = plot_param_importances(study)
fig.show()
مخطط أهمية المعلمات هو رسم بياني أفقي. تُصنف المعلمات حسب مساهمتها في تباين PnL بترتيب تنازلي. عادة ما تفسر أعلى 3-4 معلمات 70-80% من التباين.
قاعدة: إذا كانت معلمة ما تفسر أقل من 2% من تباين PnL، فإن قيمتها غير ذات صلة عملياً بالنتيجة — فهي متينة بحكم التعريف. ركّز تحليل الهضبة على أهم 5 معلمات.
أدوات التصور في Optuna
خرائط حرارية كنتورية تعرض مشهد تفاعل المعلمات إلى جانب ترتيبات الأهمية
plot_slice — شرائح أحادية البعد
import optuna
from optuna.visualization import plot_slice
fig = plot_slice(study, params=[
"htf_entry_sell", "htf_entry_buy",
"ltf_momentum_threshold", "stop_loss_pct",
"take_profit_pct", "trailing_stop_pct"
])
fig.update_layout(height=800, title="Parameter Slice Plots")
fig.show()
النتيجة — شبكة من مخططات التشتت. كل مخطط فرعي يوضح قيمة دالة الهدف (PnL، المحور Y) مقابل قيمة معلمة واحدة (المحور X). النقاط هي تجارب فردية. لمعلمة متينة، أفضل النقاط (أعلى PnL) موزعة عبر نطاق واسع من X. لمعلمة هشة — مجمعة في عمود ضيق.
plot_contour — كنتورات ثنائية البعد
from optuna.visualization import plot_contour
important_pairs = [
["htf_entry_sell", "htf_entry_buy"],
["htf_entry_sell", "stop_loss_pct"],
["ltf_momentum_threshold", "take_profit_pct"],
]
for params in important_pairs:
fig = plot_contour(study, params=params)
fig.update_layout(title=f"Contour: {params[0]} vs {params[1]}")
fig.show()
كل مخطط كنتوري هو خريطة حرارية بمعلمتين على المحاور. اللون يشفر متوسط PnL في منطقة معينة من فضاء المعلمات. أصفر/أخضر — PnL مرتفع، أزرق/بنفسجي — منخفض. خطوط الكنتور تربط نقاطاً بنفس PnL.
plot_param_importances — مساهمات المعلمات
from optuna.visualization import plot_param_importances
fig = plot_param_importances(
study,
evaluator=optuna.importance.FanovaImportanceEvaluator()
)
fig.show()
fANOVA (تحليل التباين الوظيفي) يفكك تباين دالة الهدف عبر المعلمات وتفاعلاتها. هذا أقوى من الارتباط البسيط لأنه يراعي التأثيرات غير الخطية.
مقاييس الهضبة الكمية
نسبة الحساسية وعرض الهضبة ودرجة المتانة — ثلاثة مقاييس تحدد كمياً جودة الهضبة
التقييم البصري ذاتي. نحتاج أرقاماً. إليك ثلاثة مقاييس تُشكّل مفهوم "الهضبة" بشكل رسمي.
نسبة الحساسية
نسبة تغير PnL إلى تغير المعلمة:
حيث هو انخفاض PnL عندما تنحرف المعلمة عن النقطة المثلى بمقدار .
التفسير:
- — المعلمة متينة: انزياح 10% يسبب انخفاض PnL أقل من 5%
- — حساسية معتدلة
- — المعلمة هشة: انزياح 10% يُسقط PnL بنسبة 20%+
عرض الهضبة
عرض منطقة المعلمة التي تبقى فيها PnL ضمن من النقطة المثلى:
عرض الهضبة النسبي:
حيث المقام هو نطاق البحث الكامل للمعلمة.
التفسير:
- — الهضبة تغطي أكثر من 30% من النطاق عند عتبة 10%. معلمة متينة.
- — الهضبة أضيق من 5% من النطاق. علم أحمر.
درجة المتانة
مقياس مركب عبر جميع المعلمات:
حيث هو الأهمية المعيارية للمعلمة من fANOVA ().
جداء العروض المرجحة هو مقياس صارم: إذا كان لمعلمة مهمة واحدة هضبة ضيقة، سيكون منخفضاً. المعلمات غير المهمة (ذات صغير) لا تؤثر تقريباً.
التفسير:
- — الاستراتيجية متينة
- — يلزم تحقق إضافي (walk-forward)
- — الإفراط في التخصيص محتمل جداً
كود Python للكشف الآلي عن الهضبة
نظام آلي يمسح مشهد المعلمات لتحديد الهضاب المتينة والقمم الهشة
import numpy as np
import optuna
from optuna.importance import FanovaImportanceEvaluator
from typing import Dict, List, Tuple
def compute_sensitivity_ratio(
study: optuna.Study,
param_name: str,
n_steps: int = 20,
) -> float:
"""
Compute sensitivity ratio for a single parameter.
Fixes all parameters at their best values, varies param_name,
estimates PnL drop through trial interpolation.
"""
best_trial = study.best_trial
best_value = best_trial.values[0]
best_param = best_trial.params[param_name]
all_trials = [t for t in study.trials if t.state == optuna.trial.TrialState.COMPLETE]
all_trials.sort(key=lambda t: t.values[0], reverse=True)
top_trials = all_trials[:max(10, len(all_trials) // 5)]
param_values = np.array([t.params[param_name] for t in top_trials])
pnl_values = np.array([t.values[0] for t in top_trials])
if best_param == 0 or best_value == 0:
return float('inf')
from numpy.polynomial import polynomial as P
coeffs = np.polyfit(param_values, pnl_values, deg=2)
dpnl_dparam = 2 * coeffs[0] * best_param + coeffs[1]
sensitivity = abs(dpnl_dparam * best_param / best_value)
return sensitivity
def compute_plateau_width(
study: optuna.Study,
param_name: str,
threshold_pct: float = 10.0,
) -> Tuple[float, float]:
"""
Compute absolute and relative plateau width.
Returns:
(absolute_width, relative_width)
"""
best_value = study.best_value
threshold = best_value * (1 - threshold_pct / 100)
trials = [t for t in study.trials if t.state == optuna.trial.TrialState.COMPLETE]
good_trials = [t for t in trials if t.values[0] >= threshold]
if not good_trials:
return 0.0, 0.0
good_params = [t.params[param_name] for t in good_trials]
all_params = [t.params[param_name] for t in trials]
plateau_min = min(good_params)
plateau_max = max(good_params)
absolute_width = plateau_max - plateau_min
search_range = max(all_params) - min(all_params)
relative_width = absolute_width / search_range if search_range > 0 else 0
return absolute_width, relative_width
def compute_robustness_score(
study: optuna.Study,
threshold_pct: float = 10.0,
) -> Dict:
"""
Compute combined robustness score.
Returns:
dict with per-parameter metrics and the final score
"""
evaluator = FanovaImportanceEvaluator()
importances = optuna.importance.get_param_importances(
study, evaluator=evaluator
)
results = {}
total_importance = sum(importances.values())
for param_name, importance in importances.items():
sensitivity = compute_sensitivity_ratio(study, param_name)
abs_width, rel_width = compute_plateau_width(
study, param_name, threshold_pct
)
weight = importance / total_importance
results[param_name] = {
"importance": importance,
"weight": weight,
"sensitivity_ratio": sensitivity,
"plateau_width_abs": abs_width,
"plateau_width_rel": rel_width,
}
log_score = sum(
r["weight"] * np.log(max(r["plateau_width_rel"], 1e-10))
for r in results.values()
)
robustness_score = np.exp(log_score)
return {
"robustness_score": robustness_score,
"parameters": results,
"verdict": (
"robust" if robustness_score > 0.1
else "check" if robustness_score > 0.01
else "overfitting"
),
}
الاستخدام
report = compute_robustness_score(study, threshold_pct=10.0)
print(f"Robustness score: {report['robustness_score']:.4f}")
print(f"Verdict: {report['verdict']}")
print()
for name, metrics in report["parameters"].items():
print(f" {name}:")
print(f" Importance: {metrics['importance']:.3f}")
print(f" Sensitivity: {metrics['sensitivity_ratio']:.2f}")
print(f" Plateau width: {metrics['plateau_width_rel']:.1%}")
print()
مثال على المخرجات:
Robustness score: 0.1482
Verdict: robust
htf_entry_sell:
Importance: 0.312
Sensitivity: 0.38
Plateau width: 42.5%
htf_entry_buy:
Importance: 0.251
Sensitivity: 0.45
Plateau width: 38.1%
ltf_momentum_threshold:
Importance: 0.187
Sensitivity: 1.21
Plateau width: 22.3%
stop_loss_pct:
Importance: 0.098
Sensitivity: 0.67
Plateau width: 31.0%
take_profit_pct:
Importance: 0.072
Sensitivity: 0.89
Plateau width: 28.4%
trailing_delta:
Importance: 0.031
Sensitivity: 0.22
Plateau width: 55.2%
أمثلة عملية مع استراتيجيات الفصل
مقارنة الاستراتيجية أ (هضبة واسعة، متينة)، والاستراتيجية ب (معتدلة)، والاستراتيجية ج (قمة حادة، إفراط في التخصيص)
لنفحص ثلاث استراتيجيات بها 12 معلمة فصل. كل استراتيجية خضعت لتحسين Optuna بـ 500 تجربة.
الاستراتيجية أ (PnL ~55%، ~500 صفقة، ~15% وقت)
معلمات الاستراتيجية أ تشكل هضبة واسعة. لنأخذ المعلمة الرئيسية htf_entry_sell:
- القيمة المثلى: 0.020
- PnL عند 0.015: +51% (انخفاض 7%)
- PnL عند 0.025: +49% (انخفاض 11%)
- PnL عند 0.010: +43% (انخفاض 22%)
- PnL عند 0.030: +41% (انخفاض 25%)
إذا تخيلت هذا كمخطط أحادي البعد (المحور X — قيمة htf_entry_sell، المحور Y — PnL)، سترى قطع مكافئ لطيف بقمة مسطحة. النطاق 0.010-0.030 هو الهضبة، حيث تبقى PnL ضمن +/-25% من النقطة المثلى.
نسبة الحساسية: — متينة.
عرض الهضبة عند عتبة 10%: من 0.013 إلى 0.027، .
الاستراتيجية ب (PnL ~25%، ~40 صفقة، ~5% وقت)
الاستراتيجية ب محسّنة على عدد صغير من الصفقات. المعلمة htf_entry_sell:
- القيمة المثلى: 0.018
- PnL عند 0.015: +24% (انخفاض 4%)
- PnL عند 0.025: +9% (انخفاض 64%)
- PnL عند 0.012: +11% (انخفاض 56%)
على المخطط — منحنى غير متماثل وحاد. الهضبة توجد فقط في النطاق الضيق 0.015-0.020. على يمين النقطة المثلى — هاوية.
نسبة الحساسية: — حساسية معتدلة، لكن مع 40 صفقة هذا علم أحمر. عينة صغيرة + هضبة ضيقة = احتمال عالٍ للإفراط في التخصيص.
عرض الهضبة عند عتبة 10%: من 0.016 إلى 0.020، .
الاستراتيجية ج (PnL ~300%، ~400 صفقة، ~45% وقت)
الاستراتيجية ج تُظهر PnL مذهلة، لكن تحليل الهضبة يكشف المشاكل:
- القيمة المثلى لـ
htf_entry_sell: 0.022 - PnL عند 0.020: +295% (انخفاض 2%)
- PnL عند 0.025: +142% (انخفاض 53%)
- PnL عند 0.019: +128% (انخفاض 57%)
على المخطط — "إبرة" مميزة: قمة عالية جداً عند 0.022، وانخفاض حاد في جميع الاتجاهات. المخطط الكنتوري سيُظهر بقعة ساطعة محاطة فوراً بألوان باردة.
نسبة الحساسية: — هشة. على الرغم من 400 صفقة، الاستراتيجية تعتمد بشكل مفرط على القيمة الدقيقة لمعلمة واحدة.
عرض الهضبة عند عتبة 10%: من 0.021 إلى 0.023، .
جدول ملخص
| الاستراتيجية | PnL | الصفقات | الحساسية | عرض الهضبة | درجة المتانة | الحكم |
|---|---|---|---|---|---|---|
| الاستراتيجية أ | +55% | ~500 | 0.44 | 35% | 0.148 | متينة |
| الاستراتيجية ب | +25% | ~40 | 1.64 | 10% | 0.032 | تحقق (عينة صغيرة) |
| الاستراتيجية ج | +300% | ~400 | 3.79 | 5% | 0.008 | إفراط في التخصيص |
المفارقة: الاستراتيجية ج بـ PnL +300% لديها أسوأ درجة متانة. الاستراتيجية أ بـ +55% "المتواضعة" هي الأكثر متانة. هذه نتيجة نموذجية لتحليل الهضبة: الأرقام المذهلة غالباً ما تخفي الهشاشة.
يمكن التحقق من فترات الثقة لكل استراتيجية بشكل إضافي من خلال مونت كارلو بوتستراب — سيُظهر تشتت PnL عند إعادة أخذ العينات من الصفقات.
التصور ثلاثي الأبعاد والخرائط الحرارية
مخطط سطحي ثلاثي الأبعاد لـ PnL فوق معلمتين مع خطوط كنتورية مُسقطة على المستوى الأرضي
لأهم أزواج المعلمات، من المفيد بناء سطح ثلاثي الأبعاد وخريطة حرارية. يوفر هذا فهماً بديهياً لشكل المشهد.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import cm
from mpl_toolkits.mplot3d import Axes3D
def plot_parameter_landscape(
study: "optuna.Study",
param_x: str,
param_y: str,
grid_size: int = 50,
):
"""
Build a 3D surface plot and heatmap for a pair of parameters.
"""
trials = [t for t in study.trials
if t.state == optuna.trial.TrialState.COMPLETE]
x_vals = np.array([t.params[param_x] for t in trials])
y_vals = np.array([t.params[param_y] for t in trials])
z_vals = np.array([t.values[0] for t in trials])
from scipy.interpolate import griddata
xi = np.linspace(x_vals.min(), x_vals.max(), grid_size)
yi = np.linspace(y_vals.min(), y_vals.max(), grid_size)
Xi, Yi = np.meshgrid(xi, yi)
Zi = griddata((x_vals, y_vals), z_vals, (Xi, Yi), method='cubic')
fig = plt.figure(figsize=(18, 7))
ax1 = fig.add_subplot(121, projection='3d')
surf = ax1.plot_surface(Xi, Yi, Zi, cmap=cm.viridis, alpha=0.85,
edgecolor='none')
ax1.set_xlabel(param_x)
ax1.set_ylabel(param_y)
ax1.set_zlabel('PnL, %')
ax1.set_title('3D Parameter Landscape')
fig.colorbar(surf, ax=ax1, shrink=0.5)
ax2 = fig.add_subplot(122)
hm = ax2.pcolormesh(Xi, Yi, Zi, cmap=cm.viridis, shading='auto')
contours = ax2.contour(Xi, Yi, Zi, levels=10, colors='white',
linewidths=0.8, alpha=0.7)
ax2.clabel(contours, inline=True, fontsize=8, fmt='%.0f%%')
best = study.best_trial
ax2.scatter(best.params[param_x], best.params[param_y],
color='red', s=100, marker='*', zorder=5, label='Optimum')
ax2.set_xlabel(param_x)
ax2.set_ylabel(param_y)
ax2.set_title('Contour Heatmap')
ax2.legend()
fig.colorbar(hm, ax=ax2)
plt.tight_layout()
plt.savefig(f'landscape_{param_x}_vs_{param_y}.png', dpi=150)
plt.show()
المخطط السطحي ثلاثي الأبعاد لاستراتيجية متينة يشبه جبل الطاولة — قمة مسطحة بمنحدرات لطيفة. لاستراتيجية هشة — قمة حادة مثل ماترهورن. الخريطة الحرارية تكمل المنظر ثلاثي الأبعاد، وتعرض نفس المعلومات في إسقاط علوي مع خطوط كنتورية.
أعلام حمراء: عندما تكون نتائج التحسين مشبوهة
مؤشرات تحذير تشير إلى احتمال الإفراط في التخصيص في نتائج التحسين
ثمانية علامات تدل على أن التحسين وجد إفراطاً في التخصيص وليس نمطاً حقيقياً:
1. نسبة الحساسية > 2 لمعلمة رئيسية
إذا انخفضت PnL أكثر من 20% مع انزياح 10% في المعلمة — فإن النقطة المثلى هشة.
2. عرض الهضبة < 10% من نطاق البحث
إذا كانت المنطقة "الجيدة" تشغل أقل من 10% من النطاق المُستكشف — فإن المحسّن على الأرجح وجد قطعة أثرية.
3. أعلى 3 تجارب تُنتج PnL أعلى بـ 2-3 أضعاف من الوسيط
إذا كانت أفضل التجارب قيماً شاذة مقارنة بالباقي وليست "قمة التل" — فهذا ليس هضبة.
top_3_mean = np.mean(sorted([t.values[0] for t in study.trials
if t.state == optuna.trial.TrialState.COMPLETE],
reverse=True)[:3])
median_pnl = np.median([t.values[0] for t in study.trials
if t.state == optuna.trial.TrialState.COMPLETE])
outlier_ratio = top_3_mean / median_pnl
if outlier_ratio > 2.5:
print(f"WARNING: Top trials are {outlier_ratio:.1f}x above median — possible overfitting")
4. عدد صفقات منخفض (< 50) مع PnL مرتفع
عينة صغيرة + PnL مرتفع = تباين عالٍ في التقدير. تحليل الهضبة على 40 صفقة غير موثوق بحد ذاته. لمثل هذه الاستراتيجيات، مونت كارلو بوتستراب أمر حاسم.
5. مجموعة معلمات "سحرية" واحدة
إذا أظهر المخطط الكنتوري نقطة ساطعة واحدة وسط حقل رمادي — فهذه ليست استراتيجية، بل مجموعة مخصصة للبيانات.
6. معلمات كثيرة جداً
لـ 12 معلمة بـ 10 قيم لكل منها، فضاء البحث يحتوي على مجموعة. يستكشف Optuna حوالي 500. احتمال إيجاد قطعة أثرية "جيدة" في فضاء كهذا مرتفع. كلما زادت المعلمات، يجب أن يكون تحليل الهضبة أكثر صرامة.
7. انخفاض حاد في PnL خارج العينة
إذا كانت PnL داخل العينة +87% وأظهر walk-forward +12% — فإن التحسين خصص المعلمات لفترة التدريب. المزيد عن هذا في مقالة تحسين Walk-Forward.
8. المعلمات "مثبتة" على حدود النطاق
إذا تطابقت القيمة المثلى مع حدود شبكة البحث — فقد تكون النقطة المثلى خارج النطاق. وسّع النطاق وأعد تشغيل التحسين.
تقرير تحليل الهضبة الآلي
جمع كل شيء في تقرير واحد يُولّد بعد كل تحسين:
import json
from datetime import datetime
def generate_plateau_report(
study: "optuna.Study",
strategy_name: str,
n_trades: int,
threshold_pct: float = 10.0,
) -> dict:
"""
Generate a complete plateau analysis report.
"""
robustness = compute_robustness_score(study, threshold_pct)
red_flags = []
sorted_params = sorted(
robustness["parameters"].items(),
key=lambda x: x[1]["importance"],
reverse=True
)
for name, metrics in sorted_params[:3]:
if metrics["sensitivity_ratio"] > 2.0:
red_flags.append(
f"High sensitivity for {name}: "
f"S={metrics['sensitivity_ratio']:.2f}"
)
for name, metrics in robustness["parameters"].items():
if metrics["plateau_width_rel"] < 0.05:
red_flags.append(
f"Narrow plateau for {name}: "
f"W={metrics['plateau_width_rel']:.1%}"
)
all_values = sorted(
[t.values[0] for t in study.trials
if t.state == optuna.trial.TrialState.COMPLETE],
reverse=True
)
if len(all_values) > 10:
top3 = np.mean(all_values[:3])
med = np.median(all_values)
if med > 0 and top3 / med > 2.5:
red_flags.append(
f"Top trials are outliers: "
f"{top3:.1f} vs median {med:.1f} "
f"({top3/med:.1f}x)"
)
if n_trades < 50:
red_flags.append(f"Low trade count: {n_trades}")
report = {
"strategy": strategy_name,
"timestamp": datetime.now().isoformat(),
"best_pnl": study.best_value,
"n_trials": len(study.trials),
"n_trades": n_trades,
"robustness_score": robustness["robustness_score"],
"verdict": robustness["verdict"],
"red_flags": red_flags,
"parameters": robustness["parameters"],
}
return report
report = generate_plateau_report(
study, strategy_name="Strategy A", n_trades=491
)
print(json.dumps(report, indent=2, default=str))
مثال على المخرجات:
{
"strategy": "Strategy A",
"best_pnl": 55.2,
"n_trials": 500,
"n_trades": 491,
"robustness_score": 0.1482,
"verdict": "robust",
"red_flags": [],
"parameters": {
"htf_entry_sell": {
"importance": 0.312,
"sensitivity_ratio": 0.44,
"plateau_width_rel": 0.35
}
}
}
العلاقة مع التحقق Walk-Forward
المتانة البارامترية (تحليل الهضبة) والمتانة الزمنية (walk-forward) كنظامي تحقق متكاملين
تحليل الهضبة والتحقق Walk-Forward (WFO) هما طريقتان متكاملتان:
- تحليل الهضبة يجيب عن السؤال: "ما مدى استقرار النقطة المثلى تجاه انزياحات صغيرة في المعلمات؟" هذا فحص للمتانة البارامترية.
- Walk-forward يجيب عن السؤال: "هل تعمل المعلمات على بيانات لم يرها المحسّن؟" هذا فحص للمتانة الزمنية.
يمكن للاستراتيجية أن تجتاز تحليل الهضبة (هضبة واسعة) لكنها تفشل في walk-forward (تغير نظام السوق). والعكس صحيح — يمكنها اجتياز walk-forward بمعلمات ثابتة لكن لديها نقطة مثلى هشة.
التوصية: استخدم دائماً كلتا الطريقتين. إذا اجتازت الاستراتيجية تحليل الهضبة () و walk-forward () — فهذه إشارة قوية على المتانة. المزيد من التفاصيل في مقالة تحسين Walk-Forward.
لتقييم فترات ثقة PnL في كل مرحلة، طبّق مونت كارلو بوتستراب. ولمقارنة الاستراتيجيات ذات أوقات النشاط المختلفة بشكل صحيح، استخدم مقياس PnL لكل وقت نشاط.
التوصيات
قبل التحسين
-
حدد عدد المعلمات. كلما قلّت المعلمات — كان الهضبة أكثر موثوقية. 5-7 معلمات هو الحد الأقصى المعقول. 12 تتطلب حذراً متزايداً بالفعل.
-
حدد نطاقات ذات معنى. لا تضبط
htf_entry_sellمن 0.001 إلى 1.0 إذا كان النطاق الواقعي 0.005 إلى 0.05. النطاقات الواسعة بلا داعٍ تخلق وهم وجود هضبة. -
استخدم عدداً كافياً من التجارب. لـ 12 معلمة، الحد الأدنى 300-500 تجربة. لتحليل هضبة موثوق — 1000+.
أثناء التحسين
-
راقب التقارب. إذا استمر Optuna في إيجاد حلول أفضل بشكل كبير بعد 400 تجربة — فالعملية لم تتقارب، وتحليل الهضبة سيكون غير موثوق.
-
استخدم التقليم بحذر. التقليم العدواني (MedianPruner) يمكن أن يقطع تجارب تبدو سيئة في الخطوات المبكرة لكنها مهمة لبناء صورة كاملة للمشهد.
بعد التحسين
-
أنشئ تقرير الهضبة آلياً. ادمج
generate_plateau_report()في خط أنابيب التحسين. لا تعتمد على التقييم البصري — استخدم الأرقام. -
افحص أهم 5 معلمات. إذا أظهر fANOVA أن 3 معلمات تفسر 80% من التباين — يمكن فحص الـ 9 المتبقية بشكل أقل شمولاً.
-
قارن مع الاستراتيجية الأساسية. إذا أظهرت الاستراتيجية بمعلمات افتراضية (بدون تحسين) +30%، والمحسّنة +55% — الفرق 25 نقطة مئوية فقط، والهضبة على الأرجح واسعة. إذا أظهرت الافتراضية 0% والمحسّنة +300% — كل الربحية تعتمد على الضبط الدقيق للمعلمات.
-
الفحص النهائي — walk-forward. تحليل الهضبة شرط ضروري لكنه غير كافٍ للمتانة. تحقق دائماً خارج العينة.
الخلاصة
تحسين المعلمات أداة قوية، لكن بدون تحليل الهضبة هو لعبة روليت. لا تعرف إن كنت وجدت نمطاً مستقراً أم خصصت النموذج على الضوضاء.
ثلاث قواعد لتحليل الهضبة:
-
احسب درجة المتانة. جداء عروض الهضبة المرجحة يعطي رقماً واحداً يلخص متانة جميع المعلمات. — ضوء أخضر.
-
نسبة الحساسية < 1 للمعلمات الرئيسية. إذا كان انزياح المعلمة بنسبة 10% يسبب انخفاض PnL أقل من 10% — المعلمة متينة. أكثر من ذلك — كن حذراً.
-
صوّر المخططات الكنتورية. لا يوجد مقياس يمكنه أن يحل محل فهم شكل المشهد. جبل طاولة مسطح — جيد. إبرة حادة — سيء.
تحليل الهضبة يستغرق 5 دقائق بعد التحسين ويمكن أن يوفر أسابيع من التداول الحي غير المربح. إنها خطوة إلزامية بين study.optimize() وإطلاق البوت.
روابط مفيدة
- Optuna Documentation — Visualization
- Hutter, F., Hoos, H., Leyton-Brown, K. — An Efficient Approach for Assessing Hyperparameter Importance (fANOVA, 2014)
- Pardo, R. — The Evaluation and Optimization of Trading Strategies
- Marcos Lopez de Prado — Advances in Financial Machine Learning, Chapter 11: Dangers of Backtesting
- Bailey, D.H. et al. — The Probability of Backtest Overfitting (2015)
- Optuna — optuna.visualization.plot_contour
- Optuna — optuna.importance.FanovaImportanceEvaluator
- Bergstra, J. & Bengio, Y. — Random Search for Hyper-Parameter Optimization (2012)
Citation
@article{soloviov2026plateauanalysis,
author = {Soloviov, Eugen},
title = {Plateau Analysis: How to Distinguish a Robust Optimum from Overfitting},
year = {2026},
url = {https://marketmaker.cc/en/blog/post/plateau-analysis-overfitting},
version = {0.1.0},
description = {Why finding the best strategy parameters is only half the work. How to visually and quantitatively distinguish a stable plateau from a fragile peak, and why Optuna contour plots are a mandatory step before launching an optimized strategy into production.}
}
MarketMaker.cc Team
البحوث والاستراتيجيات الكمية