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