← Назад к главному
# actions.rs - Модуль действий LIVE-движка
## 📋 Назначение модуля
`LiveAction` определяет возможные действия в LIVE режиме и предоставляет метод для их применения к позиции.
**Основная логика:**
1. **Определение действий** (открытие/закрытие позиций, установка параметров)
2. **Применение к позиции** через `apply_to_position()`
3. **Обновление состояния** позиции (PnL, маржа, режим)
**Используется во всех LIVE модулях:**
- `strategy_core.rs` - вход в позицию
- `strategy_single.rs` - управление односторонней позицией
- `strategy_both.rs` - управление BOTH позицией
- `volatility_cycle.rs` - резки
- `hedge_guard.rs` - экстренный хедж
---
## 🏗️ Структура модуля
### Enum `LiveAction`
**Строки:** 18-48
```rust
#[derive(Clone, Debug)]
pub enum LiveAction {
/// Базовое количество контрактов для всей сессии
SetBaseQty(f64),
/// Задать плечо на сессию (20x, 50x, ...)
SetLeverage(f64),
/// Задать contract_value (quanto_multiplier)
SetContractValue(f64),
/// Открыть/докупить лонг (qty контрактов по цене price)
OpenLong(f64, f64),
/// Открыть/докупить шорт
OpenShort(f64, f64),
/// Закрыть часть/весь лонг (qty, price, is_profit_side)
CloseLong(f64, f64, bool),
/// Закрыть часть/весь шорт (qty, price, is_profit_side)
CloseShort(f64, f64, bool),
/// Добавить в реализованный PnL
Realize(f64),
/// Сменить режим (Flat/Single/Both/Recovery)
SetMode(LiveMode),
/// Сбросить счётчик резок
ResetCutStreak,
}
```
**Назначение:** Определение возможных действий
**Действия и их параметры:**
| Действие | Параметры | Описание |
|----------|------------|----------|
| `SetBaseQty(q)` | `f64` | Установить базовое количество контрактов |
| `SetLeverage(lev)` | `f64` | Установить плечо |
| `SetContractValue(v)` | `f64` | Установить значение контракта |
| `OpenLong(qty, price)` | `f64, f64` | Открыть/докупить LONG |
| `OpenShort(qty, price)` | `f64, f64` | Открыть/докупить SHORT |
| `CloseLong(qty, price, is_profit)` | `f64, f64, bool` | Закрыть LONG (qty, цена, это прибыль?) |
| `CloseShort(qty, price, is_profit)` | `f64, f64, bool` | Закрыть SHORT |
| `Realize(pnl)` | `f64` | Добавить в реализованный PnL |
| `SetMode(mode)` | `LiveMode` | Сменить режим |
| `ResetCutStreak` | - | Сбросить счётчик резок |
---
## 🔄 Основная функция
### Функция `apply_to_position()`
**Строки:** 51-172
**Сигнатура:**
```rust
pub fn apply_to_position(pos: &mut PositionSnapshot, actions: &[LiveAction])
```
**Назначение:** Применение списка действий к позиции
**Алгоритм (перебор действий):**
```rust
for act in actions {
match act {
// ... обработка каждого действия ...
}
}
```
---
### Обработка `SetBaseQty(q)`
**Строки:** 55-57
```rust
LiveAction::SetBaseQty(q) => {
pos.base_qty = (*q).max(0.0);
}
```
**Назначение:** Установить базовое количество контрактов
**Логика:**
- `base_qty = max(q, 0.0)`
**Пример:**
```rust
actions = [SetBaseQty(1000.0)];
apply_to_position(&mut pos, &actions);
// pos.base_qty = 1000.0
```
---
### Обработка `SetLeverage(lev)`
**Строки:** 59-62
```rust
LiveAction::SetLeverage(lev) => {
pos.entry_leverage = *lev;
pos.recalc_margins();
}
```
**Назначение:** Установить плечо
**Логика:**
- `entry_leverage = lev`
- Пересчёт маржи: `recalc_margins()`
**Пример:**
```rust
actions = [SetLeverage(50.0)];
apply_to_position(&mut pos, &actions);
// pos.entry_leverage = 50.0
// Маржа пересчитана
```
---
### Обработка `SetContractValue(v)`
**Строки:** 64-67
```rust
LiveAction::SetContractValue(v) => {
pos.contract_value = if *v > 0.0 { *v } else { 1.0 };
pos.recalc_margins();
}
```
**Назначение:** Установить значение контракта
**Логика:**
- Если `v > 0` → `contract_value = v`
- Иначе → `contract_value = 1.0` (значение по умолчанию)
- Пересчёт маржи: `recalc_margins()`
**Пример:**
```rust
actions = [SetContractValue(100.0)];
apply_to_position(&mut pos, &actions);
// pos.contract_value = 100.0
// Маржа пересчитана
```
---
### Обработка `OpenLong(qty, price)`
**Строки:** 69-77
```rust
LiveAction::OpenLong(qty, price) => {
if *qty <= 0.0 {
continue;
}
// Используем PositionSnapshot::open_long, чтобы
// корректно поддерживать first_side, initial_entry_price,
// base_qty и mode.
pos.open_long(*qty, *price);
}
```
**Назначение:** Открыть/докупить LONG
**Логика:**
- Если `qty <= 0` → пропускаем
- Иначе → `pos.open_long(qty, price)`
**Что делает `open_long()`:**
1. Если `long_qty == 0` → первый вход:
- `entry_long = price`
- `long_qty = qty`
2. Иначе → добавление (dollar-cost averaging):
- `new_entry = (prev_entry × prev_qty + price × qty) / new_qty`
- `long_qty = new_qty`
3. Установка `base_qty = long_qty` (если `base_qty <= 0`)
4. Установка `first_side = "LONG"` (если пустая)
5. Установка `initial_entry_price = price` (если пустая)
6. Установка `mode = Both` (если `short_qty > 0`) или `Single`
7. Обновление `last_entry_time`
8. Пересчёт маржи
**Пример 1 (Первый вход):**
```rust
pos.long_qty = 0.0
actions = [OpenLong(1000.0, 0.002000)];
apply_to_position(&mut pos, &actions);
// pos.entry_long = 0.002000
// pos.long_qty = 1000.0
// pos.base_qty = 1000.0
// pos.first_side = "LONG"
// pos.initial_entry_price = 0.002000
// pos.mode = Single
```
**Пример 2 (Добавление):**
```rust
pos.long_qty = 1000.0
pos.entry_long = 0.002000
actions = [OpenLong(500.0, 0.002060)];
apply_to_position(&mut pos, &actions);
// new_qty = 1500
// new_entry = (0.002000 × 1000 + 0.002060 × 500) / 1500
// = (2.0 + 1.03) / 1500
// = 3.03 / 1500
// = 0.002020
// pos.entry_long = 0.002020 (средняя цена)
// pos.long_qty = 1500.0
// pos.mode = Both (если есть SHORT) или Single
```
---
### Обработка `OpenShort(qty, price)`
**Строки:** 79-84
```rust
LiveAction::OpenShort(qty, price) => {
if *qty <= 0.0 {
continue;
}
pos.open_short(*qty, *price);
}
```
**Назначение:** Открыть/докупить SHORT
**Логика:** Аналогично `OpenLong()` для SHORT
**Пример:**
```rust
pos.short_qty = 0.0
actions = [OpenShort(1000.0, 0.002000)];
apply_to_position(&mut pos, &actions);
// pos.entry_short = 0.002000
// pos.short_qty = 1000.0
// pos.base_qty = 1000.0
// pos.first_side = "SHORT"
// pos.initial_entry_price = 0.002000
// pos.mode = Single
```
---
### Обработка `CloseLong(qty, price, is_profit)`
**Строки:** 86-121
```rust
LiveAction::CloseLong(qty, _price, _is_profit) => {
if *qty <= 0.0 || pos.long_qty <= 0.0 {
continue;
}
// Сколько реально можем закрыть (не больше текущего объёма)
let close_qty = qty.min(pos.long_qty);
if close_qty >= pos.long_qty {
// Закрываем весь лонг
pos.long_qty = 0.0;
pos.entry_long = 0.0;
pos.long_margin = 0.0;
} else {
let prev_qty = pos.long_qty;
let new_qty = prev_qty - close_qty;
let ratio = if prev_qty > 0.0 {
new_qty / prev_qty
} else {
0.0
};
pos.long_qty = new_qty.max(0.0);
pos.long_margin *= ratio.max(0.0);
}
pos.recalc_margins();
pos.mode = if pos.long_qty > 0.0 && pos.short_qty > 0.0 {
LiveMode::Both
} else if pos.long_qty > 0.0 || pos.short_qty > 0.0 {
LiveMode::Single
} else {
LiveMode::Flat
};
}
```
**Назначение:** Закрыть LONG (полностью или частично)
**Параметры:**
- `qty` - сколько закрыть
- `price` - цена закрытия (не используется, PnL добавляется отдельно через `Realize`)
- `is_profit` - это прибыльная сторона (не используется)
**Логика:**
1. Если `qty <= 0` или `long_qty <= 0` → пропускаем
2. `close_qty = min(qty, long_qty)` (не больше текущего объёма)
3. Если `close_qty >= long_qty` → полное закрытие:
- `long_qty = 0`
- `entry_long = 0`
- `long_margin = 0`
4. Иначе → частичное закрытие:
- `new_qty = long_qty - close_qty`
- `ratio = new_qty / prev_qty`
- `long_margin *= ratio`
5. Пересчёт маржи
6. Определение режима
**Пример 1 (Полное закрытие):**
```rust
pos.long_qty = 1000.0
actions = [CloseLong(1000.0, 0.002060, true)];
apply_to_position(&mut pos, &actions);
// close_qty = 1000.0
// close_qty >= long_qty? 1000 >= 1000? ✅
// pos.long_qty = 0.0
// pos.entry_long = 0.0
// pos.long_margin = 0.0
// pos.mode = Single (если есть SHORT) или Flat
```
**Пример 2 (Частичное закрытие):**
```rust
pos.long_qty = 1000.0
pos.long_margin = 0.04 USDT
actions = [CloseLong(500.0, 0.002060, true)];
apply_to_position(&mut pos, &actions);
// close_qty = 500.0
// close_qty >= long_qty? 500 >= 1000? ❌
// new_qty = 1000 - 500 = 500
// ratio = 500 / 1000 = 0.5
// pos.long_qty = 500.0
// pos.long_margin = 0.04 × 0.5 = 0.02 USDT
// pos.mode = Both (если есть SHORT) или Single
```
---
### Обработка `CloseShort(qty, price, is_profit)`
**Строки:** 123-157
```rust
LiveAction::CloseShort(qty, _price, _is_profit) => {
if *qty <= 0.0 || pos.short_qty <= 0.0 {
continue;
}
let close_qty = qty.min(pos.short_qty);
if close_qty >= pos.short_qty {
// Закрываем весь шорт
pos.short_qty = 0.0;
pos.entry_short = 0.0;
pos.short_margin = 0.0;
} else {
let prev_qty = pos.short_qty;
let new_qty = prev_qty - close_qty;
let ratio = if prev_qty > 0.0 {
new_qty / prev_qty
} else {
0.0
};
pos.short_qty = new_qty.max(0.0);
pos.short_margin *= ratio.max(0.0);
}
pos.recalc_margins();
pos.mode = if pos.long_qty > 0.0 && pos.short_qty > 0.0 {
LiveMode::Both
} else if pos.long_qty > 0.0 || pos.short_qty > 0.0 {
LiveMode::Single
} else {
LiveMode::Flat
};
}
```
**Назначение:** Закрыть SHORT (полностью или частично)
**Логика:** Аналогично `CloseLong()` для SHORT
**Пример:**
```rust
pos.short_qty = 1000.0
actions = [CloseShort(1000.0, 0.002040, false)];
apply_to_position(&mut pos, &actions);
// pos.short_qty = 0.0
// pos.entry_short = 0.0
// pos.short_margin = 0.0
// pos.mode = Both (если есть LONG) или Flat
```
---
### Обработка `Realize(pnl)`
**Строки:** 159-161
```rust
LiveAction::Realize(pnl) => {
pos.realized_pnl += pnl;
}
```
**Назначение:** Добавить в реализованный PnL
**Логика:**
- `realized_pnl += pnl`
**Пример:**
```rust
pos.realized_pnl = 0.0
actions = [Realize(0.03)];
apply_to_position(&mut pos, &actions);
// pos.realized_pnl = 0.03 USDT
```
---
### Обработка `SetMode(mode)`
**Строки:** 163-165
```rust
LiveAction::SetMode(m) => {
pos.mode = m.clone();
}
```
**Назначение:** Сменить режим позиции
**Логика:**
- `mode = m`
**Пример:**
```rust
actions = [SetMode(LiveMode::Both)];
apply_to_position(&mut pos, &actions);
// pos.mode = Both
```
---
### Обработка `ResetCutStreak`
**Строки:** 167-169
```rust
LiveAction::ResetCutStreak => {
pos.cut_streak = 0;
}
```
**Назначение:** Сбросить счётчик резок
**Логика:**
- `cut_streak = 0`
**Пример:**
```rust
pos.cut_streak = 5
actions = [ResetCutStreak];
apply_to_position(&mut pos, &actions);
// pos.cut_streak = 0
```
---
## ⚠️ Критические моменты
### 1. PnL НЕ добавляется в `CloseLong` / `CloseShort`
**Комментарий в коде:**
```rust
/// Важное разделение:
/// - CloseLong/CloseShort НИЧЕГО НЕ ПИШЕТ в realized_pnl
/// (PnL добавляется отдельным действием Realize(pnl)).
```
**Что это значит:**
- `CloseLong` / `CloseShort` только закрывают позицию
- PnL добавляется отдельно через `Realize(pnl)`
**Пример:**
```rust
actions = [
CloseLong(1000.0, 0.002060, true),
Realize(0.06),
];
apply_to_position(&mut pos, &actions);
// 1. CloseLong: long_qty = 0
// 2. Realize: realized_pnl = 0.06
```
---
### 2. `price` в `CloseLong` / `CloseShort` не используется
**Комментарий в коде:**
```rust
LiveAction::CloseLong(qty, _price, _is_profit) => {
```
**Зачем `_price` и `_is_profit`:**
- Параметры есть, но не используются
- Возможно для будущего использования
---
### 3. Использование `pos.open_long()` вместо прямого изменения
**Комментарий в коде:**
```rust
// Используем PositionSnapshot::open_long, чтобы
// корректно поддерживать first_side, initial_entry_price,
// base_qty и mode.
pos.open_long(*qty, *price);
```
**Почему это важно:**
- `open_long()` корректно обновляет все поля
- `first_side`, `initial_entry_price`, `base_qty`, `mode`
---
## 🎯 Примеры использования
### Пример 1: Вход в позицию
```rust
let mut pos = PositionSnapshot::new("MEMES_USDT");
let actions = vec![
SetBaseQty(1000.0),
SetLeverage(50.0),
SetContractValue(100.0),
OpenLong(1000.0, 0.002000),
];
apply_to_position(&mut pos, &actions);
// pos.base_qty = 1000.0
// pos.entry_leverage = 50.0
// pos.contract_value = 100.0
// pos.long_qty = 1000.0
// pos.entry_long = 0.002000
// pos.mode = Single
```
---
### Пример 2: Резка в BOTH режиме
```rust
let mut pos = PositionSnapshot::new("MEMES_USDT");
pos.long_qty = 1000.0;
pos.short_qty = 1000.0;
let actions = vec![
CloseLong(1000.0, 0.002060, true), // Закрыть LONG (прибыльный)
CloseShort(500.0, 0.002060, false), // Закрыть часть SHORT (убыточный)
Realize(0.06), // Прибыль от LONG
Realize(-0.03), // Убыток от SHORT
OpenLong(1000.0, 0.002060), // Перезакупать LONG
];
apply_to_position(&mut pos, &actions);
// pos.long_qty = 1000.0, pos.entry_long = 0.002060
// pos.short_qty = 500.0, pos.entry_short = 0.002000
// pos.realized_pnl = 0.0 + 0.06 + (-0.03) = 0.03
// pos.mode = Both
```
---
### Пример 3: Экстренный хедж
```rust
let mut pos = PositionSnapshot::new("MEMES_USDT");
pos.long_qty = 1000.0;
let actions = vec![
OpenShort(1000.0, 0.001970), // Хедж
];
apply_to_position(&mut pos, &actions);
// pos.short_qty = 1000.0
// pos.entry_short = 0.001970
// pos.mode = Both
```
---
## 📚 Связанные файлы
| Файл | Связь |
|------|-------|
| `src/live/state.rs` | PositionSnapshot структура |
| `src/live/strategy_core.rs` | Использует LiveAction |
| `src/live/strategy_single.rs` | Использует LiveAction |
| `src/live/strategy_both.rs` | Использует LiveAction |
| `src/live/volatility_cycle.rs` | Использует LiveAction |
| `src/live/hedge_guard.rs` | Использует LiveAction |
---
## 🎯 Резюме
**Что делает actions.rs:**
1. ✅ Определяет возможные действия (Open, Close, Set, Realize)
2. ✅ Применяет действия к позиции через `apply_to_position()`
3. ✅ Обновляет состояние позиции (qty, entry, PnL, маржа, режим)
4. ✅ Использует `pos.open_long()` и `pos.open_short()` для корректного обновления
**Действия:**
- `SetBaseQty` - установить базовое количество
- `SetLeverage` - установить плечо
- `SetContractValue` - установить значение контракта
- `OpenLong` - открыть/докупить LONG
- `OpenShort` - открыть/докупить SHORT
- `CloseLong` - закрыть LONG
- `CloseShort` - закрыть SHORT
- `Realize` - добавить в реализованный PnL
- `SetMode` - сменить режим
- `ResetCutStreak` - сбросить счётчик резок
**Особенности:**
- 🔥 PnL добавляется отдельно через `Realize()` (не в `Close*`)
- 🔥 Использует `pos.open_long()` для корректного обновления всех полей
- 🔥 Автоматическое определение режима после закрытия
---
**Дата создания:** 2026-02-22
**Автор:** Claude Code Assistant
**Версия:** 1.0