184 lines
6.2 KiB
Markdown
184 lines
6.2 KiB
Markdown
|
|
# Dynamic Equilibrium After BOS/MSS Breakout
|
||
|
|
|
||
|
|
**Дата:** 2026-03-04
|
||
|
|
**Модуль:** CEquilibrium.mqh
|
||
|
|
**Статус:** Утверждён
|
||
|
|
|
||
|
|
## Проблема
|
||
|
|
|
||
|
|
После пробоя BOS/MSS уровня цена продолжает двигаться, но до формирования нового BOS/MSS линия Equilibrium остаётся на старых подтверждённых уровнях (`in_high`/`in_low`). Equilibrium становится неактуальным на время между пробоями.
|
||
|
|
|
||
|
|
## Решение
|
||
|
|
|
||
|
|
Добавить **динамический режим** в CEquilibrium: после BOS/MSS пробоя отслеживать экстремум цены по направлению пробоя и пересчитывать Equilibrium в реальном времени.
|
||
|
|
|
||
|
|
## Подход
|
||
|
|
|
||
|
|
Вся логика внутри CEquilibrium (Подход 1). Добавляются 2 параметра `price_high`, `price_low` в `Calculate()`.
|
||
|
|
|
||
|
|
## Новые поля CEquilibrium
|
||
|
|
|
||
|
|
```cpp
|
||
|
|
//--- Динамический режим после пробоя BOS/MSS
|
||
|
|
bool m_dynamic_mode; // Активен ли динамический режим
|
||
|
|
int m_break_dir; // Направление пробоя: 1=вверх, -1=вниз
|
||
|
|
double m_track_high; // Running max(high[]) с момента пробоя
|
||
|
|
double m_track_low; // Running min(low[]) с момента пробоя
|
||
|
|
int m_break_bar; // AS_SERIES индекс бара начала трекинга
|
||
|
|
```
|
||
|
|
|
||
|
|
## Изменение сигнатуры Calculate()
|
||
|
|
|
||
|
|
```cpp
|
||
|
|
void Calculate(int bar, const SStructureSwingState &state,
|
||
|
|
double price_high, double price_low, // НОВЫЕ параметры
|
||
|
|
double &buf_equilibrium[],
|
||
|
|
double &buf_premium2[],
|
||
|
|
double &buf_discount2[],
|
||
|
|
int rates_total);
|
||
|
|
```
|
||
|
|
|
||
|
|
## Логика динамического режима
|
||
|
|
|
||
|
|
### Активация (при BOS/MSS)
|
||
|
|
|
||
|
|
```cpp
|
||
|
|
bool is_bos_mss = state.bos_up || state.bos_down || state.mss_up || state.mss_down;
|
||
|
|
|
||
|
|
if(is_bos_mss)
|
||
|
|
{
|
||
|
|
//--- Предыдущий dynamic mode автоматически завершается
|
||
|
|
m_dynamic_mode = true;
|
||
|
|
m_break_bar = bar;
|
||
|
|
|
||
|
|
if(state.bos_up || state.mss_up)
|
||
|
|
{
|
||
|
|
m_break_dir = 1;
|
||
|
|
m_track_high = price_high;
|
||
|
|
m_track_low = EMPTY_VALUE;
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
m_break_dir = -1;
|
||
|
|
m_track_low = price_low;
|
||
|
|
m_track_high = EMPTY_VALUE;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### Обновление трекинга (каждый бар в dynamic mode)
|
||
|
|
|
||
|
|
```cpp
|
||
|
|
if(m_dynamic_mode && !is_bos_mss)
|
||
|
|
{
|
||
|
|
if(m_break_dir == 1)
|
||
|
|
m_track_high = MathMax(m_track_high, price_high);
|
||
|
|
else
|
||
|
|
m_track_low = MathMin(m_track_low, price_low);
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### Расчёт динамического Equilibrium (буферы)
|
||
|
|
|
||
|
|
```cpp
|
||
|
|
if(m_dynamic_mode)
|
||
|
|
{
|
||
|
|
double dyn_eq = EMPTY_VALUE;
|
||
|
|
double dyn_prem2 = EMPTY_VALUE;
|
||
|
|
double dyn_disc2 = EMPTY_VALUE;
|
||
|
|
|
||
|
|
if(m_break_dir == 1 && state.in_low != EMPTY_VALUE && m_track_high != EMPTY_VALUE)
|
||
|
|
{
|
||
|
|
double range = m_track_high - state.in_low;
|
||
|
|
if(range > 0.0)
|
||
|
|
{
|
||
|
|
dyn_eq = m_track_high - (pre_dis_cal * range); // Discount
|
||
|
|
if(m_eq_con)
|
||
|
|
dyn_disc2 = m_track_high - (pre_dis_cal2 * range);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
else if(m_break_dir == -1 && state.in_high != EMPTY_VALUE && m_track_low != EMPTY_VALUE)
|
||
|
|
{
|
||
|
|
double range = state.in_high - m_track_low;
|
||
|
|
if(range > 0.0)
|
||
|
|
{
|
||
|
|
dyn_eq = m_track_low + (pre_dis_cal * range); // Premium
|
||
|
|
if(m_eq_con)
|
||
|
|
dyn_prem2 = m_track_low + (pre_dis_cal2 * range);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
//--- Backfill от бара пробоя до текущего
|
||
|
|
if(dyn_eq != EMPTY_VALUE)
|
||
|
|
{
|
||
|
|
int fill_start = MathMin(m_break_bar, rates_total - 1);
|
||
|
|
for(int i = fill_start; i >= bar && i >= 0; i--)
|
||
|
|
{
|
||
|
|
buf_equilibrium[i] = dyn_eq;
|
||
|
|
if(dyn_prem2 != EMPTY_VALUE) buf_premium2[i] = dyn_prem2;
|
||
|
|
if(dyn_disc2 != EMPTY_VALUE) buf_discount2[i] = dyn_disc2;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### AdjustShift — корректировка при новом баре
|
||
|
|
|
||
|
|
```cpp
|
||
|
|
void AdjustShift(int shift)
|
||
|
|
{
|
||
|
|
if(m_bar_eq_prev >= 0) m_bar_eq_prev += shift;
|
||
|
|
if(m_break_bar >= 0) m_break_bar += shift;
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
## Деактивация
|
||
|
|
|
||
|
|
Dynamic mode завершается **только** при следующем BOS/MSS. На том же баре:
|
||
|
|
1. Старый dynamic mode выключается
|
||
|
|
2. Обычная логика (`structural_event = ch_in_h || ch_in_l`) обновляет `m_fixed_eq`
|
||
|
|
3. Новый dynamic mode включается (если это BOS/MSS)
|
||
|
|
|
||
|
|
Простое подтверждение свинга (`ch_in_h`/`ch_in_l` без BOS/MSS) НЕ завершает dynamic mode.
|
||
|
|
|
||
|
|
## Геттеры (для Entry сигналов)
|
||
|
|
|
||
|
|
В dynamic mode геттеры тоже обновляются:
|
||
|
|
|
||
|
|
```cpp
|
||
|
|
if(m_dynamic_mode)
|
||
|
|
{
|
||
|
|
if(m_break_dir == 1 && state.in_low != EMPTY_VALUE && m_track_high != EMPTY_VALUE)
|
||
|
|
{
|
||
|
|
double range = m_track_high - state.in_low;
|
||
|
|
if(range > 0.0)
|
||
|
|
{
|
||
|
|
m_discount = m_track_high - (pre_dis_cal * range);
|
||
|
|
if(m_eq_con) m_discount2 = m_track_high - (pre_dis_cal2 * range);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
else if(m_break_dir == -1 && state.in_high != EMPTY_VALUE && m_track_low != EMPTY_VALUE)
|
||
|
|
{
|
||
|
|
double range = state.in_high - m_track_low;
|
||
|
|
if(range > 0.0)
|
||
|
|
{
|
||
|
|
m_premium = m_track_low + (pre_dis_cal * range);
|
||
|
|
if(m_eq_con) m_premium2 = m_track_low + (pre_dis_cal2 * range);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
## Что НЕ меняется
|
||
|
|
|
||
|
|
- Обычная логика `structural_event` (ch_in_h/ch_in_l) — продолжает работать
|
||
|
|
- `m_fixed_eq` — обновляется как раньше, но в dynamic mode буфер перезаписывается
|
||
|
|
- `FullReset()` — сбрасывает dynamic state
|
||
|
|
- Логика `IsInDiscountZone()` / `IsInPremiumZone()` — без изменений
|
||
|
|
|
||
|
|
## Затронутые файлы
|
||
|
|
|
||
|
|
| Файл | Изменения |
|
||
|
|
|------|-----------|
|
||
|
|
| `CEquilibrium.mqh` | Новые поля, изменённый Calculate(), AdjustShift(), FullReset(), Init() |
|
||
|
|
| `CSwingDetector.mqh` | Обновить вызов equilibrium->Calculate() — добавить high[bar], low[bar] |
|