Цель — не просто «обучить модель», а получить три независимо проверяемых результата:
LEVEL — где находится значимый уровень и какова вероятность, что он удержится/сломается.
CONTEXT — что рынок сейчас делает: продолжение, пауза, принятие, ретест, ловушка, разворот.
SIGNAL — есть ли сейчас конкретная ситуация с положительным математическим ожиданием после комиссии, проскальзывания и задержки.
И только когда эти три части согласованы:
LEVEL + CONTEXT + EXECUTION
↓
FINAL SIGNAL
—
Целевой POTOK
Я бы довёл архитектуру до такой:
RAW FEED
Trades + Book
↓
────────────────────────────────
CORE MICROSTRUCTURE
Aggregate / Sweep / Pull / REST
DOM lifecycle / Ghost / Migration
Flow windows
────────────────────────────────
↓
RAW EVIDENCE
без порогов и без clamp
↓
┌─────────────────┬─────────────────┐
│ LEVEL ENGINE │ SCENARIO ENGINE │
│ где деньги │ что происходит │
│ hold / break │ context / phase │
└─────────────────┴─────────────────┘
↓
CANDIDATE ENGINE
↓
Quality Gate
↓
calibrated probability
↓
SIGNAL
А визуал уже только отображает результат этой системы.
—
Этап A. v0.73 — Identity & Determinism
Это следующий этап, и я считаю его обязательным до ScnLadder, V-разворотов и новых сигналов.
A1. event_uid
Добавляем настоящий 16-байтный идентификатор события:
struct UUIDv7
{
uchar bytes[16];
};
и:
PotokEvent.event_uid
StorageEventRow.event_uid
Но предложенный генератор с MathRand() я не стал бы использовать в таком виде.
Причина: мы одновременно хотим:
одни и те же исходные данные + один config → идентичная база.
Случайный хвост UUID это нарушит.
Поэтому делаем deterministic UUIDv7:
48 bit time_msc
4 bit version=7
12 bit ordinal внутри ms
62 bit deterministic hash
Hash строим из стабильных данных:
symbol
first_time_msc
last_time_msc
direction
first_px_tick
last_px_tick
volume
trades
ordinal
config_hash
UUID остаётся валидным UUIDv7, сортируется по времени, но повторный replay получает тот же event_uid.
A2. scenario_uid
В ScenarioState:
UUIDv7 scenario_uid;
Он рождается один раз:
ScnStart()
и НЕ меняется при:
PRESSURE
→ STALL
→ VACUUM
→ BREAK
→ ACCEPT
...
Это один сценарий.
A3. Не current_scenario_id, а scenario_uid
В events добавляем:
scenario_uid BLOB(16)
scn_phase INTEGER
scn_step INTEGER
Это лучше, чем INTEGER current_scenario_id.
Получится точная связь:
event
↓
scenario_uid
↓
конкретный сценарий
↓
phase/step в момент event
И JOIN уже элементарный:
SELECT *
FROM events e
JOIN scn_transitions s
ON e.scenario_uid = s.scenario_uid
A4. Разделяем scenarios и scn_transitions
Сейчас наша таблица scenario фактически уже похожа на журнал переходов: туда пишутся from_phase/to_phase.
Я бы в новой схеме сделал две сущности:
scenarios
---------
scenario_uid
start_time_msc
dir
start_px_tick
config_hash
scn_transitions
---------------
scenario_uid
time_msc
from_phase
to_phase
step
anchor_tick
reason
evidence...
scenarios — рождение.
scn_transitions — история жизни.
Никаких постоянных UPDATE не требуется.
A5. config_hash
В meta:
code_version
schema_version
feature_schema_version
config_hash
symbol
tick_size
digits
server
session_start
Причём config_hash — хеш всех Inputs, влияющих на алгоритм.
Это позволит потом сказать:
этот результат получен алгоритмом 0.80 при конфигурации 79A3....
A6. Golden Replay
Здесь есть важная вещь, которой нет даже в представленном плане.
Чтобы проверить настоящий детерминизм, мало два раза прочитать таблицу events.
Нужно иметь эталонный кусок сырого потока:
Trades
BookEvents
их точный порядок
timestamps
и дважды прогнать через Core.
Иначе мы проверяем только БД, а не алгоритм.
Поэтому делаем небольшой Golden Feed Recorder для тестовых сессий.
Гейт v0.73:
same raw feed
+ same config
=
same events
same scenario transitions
same level transitions
same UUIDs
—
Этап B. v0.74 — Raw Feature Layer
Это один из самых важных этапов всего проекта.
Ваш файл совершенно правильно ставит сырые признаки как критический этап: сейчас большинство данных уже существует, но итоговые score смешивают математику, веса и clamp.
Например сейчас:
FlowInitiativeScore()
сразу делает:
dominance
speed clamp
tempo clamp
move clamp
DOM bias
weights
→ 0..100
Для UI хорошо.
Для исследования плохо.
Что делаем
Не просто:
raw_init_ratio
а сохраняем компоненты до смешивания.
Например INIT:
flow_same_volume
flow_total_volume
flow_dominance
flow_vps
flow_tps
flow_move_ticks
dom_direction_bias
DEF:
volume
flow_delta_raw
impact
abs_raw
ice_raw
passive_wall_ratio
passive_net
passive_book_share
passive_reliability
passive_life
BRK:
sweep
shift
pull
dominance
vps
tps
weighted_dom
passive_pull
passive_thinness
defense_against_break
EXH:
dominance
vps
impact10
price_move
defense
acceleration
passive_life
То есть модель потом сможет выяснить сама:
sweep=3 + pull=5 + defense=18 важнее, чем наш сегодняшний BRK=77.
Структура БД
Я бы не раздувал опять events.
Лучше:
events
event_features
events — компактный факт исполнения.
event_features — исследовательские данные.
Связь:
event_uid BLOB(16)
Это позволит менять feature_schema_version, не ломая базовый event.
—
Этап C. v0.75 — точный Level Engine
Это непосредственно отвечает на задачу «получать правильные уровни».
Сейчас DomLevelTrack уже очень хороший фундамент:
WALL
TESTED
DEFENDED
WEAKENING
BROKEN
PULLED
Но пока это скорее lifecycle detector.
Нужно превратить его в Level Intelligence.
C1. level_uid
Один и тот же price tick может сегодня быть WALL, исчезнуть, а через минуту там появится совершенно другая стенка.
Поэтому:
price_tick ≠ level identity
Нужен:
level_uid
Например новая level episode рождается:
NONE → WALL
NONE → TESTED
и заканчивается:
BROKEN
PULLED
EXPIRED
Тогда мы узнаем настоящую историю конкретной стены.
C2. Сохраняем сырую физику уровня
Для каждого уровня:
px_tick
side
birth_time
age_ms
current_volume
hist_avg
hist_max
persistence
add_cum
pull_cum
exec_cum
add_rate
pull_rate
test_count
replenish_count
exec/display
distance_from_best
migration_count
migration_distance
reliability
life_state
C3. Migration становится характеристикой уровня
То, что мы уже начали считать, превращается в признаки:
WALL 4592.1
→ WALL 4592.0
→ WALL 4591.9
Это не три независимых стены.
Это:
ASK WALL ADVANCING
или:
BID WALL RETREATING
И это сильнейшая часть контекста.
C4. Distance-weighted liquidity
DOM5 недостаточно.
Должны существовать одновременно:
raw DOM5
weighted DOM
distance-weighted ADD
distance-weighted PULL
Снять 50 контрактов в одном тике от best намного важнее, чем снять 50 в десяти тиках.
C5. Модель уровня
В конце Level Engine будет выдавать не просто:
DEFENDED R74
а:
LEVEL 4590.0 ASK
hold_probability = 0.82
break_probability = 0.18
Но вероятность появляется только после офлайн-калибровки.
До обучения UI продолжает показывать наш heuristic reliability.
—
Этап D. v0.76 — Scenario Evidence 2.0
Здесь внедряем отложенные вами идеи.
D1. ScnLadder
Не отдельный сигнал, а характеристика кинематики тренда.
Храним последние ACCEPT:
A1 = 4594.0
A2 = 4591.2
A3 = 4588.8
Получаем:
spacing
spacing acceleration
compression
expansion
То есть:
3.0t
5.0t
8.0t
→ импульс ускоряется.
А:
9t
5t
2t
→ trend compression / exhaustion.
Я бы не добавлял сразу ±15 к score.
Сначала пишем:
ladder_spacing
ladder_spacing_prev
ladder_ratio
в БД.
После статистики решаем, какой у них вес.
D2. Aggression/Progress Divergence
Идея хорошая:
огромная агрессия
+
нет движения
Но тоже не называем сразу iceberg.
Создаём сырые evidence:
aggr_no_progress_hits
aggr_no_progress_volume
aggr_no_progress_duration
Например:
BUY 15
BUY 10
BUY 18
quote move 0/1
Это объективный факт.
Интерпретация:
possible absorption / hidden liquidity
D3. Extreme Defense
Кейс V-разворота:
агрессор давит
→ экстремум не проходит
→ DEFENDED
→ ABS
→ противоположная INIT
Это действительно сильная модель.
Но я бы не делал:
ScnStart(OPPOSITE, BUILDUP)
сразу после одного DEFENDED+ABS.
Лучше новый evidence:
EXTREME_DEFENSE
с score из:
defended
reliability
execution
replenishment
absorption
no-progress
opposite initiative
И только последовательность:
STALL
+ EXTREME DEFENSE
+ opposite INIT
+ reclaim
может дать:
REVERSAL
Это защитит нас от ложных V-разворотов.
—
Этап E. v0.77 — точная база будущих outcomes
Здесь есть один критический момент, которого в исходном плане недостаточно.
Чтобы действительно точно считать:
MFE
MAE
time_to_MAE
max_adverse_duration
TP/SL кто был первым
одних агрегатов и 1-секундных snapshots недостаточно.
Нужна траектория цены.
Поэтому добавляем компактный price_tape.
Не обязательно отдельная SQLite-строка на каждый тик.
Лучше:
tape_chunks
-----------
start_time_msc
end_time_msc
count
payload BLOB
В payload delta-encoding:
dt_ms
last_tick_delta
bid_tick_delta
ask_tick_delta
volume
flags
Запись также транзакциями.
Это даст точный офлайн ответ:
после сигнала сначала было +11 тиков или -6?
Без такого tape результаты могут быть приблизительными.
—
Этап F. Offline Labels — без будущего внутри MQL5
Здесь полностью согласен с вашим документом: labels считаются после завершения горизонта, а не внутри индикатора.
Для каждого потенциального входа:
signal_time
scenario_uid
event_uid
direction
entry_tick
MFE
MAE
time_to_MFE
time_to_MAE
max_adverse_duration
time_under_water
recovery_time
TP first?
SL first?
timeout?
is_censored
max_adverse_duration_ms обязательно оставляем.
Он отвечает на очень практичный вопрос:
сигнал был правильным, но рынок заставил сидеть против позиции 3 секунды или 90 секунд?
Это разные по качеству сетапы.
—
Этап G. Signal Candidates
До ML я бы выделил всего три основных семейства сигналов.
Это поможет не обучать систему на хаосе.
CONTINUATION
BREAK
→ ACCEPT
→ RETEST
→ HELD
→ renewed initiative
Пример того, что вы уже разбирали на нисходящем движении.
TRAP / REVERSAL
BREAK
→ fast RECLAIM
→ extreme defense
→ opposite INIT
VACUUM BREAK
large pull
+ path open
+ low opposing defense
+ real sweep
+ acceptance
Но VACUUM без подтверждения остаётся WATCH, а не сигналом.
—
Этап H. Quality Gate
Это должно стоять перед любой моделью.
Если данные плохие — никаких сигналов независимо от probability.
Например:
AQ2 required
depth_truncated = false
DOM baseline ready
book settled
latency <= max
anchor level known
tick direction reliable
minimum flow volume reached
Тогда модель не будет пытаться «угадывать» на плохом фиде.
В UI:
DATA GOOD
или:
DATA UNSTABLE
—
Этап I. v0.78 — Parquet pipeline
Ваш документ правильно фиксирует принцип:
SQLite — сбор; Parquet — обучение.
Пайплайн:
SQLite
↓
integrity validation
↓
JOIN by UUID / px_tick
↓
labels
↓
normalization
↓
Parquet
↓
training
Нормализация только офлайн:
volume percentile
rolling z-score
ATR ticks
relative spread
relative DOM depth
relative execution
session percentile
Не в MQL5.
—
Этап J. Regime Engine
Не надо заставлять один порог работать одинаково во всех состояниях рынка.
Например:
LOW VOL ROTATION
HIGH VOL TREND
THIN BOOK
HEAVY BOOK
OPEN IMPULSE
MIDSESSION QUIET
И один BRK=75 в них означает совершенно разные вещи.
Regime определяется офлайн.
Потом получаем:
P(CONT | regime=TREND)
P(CONT | regime=ROTATION)
—
Этап K. Обучение: я бы начинал не с нейросети
У нас структурированные табличные данные.
Первая линия:
Logistic Regression baseline
↓
LightGBM / XGBoost
Нейросеть только если она действительно выигрывает out-of-sample.
Причём я бы не начинал с одной гигантской модели.
Лучше Триада моделей.
Level Model
P(level holds)
P(level breaks)
Context Model
P(continuation)
P(reversal)
P(trap)
Execution Model
P(target before stop | entry now)
А после них маленький meta-model:
LEVEL
+ CONTEXT
+ EXECUTION
↓
FINAL P
Вот это уже буквально соответствует вашей «Триаде Потока».
—
Этап L. Валидация
Не просто:
train 80%
valid 20%
Этого мало.
Нужен walk-forward:
TRAIN ───────────────
VALID ───
TRAIN ───────────────
VALID ───
С purging/embargo на длину label horizon.
Иначе соседние события одного и того же движения попадут и в train, и в validation.
Главные метрики я бы поставил такие:
Precision при выбранном coverage
Signals/day
Expected ticks after costs
Profit factor
MAE/MFE
max adverse duration
Brier score
Probability calibration
false breakout rate
max drawdown
latency sensitivity
Особенно важно:
P=0.80 должно действительно выигрывать примерно в 80% случаев по выбранному определению outcome.
—
Этап M. v0.79 — ExecHint SHADOW
Только после калибровки.
Никакой торговли.
Microscope будет показывать:
CONTEXT DOWN
LEVEL 4590.0 ASK HOLD 84%
SETUP CONTINUATION
MODEL 78%
QUALITY GOOD
ACTION READY SELL
либо:
ACTION WAIT
или:
NO TRADE — LEVEL UNCLEAR
Все hint записываются в БД, но ничего не исполняется.
Так мы получим paper/shadow statistics на новых данных.
—
Этап N. v0.80 — Production Signal Engine
Только после shadow validation появляется настоящий SignalCandidate:
struct PotokSignal
{
UUIDv7 signal_uid;
UUIDv7 scenario_uid;
UUIDv7 event_uid;
UUIDv7 level_uid;
int dir;
int type;
long time_msc;
long entry_tick;
long anchor_tick;
long invalidation_tick;
double p_level;
double p_context;
double p_execution;
double p_final;
int regime_id;
int quality;
int latency_ms;
};
Сигнал появляется только если:
quality PASS
AND
level probability sufficient
AND
context probability sufficient
AND
execution probability sufficient
AND
expected value > costs
AND
latency acceptable
Вот тогда это уже не:
BRK 81
а:
SELL CONTINUATION
ANCHOR 4590.0
P 0.83
QUALITY A
—
Что остаётся на графике M1
Я бы не расширял M1 обратно.
Оставляем:
● Big Trade
— ICE
— Sweep
— Break
━━ Wall
А позже добавляем только конечный подтверждённый сигнал, если он доказал статистическую пользу.
Вся внутренняя механика:
STALL
REV WATCH
EDGE
REST
WDOM
Ladder
divergence
остаётся в Microscope/DB.
—
Итоговый маршрут по версиям
| Версия | Главная работа | Результат |
| 0.73 | deterministic UUIDv7, scenario_uid, event_uid, config hash, golden replay | надёжная идентичность |
| 0.74 | scenarios + scn_transitions, связь event→scenario, raw feature layer | правильный датасет |
| 0.75 | level_uid, Level Engine, migration/velocity/distance features | точные уровни |
| 0.76 | ScnLadder, aggression/no-progress, extreme defense, Scenario Evidence 2.0 | качественный контекст |
| 0.77 | compact price tape, outcomes/labels pipeline | объективная оценка |
| 0.78 | Parquet, regime, walk-forward, Level/Context/Execution models | калиброванные вероятности |
| 0.79 | ExecHint shadow mode | проверка live без торговли |
| 0.80 | Final Signal Engine | статистически подтверждённые сигналы |
| 1.00 | замороженный production core + модель/пороги/versioning | релизная система |
Это немного отличается от предложенного в файле порядка фаз 0–7, но сохраняет его ключевые принципы и делает отдельный этап именно для уровней, без которых качественный signal engine всё равно будет слабым. Сам документ правильно ставит детерминизм, raw features, labels, Parquet и версионирование как обязательные опоры всей системы.
Что делать прямо следующим
Следующий файл я бы делал v0.73 Identity & Determinism, без новых детекторов и без визуальных изменений:
deterministic UUIDv7
event_uid
scenario_uid
events.scenario_uid
phase_at_event
scenarios
scn_transitions
config_hash
schema 0.73
И отдельно сразу заложил level_uid тип, но пока не активировал его, чтобы в v0.75 не менять снова всю UUID-инфраструктуру.
Это будет последний инфраструктурный рефакторинг перед тем, как мы начнём уже системно улучшать уровни → контекст → сигналы.