Нужно сделать meta-labeling
Первичный алгоритм уже говорит LONG/SHORT по кроссу, а второй слой должен решить, стоит ли этот конкретный сигнал исполнять. Такой подход хорошо соответствует задаче повышения precision вторичным классификатором. (SSRN)
Главный вывод
Лучшее решение я вижу как трёхконтурный классификатор:
1. CHOP / WHIPSAW detector
Определяет вероятность, что сам кросс возник внутри пилы.
2. TREND continuation expert
Определяет качество обычного трендового кросса.
3. CAPITULATION / REVERSAL expert
Отдельно распознаёт ситуацию «сильная агрессия против будущего сигнала → цена перестала отвечать на агрессию → кросс обратно».
И уже затем:
[
PASS = CAPITULATION_PASS
\quad OR \quad
(\neg CHOP \land TREND_PASS)
]
То есть капитуляционный эксперт получает право отменять запрет диапазонного фильтра.
Именно этого сейчас не хватает вашей архитектуре.
—
1. Сначала я бы исправил несколько вещей в текущем v4
Есть вещи важнее оптимизации весов.
Критическая проблема №1 — исторический lookahead в медианах
Сейчас:
CopyRates(_Symbol, _Period, 2, InpMedLookback, r);
GetMedians() вообще не получает shift.
Это означает:
- для live-сигнала
shift=1 всё примерно логично;
- но когда вы пересчитываете старый сигнал, например
shift=80,
- его
medVol/medRange всё равно строятся по барам около текущего момента графика.
Следовательно, старый сигнал использует данные, которые для него находились в будущем.
Это чистый lookahead и он способен полностью испортить результаты оптимизации.
Нужно:
GetMedians(int signalShift, ...)
CopyRates(_Symbol, _Period, signalShift + 1, InpMedLookback, r);
Это я бы исправил до получения любого обучающего CSV.
—
2. CumDelta сейчас математически ориентирована назад
Посмотрите:
cumArr[0] = barDeltas[0];
for(int i = 1; i <= InpCDLookback; i++)
cumArr[i] = cumArr[i - 1] + barDeltas[i];
f.currentCum = cumArr[0];
При этом:
barDeltas[0] — сигнальный бар;
barDeltas[1] — предыдущий;
barDeltas[60] — самый старый.
Получается, вы строите CumDelta из настоящего назад в прошлое.
А затем считаете:
position =
(currentCum - minCum) / (maxCum-minCum)
В такой конструкции currentCum фактически равен одной дельте сигнального бара, а min/max относятся к суммированию в обратном времени.
Для вашей идеи:
агрессия вниз → экстремальная CumDelta → LONG
это особенно опасно: положение 0/1 может означать совсем не то, что предполагает комментарий.
Правильнее
Строить путь хронологически:
старейший бар
↓
...
↓
предыдущий
↓
сигнальный бар
Примерно:
double cum = 0;
for(int i=InpCDLookback; i>=0; --i)
{
cum += barDeltas[i];
...
}
currentCum = cum;
После этого position действительно означает положение текущей CumDelta относительно её траектории за последние 60 баров.
—
3. И дивергенция CumDelta вследствие этого тоже подозрительна
Сейчас:
double cumChg = cumArr[0] - cumArr[InpCDLookback];
Но при существующем направлении массива это примерно:
delta текущего бара
-
сумма текущего + предыдущих баров
то есть по сути отрицательная сумма прошлого участка.
Поэтому bullDiv/bearDiv я бы пока вообще не использовал как значимый фактор, пока CumDelta не переписана.
—
4. InpMinTrades на самом деле сейчас проверяет не количество сделок
У вас:
classified += vol;
А затем:
bool trustOF = (c.classified >= InpMinTrades);
При этом параметр называется:
InpMinTrades = 30; // мин. сделок
То есть сейчас это минимальный классифицированный объём, а не 30 trades.
Я бы хранил отдельно:
trade_count
classified_trade_count
classified_volume
flagged_trade_count
flagged_volume
и два качества:
[
FlagQuality_{count}=
\frac{flaggedTrades}{trades}
]
[
FlagQuality_{vol}=
\frac{flaggedVolume}{classifiedVolume}
]
Флаги BUY/SELL действительно обозначают направление сделки в MQL5; CopyTicksRange возвращает тики в хронологическом порядке. (