Плато анализі: тұрақты оптимумды overfitting-тен қалай ажыратуға болады
"Иллюзиясыз бэктесттер" сериясының 6-мақаласы
📄 Бұл мақала зерттеу мақаласына айналды. Метрикалар формализацияланған және қайта жаңғыртылатын симуляциялармен стресс-тестелген, Probability of Backtest Overfitting және Deflated Sharpe Ratio-мен салыстырылған. Мақаланы онлайн оқыңыз (интерактивті нұсқа + PDF) plateau.marketmaker.cc сайтында, код пен деректер github.com/suenot/plateau-robustness мекенжайында.
Сіз study.optimize() іске қостыңыз, Optuna PnL +87% болатын параметрлер жиынын тапты. Сіз қуанып, стратегияны production-ға дайындап жатырсыз. Екі апталық лайв трейдингтен кейін PnL нөлге жақын. Не болды?
Оптимизатор параметрлер кеңістігінен инеше ұшын тапты. Параметрлер сауда-саттықтардың тарихи тізбегіне тамаша сай келеді — бірақ нарық жағдайларының болмашы ауытқуы бүкіл құрылымды құртады. Бұл классикалық overfitting, әрі оны іске қосудан бұрын анықтауға болатын еді.
Алдыңғы мақалада біз coordinate descent-ті байестік оптимизациямен салыстырдық және Optuna оптимумды неге тиімдірек табатынын көрсеттік. Бүгін — келесі қадам: табылған оптимумның шуылға бейімделудің нәтижесі емес, тұрақты екеніне қалай көз жеткізуге болады.
Ең жақсы параметрлерді табу неге жұмыстың тек жартысы
Оптимизатор шынайы оптимумды іздеп, кең көпөлшемді параметрлер ландшафтын шарлап жатыр
Стратегия параметрлерін оптимизациялау — көпөлшемді кеңістіктегі максимумды іздеу. Мәселе — максимумдар екі түрлі болады:
-
Плато — параметрлер өзгерсе де PnL тұрақты жоғары болатын кең тегіс аймақ. Нарық жағдайлары тиімді параметрлерді 10-20%-ға ауыстырса да, стратегия пайда әкелуін жалғастырады.
-
Өткір шың — PnL тек параметрдің дәл мәнінде ғана жоғары болатын тар шоқы. Бір қадамдық ауытқу пайдалылықты құлатады. Бұл дерлік сөзсіз overfitting: оптимизатор тарихи деректердің артефактісін тапты, тұрақты заңдылықты емес.
Альпинистік метафора: плато — сіз қауіпсіз жүре алатын таулы үстірт. Өткір шың — тек тепе-теңдік сақтауға болатын инеше ұшы.
Өткір шың мен тегіс плато — визуалды түйсік
Сол жақта: тұрақты плато (жайпақ беткейлері бар кең үстел тау). Оң жақта: нәзік өткір шың (терең аңғарлармен қоршалған инеше ұшы)
Осьтер екі стратегия параметрі болатын, ал түс PnL-ді білдіретін контурлық картаны елестетіңіз. Екі үлгіні визуалды түрде оңай ажыратуға болады:
Плато (тұрақты оптимум):
- Бір түстің кең аймақтары
- PnL деңгейлері арасындағы жұмсақ ауысулар
- Бір-бірінен алшақ орналасқан изосызықтар
- Оптимумнан +/-20%-ға ауытқу PnL-ді 10%-дан артық өзгертпейді
Жылу картасын елестетіңіз: ортасында — бүкіл картаның шамамен үштен бір бөлігіндей жарқын сары тіктөртбұрыш. Түс жиектерге қарай біртіндеп қызғылт сарыға, содан кейін қызылға ауысады. Оптимум — нүкте емес, аймақ.
Өткір шың (overfitting):
- Салқын түстермен қоршалған тар жарқын дақ
- Кенет ауысулар: оптимумның қасында тікелей құлдырау
- Тар сақиналарға сығылған изосызықтар
- +/-5%-ға ауытқу PnL-ді 50% немесе одан да көп төмендетеді
Сол жылу картасын елестетіңіз, бірақ ортасында — көгілдір мен күлгінмен бірден қоршалған кішкентай сары нүкте. Бір ғана "дұрыс" параметрлер комбинациясы.
Параметрлердің сезімталдық анализі
PnL жеке параметр мәндеріне қалай тәуелді екенін көрсететін slice plot-тар — кең жолақтар тұрақтылықты, тар кластерлер нәзіктікті білдіреді
Бір өлшемді анализ: 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()
Slice plot-та неге назар аудару керек:
- Тұрақты параметр: нүктелер бұлты оптимумның маңында кең горизонталь жолақ құрайды. Ең жақсы трайлдар параметр мәндерінің кең диапазонына таралған.
- Нәзік параметр: ең жақсы трайлдар тар диапазонда шоғырланған. Параметрді бір-екі қадамға жылжыту — және пайдалылық құлайды.
Екі өлшемді анализ: контурлық сызбалар (heatmap-тар)
Контурлық сызба екі параметрдің өзара әрекеттесуін бір мезгілде көрсетеді. Бұл плато анализінің басты құралы, себебі параметрлер сирек тәуелсіз әрекет етеді — кіру мен шығу шектері, таймфреймдер және позиция көлемдері өзара байланысты.
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%-ын түсіндіреді.
Ереже: параметр PnL дисперсиясының 2%-дан азын түсіндірсе, оның мәні нәтиже үшін іс жүзінде маңызды емес — ол анықтама бойынша тұрақты. Плато анализін ең маңызды 5 параметрге шоғырландырыңыз.
Optuna визуализация құралдары
Маңыздылық рейтингтерімен қатар параметрлердің өзара әрекеттесу ландшафтын көрсететін контурлық heatmap-тар
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()
Әрбір контурлық сызба осьтерде екі параметрі бар heatmap. Түс параметрлер кеңістігінің берілген аймағындағы орташа 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 (functional ANOVA) мақсат функциясының дисперсиясын параметрлер мен олардың өзара әрекеттесуіне жіктейді. Бұл қарапайым корреляциядан күштірек, себебі сызықты емес әсерлерді ескереді.
Плато сандық метрикалары
Sensitivity ratio, плато ені және robustness score — плато сапасын формализациялайтын үш метрика
Визуалды бағалау субъективті. Бізге сандар керек. Мұнда "плато" ұғымын формализациялайтын үш метрика бар.
Sensitivity ratio
PnL өзгерісінің параметр өзгерісіне қатынасы:
мұндағы — параметрі оптимумнан -ге ауытқыған кездегі PnL құлдырауы.
Түсіндірме:
- — параметр тұрақты: 10%-дық ауытқу 5%-дан аз PnL құлдырауын тудырады
- — орташа сезімталдық
- — параметр нәзік: 10%-дық ауытқу PnL-ді 20%+-ға құлатады
Плато ені
PnL оптимумның шегінде қалатын параметр аймағының ені:
Салыстырмалы плато ені:
мұндағы бөлгіш — параметрдің толық іздеу диапазоны.
Түсіндірме:
- — плато 10%-дық шекте диапазонның 30%-дан астамын қамтиды. Тұрақты параметр.
- — плато диапазонның 5%-ынан тар. Ескерту белгісі.
Robustness score
Барлық параметрлер бойынша біріктірілген метрика:
мұндағы — fANOVA-дан алынған параметрінің нормаланған маңыздылығы ().
Салмақталған ендердің көбейтіндісі — қатаң метрика: тіпті бір маңызды параметрдің плато тар болса, төмен болады. Маңызды емес параметрлер (кіші бар) дерлік әсер етпейді.
Түсіндірме:
- — стратегия тұрақты
- — қосымша валидация қажет (walk-forward)
- — overfitting өте ықтимал
Автоматтандырылған плато анықтауға арналған 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%
Сепарация стратегияларымен практикалық мысалдар
A стратегиясын (кең плато, тұрақты), B стратегиясын (орташа) және C стратегиясын (өткір шың, overfitted) салыстыру
12 сепарация параметрі бар үш стратегияны қарастырайық. Әрбір стратегия 500 трайлмен Optuna оптимизациясынан өтті.
A стратегиясы (~55% PnL, ~500 сауда, ~15% уақыт)
A стратегиясының параметрлері кең плато құрайды. htf_entry_sell негізгі параметрін алайық:
- Оптималды мән: 0.020
- 0.015-те PnL: +51% (7% құлдырау)
- 0.025-те PnL: +49% (11% құлдырау)
- 0.010-да PnL: +43% (22% құлдырау)
- 0.030-да PnL: +41% (25% құлдырау)
Мұны бір өлшемді сызба ретінде елестетсеңіз (X осі — htf_entry_sell мәні, Y осі — PnL), жайпақ шыңы бар жұмсақ параболаны көресіз. 0.010-0.030 диапазоны — PnL оптимумның +/-25% шегінде қалатын плато.
Sensitivity ratio: — тұрақты.
10%-дық шекте плато ені: 0.013-тен 0.027-ге дейін, .
B стратегиясы (~25% PnL, ~40 сауда, ~5% уақыт)
B стратегиясы аз санды саудалар бойынша оптимизацияланған. htf_entry_sell параметрі:
- Оптималды мән: 0.018
- 0.015-те PnL: +24% (4% құлдырау)
- 0.025-те PnL: +9% (64% құлдырау)
- 0.012-де PnL: +11% (56% құлдырау)
Сызбада — асимметриялы әрі тік қисық. Плато тек тар 0.015-0.020 диапазонында бар. Оптимумның оң жағында — жар.
Sensitivity ratio: — орташа сезімталдық, бірақ 40 сауда кезінде бұл ескерту белгісі. Кіші үлгі + тар плато = overfitting ықтималдығы жоғары.
10%-дық шекте плато ені: 0.016-дан 0.020-ге дейін, .
C стратегиясы (~300% PnL, ~400 сауда, ~45% уақыт)
C стратегиясы керемет PnL көрсетеді, бірақ плато анализі мәселелерді ашады:
htf_entry_sell-дің оптималды мәні: 0.022- 0.020-да PnL: +295% (2% құлдырау)
- 0.025-те PnL: +142% (53% құлдырау)
- 0.019-да PnL: +128% (57% құлдырау)
Сызбада — тән "ине": 0.022-де өте жоғары шың, барлық бағытта күрт құлдырау. Контурлық сызба салқын түстермен бірден қоршалған жарқын дақты көрсетер еді.
Sensitivity ratio: — нәзік. 400 саудаға қарамастан, стратегия бір параметрдің дәл мәніне шамадан тыс тәуелді.
10%-дық шекте плато ені: 0.021-ден 0.023-ке дейін, .
Қорытынды кесте
| Стратегия | PnL | Саудалар | Sensitivity | Плато ені | Robustness score | Қорытынды |
|---|---|---|---|---|---|---|
| A стратегиясы | +55% | ~500 | 0.44 | 35% | 0.148 | Тұрақты |
| B стратегиясы | +25% | ~40 | 1.64 | 10% | 0.032 | Тексеру керек (кіші үлгі) |
| C стратегиясы | +300% | ~400 | 3.79 | 5% | 0.008 | Overfitting |
Парадокс: PnL-і +300% болатын C стратегиясының robustness score-ы ең нашар. "Қарапайым" +55% болатын A стратегиясы ең тұрақты. Бұл плато анализінің типтік нәтижесі: әсерлі сандар жиі нәзіктікті жасырады.
Әрбір стратегияның сенімділік интервалдарын қосымша Monte Carlo bootstrap арқылы тексеруге болады — ол саудаларды қайта таңдау кезіндегі PnL таралуын көрсетеді.
3D визуализация және heatmap-тар
Еден жазықтығына проекцияланған контурлық сызықтары бар екі параметр бойынша PnL-дің 3D бет сызбасы
Ең маңызды параметр жұптары үшін 3D бет пен heatmap құру пайдалы. Бұл ландшафт пішінін интуитивті түсінуге мүмкіндік береді.
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()
Тұрақты стратегия үшін 3D бет сызбасы үстел тауға ұқсайды — жайпақ беткейлері бар тегіс шың. Нәзік стратегия үшін — Маттерхорн сияқты өткір шың. Heatmap 3D көрінісін толықтырып, изосызықтармен бірге сол ақпаратты жоғарыдан төмен проекциясында көрсетеді.
Ескерту белгілері: оптимизация нәтижелері күдікті болған кезде
Оптимизация нәтижелеріндегі ықтимал overfitting-ті білдіретін ескерту индикаторлары
Оптимизация нақты заңдылықтың орнына overfitting тапқанын білдіретін сегіз белгі:
1. Негізгі параметр үшін sensitivity ratio > 2
Параметрдің 10%-дық ауытқуы кезінде PnL 20%-дан астам құлдыраса — оптимум нәзік.
2. Іздеу диапазонының 10%-нан аз плато ені
"Жақсы" аймақ зерттелген диапазонның 10%-нан аз бөлігін алса — оптимизатор көбіне артефакт тапқан.
3. Топ-3 трайл медианадан 2-3 есе жоғары PnL береді
Ең жақсы трайлдар "төбе шыңы" болудың орнына қалғандардың ішінде ерекшеленсе — бұл плато емес.
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. Жоғары PnL бар кезде саудалар саны аз (< 50)
Кіші үлгі + жоғары PnL = бағалаудағы жоғары дисперсия. 40 сауда бойынша плато анализі өзі-ақ сенімсіз. Мұндай стратегиялар үшін Monte Carlo bootstrap өте маңызды.
5. Бір "сиқырлы" параметрлер комбинациясы
Контурлық сызба сұр өрістің ортасында бір жарқын нүктені көрсетсе — бұл стратегия емес, деректерге бейімделген комбинация.
6. Тым көп параметрлер
Әрбіреуінің 10 мәні бар 12 параметр үшін іздеу кеңістігінде комбинация бар. Optuna ~500-ін зерттейді. Мұндай кеңістіктен "жақсы" артефакт табу ықтималдығы жоғары. Параметр неғұрлым көп болса, плато анализі соғұрлым қатаң болуы тиіс.
7. PnL out-of-sample-де күрт құлдырайды
In-sample 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 сенімділік интервалдарын бағалау үшін Monte Carlo bootstrap қолданыңыз. Ал әртүрлі белсенді уақыты бар стратегияларды дұрыс салыстыру үшін белсенді уақытқа шаққандағы PnL метрикасын қолданыңыз.
Ұсыныстар
Оптимизацияға дейін
-
Параметрлер санын шектеңіз. Параметр аз болған сайын — плато соғұрлым сенімді. 5-7 параметр ақылға қонымды максимум. 12-де қазірдің өзінде жоғары сақтық қажет.
-
Мағыналы диапазондар орнатыңыз. Шынайы диапазон 0.005-тен 0.05-ке дейін болса,
htf_entry_sell-ды 0.001-ден 1.0-ге дейін орнатпаңыз. Қажетсіз кең диапазондар плато елесін жасайды. -
Жеткілікті трайл қолданыңыз. 12 параметр үшін кемінде 300-500 трайл. Сенімді плато анализі үшін — 1000+.
Оптимизация кезінде
-
Конвергенцияны бақылаңыз. Optuna 400 трайлдан кейін де айтарлықтай жақсырақ шешімдер тауып жатса — процесс жинақталмаған, әрі плато анализі сенімсіз болады.
-
Pruning-ті абайлап қолданыңыз. Агрессивті pruning (MedianPruner) алғашқы қадамдарда нашар көрінетін, бірақ ландшафттың толық көрінісін құру үшін маңызды трайлдарды кесіп тастауы мүмкін.
Оптимизациядан кейін
-
Плато есебін автоматты түрде жасаңыз.
generate_plateau_report()-ті оптимизация pipeline-ына кіріктіріңіз. Визуалды бағалауға сенбеңіз — сандарды қолданыңыз. -
Топ-5 параметрді тексеріңіз. fANOVA 3 параметр дисперсияның 80%-ын түсіндіретінін көрсетсе — қалған 9-ды азырақ мұқияттылықпен тексеруге болады.
-
Baseline стратегиямен салыстырыңыз. Стандартты параметрлері бар стратегия (оптимизациясыз) +30% көрсетсе, ал оптимизацияланғаны +55% көрсетсе — айырмашылық небәрі 25 пайыздық пункт, әрі плато кең болуы ықтимал. Стандарт 0% көрсетсе, ал оптимизацияланғаны +300% көрсетсе — бүкіл пайдалылық параметрлердің дәл бейімделуіне тәуелді.
-
Соңғы тексеру — walk-forward. Плато анализі тұрақтылық үшін қажетті, бірақ жеткіліксіз шарт. Әрқашан out-of-sample валидациялаңыз.
Қорытынды
Параметрлерді оптимизациялау — қуатты құрал, бірақ плато анализінсіз бұл рулетка ойыны. Сіз тұрақты заңдылық тапқаныңызды немесе модельді шуылға бейімдегеніңізді білмейсіз.
Плато анализінің үш ережесі:
-
Robustness score-ты есептеңіз. Салмақталған плато ендерінің көбейтіндісі барлық параметрлердің тұрақтылығын жинақтайтын бір сан береді. — жасыл жарық.
-
Негізгі параметрлер үшін sensitivity ratio < 1. Параметрдің 10%-дық ауытқуы 10%-дан аз PnL құлдырауын тудырса — параметр тұрақты. Көбірек болса — сақ болыңыз.
-
Контурлық сызбаларды визуализациялаңыз. Ешбір метрика ландшафт пішінін түсінудің орнын баса алмайды. Тегіс үстел тау — жақсы. Өткір ине — жаман.
Плато анализі оптимизациядан кейін 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)
Дәйексөз
@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.}
}
Authors
Trading-systems engineer
Trading-systems engineer building bots since 2017: cross-exchange arbitrage (connected up to 30 venues), cointegration-based pairs arbitrage across spot and futures, scalping, news and sentiment-driven strategies, trend algorithms, and portfolio management and balancing algorithms. Also builds sub-millisecond order execution, big-data warehouses, backtesting engines, AI agents, and trading interfaces (incl. open-source profitmaker.cc). Stack: JS/TS, Python, Rust/Zig/Go, DevOps, backend, frontend, architecture.