← Назад к главному
# 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