← Назад к главному
# position_machine.rs - Фазовая машина позиций ## 📋 Назначение модуля `PositionMachine` управляет фазами позиции на основе количества резок (`cut_count`) и текущего состояния позиции. **Основная логика:** 1. **Определение фазы** по `cut_count` и состоянию позиции (BOTH или Single) 2. **Обновление фазы** на каждом тике через `update_phase()` 3. **Обработка резки** через `on_cut()` (увеличение `cut_count`) 4. **Обработка выхода** через `on_exit()` (сброс машины) **Фазы:** - `Single` - односторонняя позиция (LONG или SHORT) - `Both` - BOTH режим (первая резка ещё не была) - `Boosted` - BOTH режим после первой резки - `WindDown` - BOTH режим после второй резки - `ExitReady` - BOTH режим после трёх+ резок (готов к выходу) --- ## 🏗️ Структура модуля ### Enum `PositionPhase` **Строки:** 3-10 ```rust #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum PositionPhase { Single, // Односторонняя позиция (LONG или SHORT) Both, // BOTH режим (первая резка ещё не была) Boosted, // BOTH режим после первой резки WindDown, // BOTH режим после второй резки ExitReady, // BOTH режим после трёх+ резок (готов к выходу) } ``` **Назначение:** Определяет текущую фазу позиции **Интерпретация:** - `Single` → Нет хеджа, только одна сторона - `Both` → Есть хедж (две стороны), первая резка - `Boosted` → Есть хедж, вторая резка (BOOST) - `WindDown` → Есть хедж, третья резка (сужение) - `ExitReady` → Есть хедж, 4+ резка (готов к выходу) --- ### Структура `PositionMachine` **Строки:** 12-16 ```rust pub struct PositionMachine { pub phase: PositionPhase, // Текущая фаза pub cut_count: u32, // Количество резок pub boosted_once: bool, // Был ли BOOST-цикл? } ``` **Назначение:** Состояние фазовой машины **Поля:** - `phase` - текущая фаза (Single, Both, Boosted, WindDown, ExitReady) - `cut_count` - количество резок (увеличивается при каждой резке) - `boosted_once` - флаг, была ли первая резка (BOOST) **Пример:** ```rust PositionMachine { phase: PositionPhase::Both, cut_count: 1, boosted_once: true, } ``` --- ## 🔄 Основные функции ### Функция `new()` **Строки:** 19-25 ```rust pub fn new() -> Self { Self { phase: PositionPhase::Single, cut_count: 0, boosted_once: false, } } ``` **Назначение:** Создание новой инстанции машины **Начальное состояние:** - `phase = Single` - `cut_count = 0` - `boosted_once = false` --- ### Функция `reset()` **Строки:** 27-31 ```rust pub fn reset(&mut self) { self.phase = PositionPhase::Single; self.cut_count = 0; self.boosted_once = false; } ``` **Назначение:** Сброс состояния машины **Когда вызывается:** - При выходе из позиции (`on_exit()`) - При начале новой сессии **Пример:** ``` До: PositionMachine { phase: PositionPhase::ExitReady, cut_count: 4, boosted_once: true, } reset(): После: PositionMachine { phase: PositionPhase::Single, cut_count: 0, boosted_once: false, } ``` --- ### Функция `update_phase()` **Строки:** 33-50 **Сигнатура:** ```rust pub fn update_phase(&mut self, pos: &PositionSnapshot) ``` **Назначение:** Актуализация фазы на основе текущего состояния позиции **Алгоритм:** ```rust pub fn update_phase(&mut self, pos: &PositionSnapshot) { let has_long = pos.long_qty > 0.0; let has_short = pos.short_qty > 0.0; if has_long && has_short { if self.cut_count == 0 { self.phase = PositionPhase::Both; } else if self.cut_count == 1 { self.phase = PositionPhase::Boosted; } else if self.cut_count == 2 { self.phase = PositionPhase::WindDown; } else { self.phase = PositionPhase::ExitReady; } } else { self.phase = PositionPhase::Single; } } ``` **Логика:** 1. **Определение типа позиции:** ``` has_long = long_qty > 0 has_short = short_qty > 0 ``` 2. **Если BOTH (обе стороны активны):** ``` if has_long && has_short: if cut_count == 0: phase = Both else if cut_count == 1: phase = Boosted else if cut_count == 2: phase = WindDown else: phase = ExitReady ``` 3. **Если Single (только одна сторона):** ``` else: phase = Single ``` **Таблица фаз:** | cut_count | Фаза | Описание | |-----------|-------|----------| | 0 | Single | Односторонняя позиция | | 0 | Both | BOTH режим, первая резка ещё не была | | 1 | Boosted | BOTH режим, первая резка сделана | | 2 | WindDown | BOTH режим, вторая резка сделана | | 3+ | ExitReady | BOTH режим, готов к выходу | **Пример 1: Single → Both** ``` До: long_qty = 1000, short_qty = 0 cut_count = 0 phase = Single Хедж: OpenShort(1000, price) После: long_qty = 1000, short_qty = 1000 has_long = true, has_short = true ✅ update_phase(): cut_count == 0? ✅ phase = Both ``` **Пример 2: Both → Boosted** ``` До: long_qty = 1000, short_qty = 1000 cut_count = 0 phase = Both Резка: cut_count += 1 → cut_count = 1 После: update_phase(): has_long && has_short ✅ cut_count == 1? ✅ phase = Boosted ``` **Пример 3: Boosted → WindDown** ``` До: long_qty = 1000, short_qty = 1000 cut_count = 1 phase = Boosted Резка: cut_count += 1 → cut_count = 2 После: update_phase(): has_long && has_short ✅ cut_count == 2? ✅ phase = WindDown ``` **Пример 4: WindDown → ExitReady** ``` До: long_qty = 1000, short_qty = 1000 cut_count = 2 phase = WindDown Резка: cut_count += 1 → cut_count = 3 После: update_phase(): has_long && has_short ✅ cut_count == 3? ❌ phase = ExitReady ``` **Пример 5: ExitReady → Single** ``` До: long_qty = 1000, short_qty = 1000 cut_count = 4 phase = ExitReady Выход: CloseLong(1000, price) CloseShort(1000, price) После: long_qty = 0, short_qty = 0 has_long = false ❌ has_short = false ❌ update_phase(): has_long && has_short ❌ phase = Single ``` --- ### Функция `on_cut()` **Строки:** 52-55 ```rust pub fn on_cut(&mut self) { self.cut_count += 1; self.boosted_once = true; } ``` **Назначение:** Обработка резки **Действия:** 1. Увеличение `cut_count` на 1 2. Установка `boosted_once = true` **Пример:** ``` До: cut_count = 0 boosted_once = false on_cut(): cut_count = 0 + 1 = 1 boosted_once = true После: cut_count = 1 boosted_once = true ``` **Когда вызывается:** - После каждой резки в `VolatilityCycle::execute()` - В `BothStrategy::process()` после резки --- ### Функция `on_exit()` **Строки:** 57-59 ```rust pub fn on_exit(&mut self) { self.reset(); } ``` **Назначение:** Обработка выхода из позиции **Действия:** Вызов `reset()` **Пример:** ``` До: phase = ExitReady cut_count = 4 boosted_once = true on_exit(): reset() После: phase = Single cut_count = 0 boosted_once = false ``` **Когда вызывается:** - При финальном выходе в `BothStrategy::process()` (ExitReady фаза) - При завершении сессии --- ## 📊 Граф переходов между фазами ``` ┌─────────────────────────────────────────────────────────────┐ │ PositionMachine │ └─────────────────────────────────────────────────────────────┘ │ │ new() / reset() ▼ ┌─────────────┐ │ Single │ │ cut_count=0 │ └─────────────┘ │ │ has_long && has_short │ (хедж открылся) ▼ ┌─────────────┐ │ Both │ │ cut_count=0 │ └─────────────┘ │ │ on_cut() ▼ ┌─────────────┐ │ Boosted │ │ cut_count=1 │ │ boosted_once=│ │ true │ └─────────────┘ │ │ on_cut() ▼ ┌─────────────┐ │ WindDown │ │ cut_count=2 │ └─────────────┘ │ │ on_cut() ▼ ┌─────────────┐ │ ExitReady │ │ cut_count=3+│ └─────────────┘ │ │ Close ALL (on_exit) ▼ reset() │ ▼ ┌─────────────┐ │ Single │ │ cut_count=0 │ └─────────────┘ ``` --- ## 🎯 Полный жизненный цикл фаз ### Сценарий: Полный цикл от входа до выхода ``` 00:00 → Вход в LONG 1000 PositionMachine { phase: Single, cut_count: 0, boosted_once: false, } 01:00 → Первая резка (SingleStrategy) Закрываем 50%, перезакупаем 50% on_cut(): cut_count = 1 boosted_once = true PositionMachine { phase: Single, // всё ещё Single cut_count: 1, boosted_once: true, } 02:00 → Хедж (SingleStrategy) Открываем SHORT 1000 update_phase(): has_long && has_short ✅ cut_count = 1? phase = Boosted PositionMachine { phase: Boosted, cut_count: 1, boosted_once: true, } 03:00 → Резка (BothStrategy) Закрываем прибыльную, резаем убыточную on_cut(): cut_count = 2 update_phase(): cut_count = 2? phase = WindDown PositionMachine { phase: WindDown, cut_count: 2, boosted_once: true, } 04:00 → Резка (BothStrategy) on_cut(): cut_count = 3 update_phase(): cut_count = 3? phase = ExitReady PositionMachine { phase: ExitReady, cut_count: 3, boosted_once: true, } 05:00 → Резка (BothStrategy) on_cut(): cut_count = 4 update_phase(): cut_count = 4? phase = ExitReady PositionMachine { phase: ExitReady, cut_count: 4, boosted_once: true, } 06:00 → Выход (BothStrategy) Прибыль >= 0.5% Close ALL: - CloseLong(1000, price) - CloseShort(1000, price) on_exit(): reset() PositionMachine { phase: Single, cut_count: 0, boosted_once: false, } → Сессия завершена, ищем новую пару ``` --- ## ⚠️ Критические моменты ### 1. Обновление фазы на каждом тике **Вызывается:** `update_phase(pos)` на каждом тике **Зачем:** Фаза может измениться при: - Открытии хеджа (Single → Both) - Резке (Both → Boosted → WindDown → ExitReady) - Закрытии одной стороны (BOTH → Single) **Пример:** ``` Тик 1: long_qty = 1000, short_qty = 0 phase = Single Тик 2: long_qty = 1000, short_qty = 1000 (открыт хедж) update_phase(): has_long && has_short ✅ cut_count == 0? phase = Both ✅ ``` --- ### 2. Различие cut_count и boosted_once **`cut_count`:** - Увеличивается при каждой резке - Определяет фазу: 0 → Both, 1 → Boosted, 2 → WindDown, 3+ → ExitReady **`boosted_once`:** - Устанавливается в `true` после первой резки - Остаётся `true` до `reset()` - Используется в `VolatilityCycle` для определения порога резки (3% или 2%) **Пример:** ``` До первой резки: cut_count = 0 boosted_once = false Порог резки = 3.0% После первой резки: cut_count = 1 boosted_once = true Порог резки = 2.0% После второй резки: cut_count = 2 boosted_once = true (остаётся true) Порог резки = 2.0% ``` --- ### 3. Transition из BOTH в Single **Когда происходит:** - При полной резке убыточной стороны (в `VolatilityCycle`) - При закрытии одной стороны вручную **Логика в `update_phase()`:** ```rust else { self.phase = PositionPhase::Single; } ``` **Пример:** ``` До: long_qty = 1000, short_qty = 1000 phase = Both Резка: Закрываем SHORT полностью (rem_loss = 0) После: long_qty = 1000, short_qty = 0 has_long && has_short ❌ phase = Single ✅ ``` --- ### 4. Transition из ExitReady в Single **Когда происходит:** - При финальном выходе (CLOSE ALL) - При закрытии обеих сторон **Пример:** ``` До: long_qty = 100, short_qty = 100 phase = ExitReady cut_count = 4 Выход: CloseLong(100, price) CloseShort(100, price) После: long_qty = 0, short_qty = 0 has_long && has_short ❌ phase = Single ✅ ``` --- ## 📊 Параметры и их источники | Параметр | Тип | Источник | |----------|-----|----------| | `phase` | PositionPhase | PositionMachine (внутреннее состояние) | | `cut_count` | u32 | PositionMachine (увеличивается при on_cut) | | `boosted_once` | bool | PositionMachine (устанавливается при on_cut) | | `long_qty` | f64 | PositionSnapshot | | `short_qty` | f64 | PositionSnapshot | --- ## 🔗 Интеграция с другими модулями ### 1. PositionSnapshot **Используется:** Для определения типа позиции (BOTH или Single) **Используемые поля:** ```rust pos.long_qty pos.short_qty ``` **Пример:** ```rust let has_long = pos.long_qty > 0.0; let has_short = pos.short_qty > 0.0; if has_long && has_short { // BOTH режим } else { // Single режим } ``` --- ### 2. VolatilityCycle **Используется:** `boosted_once` для определения порога резки **Пример:** ```rust let trigger = if boosted_once { 2.0 } else { 3.0 }; ``` --- ### 3. BothStrategy **Используется:** Для определения фазы и соответствующей логики **Пример:** ```rust match machine.phase { PositionPhase::Single => { /* Пропускаем */ } PositionPhase::Both | PositionPhase::Boosted | PositionPhase::WindDown => { // Резки, выравнивание и т.д. } PositionPhase::ExitReady => { // Финальный выход } } ``` --- ## 🔄 Поток данных ``` live_step() ↓ ├─→ PositionMachine::new() (при старте сессии) ├─→ update_phase() (на каждом тике) │ ├─→ has_long = long_qty > 0 │ ├─→ has_short = short_qty > 0 │ │ │ ├─→ if has_long && has_short: │ │ ├─→ cut_count == 0? → phase = Both │ │ ├─→ cut_count == 1? → phase = Boosted │ │ ├─→ cut_count == 2? → phase = WindDown │ │ └─→ cut_count >= 3? → phase = ExitReady │ │ │ └─→ else: │ └─→ phase = Single │ ├─→ on_cut() (при каждой резке) │ ├─→ cut_count += 1 │ └─→ boosted_once = true │ └─→ on_exit() (при финальном выходе) └─→ reset() ``` --- ## 📝 Логирование **Примечание:** `PositionMachine` сам не логируется, логи выводятся в вызывающих модулях (`BothStrategy`, `VolatilityCycle`, и т.д.) --- ## 🚀 Использование в коде ### Пример 1: Создание машины ```rust let mut machine = PositionMachine::new(); // phase = Single, cut_count = 0, boosted_once = false ``` ### Пример 2: Обновление фазы ```rust machine.update_phase(&pos); // Фаза обновлена на основе текущего состояния позиции ``` ### Пример 3: Обработка резки ```rust // После резки machine.on_cut(); // cut_count += 1, boosted_once = true machine.update_phase(&pos); // Фаза обновлена (например, Both → Boosted) ``` ### Пример 4: Финальный выход ```rust // При финальном выходе machine.on_exit(); // Сброс: phase = Single, cut_count = 0, boosted_once = false ``` ### Пример 5: Проверка фазы ```rust match machine.phase { PositionPhase::Single => { println!("Single режим - обрабатывается SingleStrategy"); } PositionPhase::Both | PositionPhase::Boosted | PositionPhase::WindDown => { println!("BOTH режим - обрабатывается BothStrategy"); } PositionPhase::ExitReady => { println!("ExitReady - финальный выход"); } } ``` --- ## 📊 Важные формулы ### 1. Определение типа позиции ```rust let has_long = pos.long_qty > 0.0; let has_short = pos.short_qty > 0.0; let is_both = has_long && has_short; let is_single = !is_both; ``` ### 2. Определение фазы по cut_count (BOTH) ```rust let phase = if cut_count == 0 { PositionPhase::Both } else if cut_count == 1 { PositionPhase::Boosted } else if cut_count == 2 { PositionPhase::WindDown } else { PositionPhase::ExitReady }; ``` --- ## 🔍 Проверка работы ### Проверка 1: Single → Both 1. Открыть LONG позицию 2. Проверить что `phase = Single` 3. Открыть SHORT (хедж) 4. Вызвать `update_phase()` 5. Проверить что `phase = Both` ### Проверка 2: Both → Boosted 1. Открыть BOTH позицию 2. Вызвать `update_phase()` → `phase = Both` 3. Сделать резку 4. Вызвать `on_cut()` → `cut_count = 1` 5. Вызвать `update_phase()` → `phase = Boosted` ### Проверка 3: Boosted → WindDown → ExitReady 1. Сделать ещё одну резку 2. Вызвать `on_cut()` → `cut_count = 2` 3. Вызвать `update_phase()` → `phase = WindDown` 4. Сделать ещё одну резку 5. Вызвать `on_cut()` → `cut_count = 3` 6. Вызвать `update_phase()` → `phase = ExitReady` ### Проверка 4: ExitReady → Single 1. Закрыть обе стороны 2. Вызвать `update_phase()` → `phase = Single` --- ## 📚 Связанные файлы | Файл | Связь | |------|-------| | `src/live/state.rs` | PositionSnapshot структура | | `src/live/volatility_cycle.rs` | Использует `boosted_once` | | `src/live/strategy_both.rs` | Использует `phase` для логики | | `src/live/strategy_single.rs` | Работает в Single фазе | --- ## 🎯 Резюме **Что делает PositionMachine:** 1. ✅ Управляет фазами позиции (Single, Both, Boosted, WindDown, ExitReady) 2. ✅ Определяет фазу по `cut_count` и состоянию позиции 3. ✅ Увеличивает `cut_count` при каждой резке 4. ✅ Устанавливает `boosted_once` после первой резки 5. ✅ Сбрасывается при финальном выходе **Фазы:** - `Single` - односторонняя позиция (long_qty XOR short_qty) - `Both` - BOTH режим, cut_count = 0 - `Boosted` - BOTH режим, cut_count = 1 - `WindDown` - BOTH режим, cut_count = 2 - `ExitReady` - BOTH режим, cut_count = 3+ **Переходы:** - `Single → Both` (при открытии хеджа) - `Both → Boosted` (on_cut()) - `Boosted → WindDown` (on_cut()) - `WindDown → ExitReady` (on_cut()) - `ExitReady → Single` (on_exit() / close all) **Особенности:** - 🔥 `boosted_once` используется для определения порога резки (3% или 2%) - 🔥 `cut_count` определяет фазу в BOTH режиме --- **Дата создания:** 2026-02-22 **Автор:** Claude Code Assistant **Версия:** 1.0