337 lines
12 KiB
Markdown
337 lines
12 KiB
Markdown
|
|
# Dynamic Equilibrium Implementation Plan
|
||
|
|
|
||
|
|
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
||
|
|
|
||
|
|
**Goal:** После пробоя BOS/MSS Equilibrium динамически обновляется по новым экстремумам цены до появления следующего BOS/MSS.
|
||
|
|
|
||
|
|
**Architecture:** Вся логика трекинга внутри CEquilibrium. Добавляются 2 параметра (price_high, price_low) в Calculate(). Новый динамический режим (m_dynamic_mode) активируется при BOS/MSS, трекает running extreme, пересчитывает буферы с backfill. Деактивируется при следующем BOS/MSS.
|
||
|
|
|
||
|
|
**Tech Stack:** MQL5, MetaEditor compiler (validate_mql_code)
|
||
|
|
|
||
|
|
**Design doc:** `docs/plans/2026-03-04-dynamic-equilibrium-design.md`
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### Task 1: Добавить новые поля в CEquilibrium
|
||
|
|
|
||
|
|
**Files:**
|
||
|
|
- Modify: `CEquilibrium.mqh:50-65` (private секция, после m_fixed_disc2)
|
||
|
|
|
||
|
|
**Step 1: Добавить поля динамического режима**
|
||
|
|
|
||
|
|
В `CEquilibrium.mqh`, после строки 64 (`m_fixed_disc2`), добавить:
|
||
|
|
|
||
|
|
```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 индекс бара начала трекинга
|
||
|
|
```
|
||
|
|
|
||
|
|
**Step 2: Инициализация в конструкторе**
|
||
|
|
|
||
|
|
В `CEquilibrium::CEquilibrium()` (строка ~71-84), перед закрывающей `}`, добавить:
|
||
|
|
|
||
|
|
```cpp
|
||
|
|
m_dynamic_mode = false;
|
||
|
|
m_break_dir = 0;
|
||
|
|
m_track_high = EMPTY_VALUE;
|
||
|
|
m_track_low = EMPTY_VALUE;
|
||
|
|
m_break_bar = -1;
|
||
|
|
```
|
||
|
|
|
||
|
|
**Step 3: Инициализация в Init()**
|
||
|
|
|
||
|
|
В `CEquilibrium::Init()` (строка ~96-112), перед `return true;`, добавить:
|
||
|
|
|
||
|
|
```cpp
|
||
|
|
m_dynamic_mode = false;
|
||
|
|
m_break_dir = 0;
|
||
|
|
m_track_high = EMPTY_VALUE;
|
||
|
|
m_track_low = EMPTY_VALUE;
|
||
|
|
m_break_bar = -1;
|
||
|
|
```
|
||
|
|
|
||
|
|
**Step 4: Сброс в FullReset()**
|
||
|
|
|
||
|
|
В `CEquilibrium::FullReset()` (строка ~117-133), перед закрывающей `}`, добавить:
|
||
|
|
|
||
|
|
```cpp
|
||
|
|
m_dynamic_mode = false;
|
||
|
|
m_break_dir = 0;
|
||
|
|
m_track_high = EMPTY_VALUE;
|
||
|
|
m_track_low = EMPTY_VALUE;
|
||
|
|
m_break_bar = -1;
|
||
|
|
```
|
||
|
|
|
||
|
|
**Step 5: Обновить AdjustShift()**
|
||
|
|
|
||
|
|
Заменить `AdjustShift` (строка 38):
|
||
|
|
|
||
|
|
```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; }
|
||
|
|
```
|
||
|
|
|
||
|
|
**Step 6: Компиляция**
|
||
|
|
|
||
|
|
Run: `validate_mql_code(mq_file_path="<full_path>/Ind_Aleks_ICT_Entry_V2_TS_Indie.mq5")`
|
||
|
|
Expected: success: true (поля добавлены, но ещё не используются — всё компилируется)
|
||
|
|
|
||
|
|
**Step 7: Commit**
|
||
|
|
|
||
|
|
```bash
|
||
|
|
git add CEquilibrium.mqh
|
||
|
|
git commit -m "feat(CEquilibrium): add dynamic mode fields for post-BOS/MSS tracking"
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### Task 2: Изменить сигнатуру Calculate() и обновить вызов
|
||
|
|
|
||
|
|
**Files:**
|
||
|
|
- Modify: `CEquilibrium.mqh:25-29` (объявление Calculate)
|
||
|
|
- Modify: `CEquilibrium.mqh:161-165` (определение Calculate)
|
||
|
|
- Modify: `CSwingDetector.mqh:492-493` (вызов equilibrium.Calculate)
|
||
|
|
|
||
|
|
**Step 1: Обновить объявление Calculate()**
|
||
|
|
|
||
|
|
В `CEquilibrium.mqh`, строка 25-29. Заменить:
|
||
|
|
|
||
|
|
```cpp
|
||
|
|
void Calculate(int bar, const SStructureSwingState &state,
|
||
|
|
double &buf_equilibrium[],
|
||
|
|
double &buf_premium2[],
|
||
|
|
double &buf_discount2[],
|
||
|
|
int rates_total);
|
||
|
|
```
|
||
|
|
|
||
|
|
На:
|
||
|
|
|
||
|
|
```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);
|
||
|
|
```
|
||
|
|
|
||
|
|
**Step 2: Обновить определение Calculate()**
|
||
|
|
|
||
|
|
В `CEquilibrium.mqh`, строка 161-165. Заменить:
|
||
|
|
|
||
|
|
```cpp
|
||
|
|
void CEquilibrium::Calculate(int bar, const SStructureSwingState &state,
|
||
|
|
double &buf_equilibrium[],
|
||
|
|
double &buf_premium2[],
|
||
|
|
double &buf_discount2[],
|
||
|
|
int rates_total)
|
||
|
|
```
|
||
|
|
|
||
|
|
На:
|
||
|
|
|
||
|
|
```cpp
|
||
|
|
void CEquilibrium::Calculate(int bar, const SStructureSwingState &state,
|
||
|
|
double price_high, double price_low,
|
||
|
|
double &buf_equilibrium[],
|
||
|
|
double &buf_premium2[],
|
||
|
|
double &buf_discount2[],
|
||
|
|
int rates_total)
|
||
|
|
```
|
||
|
|
|
||
|
|
**Step 3: Обновить вызов в CSwingDetector.mqh**
|
||
|
|
|
||
|
|
В `CSwingDetector.mqh`, строка 493. Заменить:
|
||
|
|
|
||
|
|
```cpp
|
||
|
|
equilibrium.Calculate(bar, m_str, buf_equilibrium, buf_premium2, buf_discount2, rates_total);
|
||
|
|
```
|
||
|
|
|
||
|
|
На:
|
||
|
|
|
||
|
|
```cpp
|
||
|
|
equilibrium.Calculate(bar, m_str, high[bar], low[bar], buf_equilibrium, buf_premium2, buf_discount2, rates_total);
|
||
|
|
```
|
||
|
|
|
||
|
|
**Step 4: Компиляция**
|
||
|
|
|
||
|
|
Run: `validate_mql_code(mq_file_path="<full_path>/Ind_Aleks_ICT_Entry_V2_TS_Indie.mq5")`
|
||
|
|
Expected: success: true (сигнатура обновлена, новые параметры передаются но не используются)
|
||
|
|
|
||
|
|
**Step 5: Commit**
|
||
|
|
|
||
|
|
```bash
|
||
|
|
git add CEquilibrium.mqh CSwingDetector.mqh
|
||
|
|
git commit -m "refactor(CEquilibrium): add price_high/price_low params to Calculate()"
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### Task 3: Реализовать логику динамического режима
|
||
|
|
|
||
|
|
**Files:**
|
||
|
|
- Modify: `CEquilibrium.mqh:161-308` (метод Calculate)
|
||
|
|
|
||
|
|
**Step 1: Добавить динамическую логику в Calculate()**
|
||
|
|
|
||
|
|
В метод `CEquilibrium::Calculate()`, ПОСЛЕ блока геттеров (строка ~215, перед комментарием `//=== Стабильные значения для буферов`) вставить:
|
||
|
|
|
||
|
|
```cpp
|
||
|
|
//=== Динамический режим: трекинг после BOS/MSS ===
|
||
|
|
bool is_bos_mss = state.bos_up || state.bos_down || state.mss_up || state.mss_down;
|
||
|
|
|
||
|
|
if(is_bos_mss)
|
||
|
|
{
|
||
|
|
//--- Новый BOS/MSS → начать (или перезапустить) динамический режим
|
||
|
|
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;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
else if(m_dynamic_mode)
|
||
|
|
{
|
||
|
|
//--- Обновление трекинга (без 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);
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Step 2: Добавить динамический пересчёт геттеров**
|
||
|
|
|
||
|
|
Сразу после блока из Step 1, ПЕРЕД `//=== Стабильные значения для буферов`, добавить:
|
||
|
|
|
||
|
|
```cpp
|
||
|
|
//=== Динамический пересчёт геттеров в dynamic mode ===
|
||
|
|
if(m_dynamic_mode)
|
||
|
|
{
|
||
|
|
if(m_break_dir == 1 && state.in_low != EMPTY_VALUE && m_track_high != EMPTY_VALUE)
|
||
|
|
{
|
||
|
|
double dyn_range = m_track_high - state.in_low;
|
||
|
|
if(dyn_range > 0.0)
|
||
|
|
{
|
||
|
|
m_discount = m_track_high - (pre_dis_cal * dyn_range);
|
||
|
|
m_premium = EMPTY_VALUE;
|
||
|
|
if(m_eq_con)
|
||
|
|
{
|
||
|
|
m_discount2 = m_track_high - (pre_dis_cal2 * dyn_range);
|
||
|
|
m_premium2 = EMPTY_VALUE;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
else if(m_break_dir == -1 && state.in_high != EMPTY_VALUE && m_track_low != EMPTY_VALUE)
|
||
|
|
{
|
||
|
|
double dyn_range = state.in_high - m_track_low;
|
||
|
|
if(dyn_range > 0.0)
|
||
|
|
{
|
||
|
|
m_premium = m_track_low + (pre_dis_cal * dyn_range);
|
||
|
|
m_discount = EMPTY_VALUE;
|
||
|
|
if(m_eq_con)
|
||
|
|
{
|
||
|
|
m_premium2 = m_track_low + (pre_dis_cal2 * dyn_range);
|
||
|
|
m_discount2 = EMPTY_VALUE;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Step 3: Добавить динамическую отрисовку буферов**
|
||
|
|
|
||
|
|
В блок `//=== Отрисовка ===` (строка ~259), ПОСЛЕ текущей логики записи буферов (строка ~304, `buf_discount2[bar] = m_fixed_disc2;`), добавить блок перезаписи буферов в динамическом режиме:
|
||
|
|
|
||
|
|
```cpp
|
||
|
|
//=== Динамическая отрисовка: перезаписать буфер, если dynamic mode активен ===
|
||
|
|
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 dyn_range = m_track_high - state.in_low;
|
||
|
|
if(dyn_range > 0.0)
|
||
|
|
{
|
||
|
|
dyn_eq = m_track_high - (pre_dis_cal * dyn_range);
|
||
|
|
if(m_eq_con)
|
||
|
|
dyn_disc2 = m_track_high - (pre_dis_cal2 * dyn_range);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
else if(m_break_dir == -1 && state.in_high != EMPTY_VALUE && m_track_low != EMPTY_VALUE)
|
||
|
|
{
|
||
|
|
double dyn_range = state.in_high - m_track_low;
|
||
|
|
if(dyn_range > 0.0)
|
||
|
|
{
|
||
|
|
dyn_eq = m_track_low + (pre_dis_cal * dyn_range);
|
||
|
|
if(m_eq_con)
|
||
|
|
dyn_prem2 = m_track_low + (pre_dis_cal2 * dyn_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;
|
||
|
|
buf_premium2[i] = (dyn_prem2 != EMPTY_VALUE) ? dyn_prem2 : EMPTY_VALUE;
|
||
|
|
buf_discount2[i] = (dyn_disc2 != EMPTY_VALUE) ? dyn_disc2 : EMPTY_VALUE;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Step 4: Компиляция**
|
||
|
|
|
||
|
|
Run: `validate_mql_code(mq_file_path="<full_path>/Ind_Aleks_ICT_Entry_V2_TS_Indie.mq5")`
|
||
|
|
Expected: success: true
|
||
|
|
|
||
|
|
**Step 5: Commit**
|
||
|
|
|
||
|
|
```bash
|
||
|
|
git add CEquilibrium.mqh
|
||
|
|
git commit -m "feat(CEquilibrium): implement dynamic equilibrium tracking after BOS/MSS"
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### Task 4: Визуальная проверка на графике
|
||
|
|
|
||
|
|
**Step 1: Открыть MetaTrader 5, наложить индикатор на график**
|
||
|
|
|
||
|
|
Проверить визуально:
|
||
|
|
1. До пробоя BOS/MSS — Equilibrium рисуется как раньше (без изменений)
|
||
|
|
2. После пробоя BOS Up — линия Equilibrium динамически сдвигается вверх при новых максимумах
|
||
|
|
3. После пробоя BOS Down — линия динамически сдвигается вниз при новых минимумах
|
||
|
|
4. При появлении нового BOS/MSS — dynamic mode перезапускается с новыми уровнями
|
||
|
|
5. Backfill корректный — вся линия от точки пробоя обновляется, а не только текущий бар
|
||
|
|
|
||
|
|
**Step 2: Проверить в тестере стратегий**
|
||
|
|
|
||
|
|
Запустить визуальный тестер на исторических данных, чтобы убедиться что:
|
||
|
|
- `AdjustShift` корректно сдвигает `m_break_bar`
|
||
|
|
- Нет вылетов за границы буфера
|
||
|
|
- Линия не "улетает" на ноль
|
||
|
|
|
||
|
|
**Step 3: Если всё ОК — финальный commit**
|
||
|
|
|
||
|
|
```bash
|
||
|
|
git add -A
|
||
|
|
git commit -m "verified: dynamic equilibrium visual test passed"
|
||
|
|
```
|