← Назад к главному
# strategy_single.rs - Модуль управления односторонней позицией ## 📋 Назначение модуля `SingleStrategy` управляет односторонней позицией (только LONG или только SHORT). **Основная логика:** 1. **Первая резка** при +3% чистого PnL → закрыть 50%, перезакупить 50% 2. **Вторая резка** при +1.5% от точки пересборки → CLOSE ALL 3. **Хедж** при -1.5% от точки пересборки → открыть противоположную сторону (переход в BOTH) 4. **Cooldown** - не резать/хеджировать N секунд после входа **Переход в BOTH:** При хедже открывается противоположная сторона того же размера → позиция становится BOTH. --- ## 🏗️ Структура модуля ### Константы ```rust const FIRST_TRIGGER_PCT: f64 = 3.0; // Первая резка при +3% const NEXT_TRIGGER_PCT: f64 = 1.5; // Вторая резка при +1.5% const HEDGE_TRIGGER_PCT: f64 = -1.5; // Хедж при -1.5% ``` ### Структура ```rust pub struct SingleStrategy { pub did_cut_once: bool, // Была ли первая резка? pub first_rebuild_price: f64, // Цена первой пересборки (отсчёт для 2-й резки/хеджа) } ``` **Поля:** - `did_cut_once`: `false` → ещё не было первой резки → ждем +3% - `did_cut_once`: `true` → была первая резка → ждем +1.5% или -1.5% - `first_rebuild_price`: Цена в момент первой пересборки → база для расчёта дельты --- ## 🔄 Основная логика ### Функция `process()` **Строки:** 41-219 **Сигнатура:** ```rust pub fn process( &mut self, pos: &mut PositionSnapshot, // Состояние позиции (изменяемое) meta: &TradingMeta, // Метаданные контракта ) -> Vec ``` **Алгоритм (7 этапов):** ### Этап 1: Проверка режима позиции ```rust // BOTH — пропускаем if pos.long_qty > 0.0 && pos.short_qty > 0.0 { return actions; } // Пусто — сбрасываем состояние if pos.long_qty <= 0.0 && pos.short_qty <= 0.0 { self.reset(); return actions; } ``` **Условия:** - ✅ `long_qty > 0` **и** `short_qty == 0` → работаем - ✅ `short_qty > 0` **и** `long_qty == 0` → работаем - ❌ `long_qty > 0` **и** `short_qty > 0` → BOTH → пропускаем - ❌ `long_qty == 0` **и** `short_qty == 0` → Flat → сбрасываем --- ### Этап 2: 🔥🔥 Переход из BOTH режима ```rust if pos.transitioning_to_single_from_both { let price = if pos.last_price > 0.0 { pos.last_price } else { meta.price }; println!("🎯 SINGLE СТРАТЕГИЯ: Переход из BOTH режима (после полной резки убыточной стороны)"); println!(" - Устанавливаем did_cut_once = true (пропускаем +3%)"); println!(" - Устанавливаем first_rebuild_price = {:.6} (текущая цена)", price); println!(" - Ждём: +1.5% → CLOSE ALL, -1.5% → ХЕДЖ"); self.did_cut_once = true; self.first_rebuild_price = price; pos.transitioning_to_single_from_both = false; // Сбрасываем флаг } ``` **Сценарий:** Bot был в BOTH, затем полностью закрил убыточную сторону → теперь осталась только прибыльная сторона. **Действия:** 1. Устанавливаем `did_cut_once = true` (пропускаем первую резку +3%) 2. Устанавливаем `first_rebuild_price = текущая_цена` 3. Ждем +1.5% (CLOSE ALL) или -1.5% (ХЕДЖ) **Почему это важно:** - В BOTH режиме уже были резки - Мы не хотим снова резать +3% от входа - Мы хотим управлять как после первой резки (от текущей цены) --- ### Этап 3: 🔥🔥 Cooldown - защита от немедленных действий ```rust let now = chrono::Utc::now().timestamp(); let elapsed = now - pos.last_entry_time; if elapsed < pos.hedge_cooldown_sec { return actions; } ``` **Назначение:** Защита от немедленных резок/хеджов сразу после входа. **Параметр:** `pos.hedge_cooldown_sec` (обычно 5 секунд) **Логика:** - Только что открыли позицию → elapsed < 5 сек → НЕ резаем - 5 секунд прошло → elapsed >= 5 сек → можем резать/хеджировать **Почему это важно:** - Сразу после входа может быть проскальзывание - Нельзя принимать решения до стабилизации цены --- ### Этап 4: Расчёт PnL% ```rust let price = if pos.last_price > 0.0 { pos.last_price } else { meta.price }; let cv = pos.contract_value.max(1.0); let (is_long, qty, entry, unrl) = if pos.long_qty > 0.0 { (true, pos.long_qty, pos.entry_long, pos.unrealized_long()) } else { (false, pos.short_qty, pos.entry_short, pos.unrealized_short()) }; if qty <= 0.0 || entry <= 0.0 { return actions; } let notional = qty * entry * cv; if notional <= 0.0 { return actions; } let pnl_pct_raw = (unrl / notional) * 100.0; // УЧЁТ КОМИССИЙ let fee_rate = meta .taker_fee_rate .abs() .max(meta.maker_fee_rate.abs()) .max(0.0003); let funding = meta.funding_rate.abs(); let cost_pct = (fee_rate * 2.0 + funding * 2.0) * 100.0; let pnl_pct = pnl_pct_raw - cost_pct; ``` **Расчёт:** 1. Определяем сторону (LONG или SHORT) 2. `notional = qty × entry_price × contract_value` 3. `pnl_pct_raw = unrealized_pnl / notional × 100` 4. `cost_pct = (taker_fee × 2 + funding × 2) × 100` 5. `pnl_pct = pnl_pct_raw - cost_pct` (чистый PnL с учётом комиссий) **Пример:** ``` LONG позиция: - qty = 1000 контрактов - entry_long = 0.002000 - contract_value = 1.0 - current_price = 0.002060 notional = 1000 × 0.002000 × 1.0 = 2.0 USDT unrealized_long = (0.002060 - 0.002000) × 1000 × 1.0 = 0.06 USDT pnl_pct_raw = 0.06 / 2.0 × 100 = 3.0% Комиссии: - taker_fee = 0.00075 - funding = 0.000012 - cost_pct = (0.00075 × 2 + 0.000012 × 2) × 100 = 0.1524% pnl_pct = 3.0 - 0.1524 = 2.8476% ``` --- ### Этап 5: СЦЕНАРИЙ 1 - ПЕРВАЯ РЕЗКА (+3%) ```rust if !self.did_cut_once && pnl_pct >= FIRST_TRIGGER_PCT { println!( "✂️ ПЕРВАЯ РЕЗКА: {} side profit hit {:.2}% (raw={:.2}%, fees={:.3}%)", if is_long { "LONG" } else { "SHORT" }, pnl_pct, pnl_pct_raw, cost_pct ); let half_qty = qty / 2.0; // Закрываем ПОЛОВИНУ if is_long { actions.push(LiveAction::CloseLong(half_qty, price, true)); } else { actions.push(LiveAction::CloseShort(half_qty, price, true)); } // Сразу докупаем ПОЛОВИНУ if is_long { actions.push(LiveAction::OpenLong(half_qty, price)); } else { actions.push(LiveAction::OpenShort(half_qty, price)); } // Запоминаем цену пересборки для дальнейших проверок self.did_cut_once = true; self.first_rebuild_price = price; println!( " 🔄 ПЕРЕСБОРКА: зафиксировали прибыль, размер остался {} (как был)", qty ); return actions; } ``` **Условия:** - `did_cut_once == false` (ещё не было первой резки) - `pnl_pct >= 3.0%` (чистый PnL ≥ +3%) **Действия:** 1. `qty = qty / 2` (половина позиции) 2. `Close{Side}(half_qty, price, true)` - закрываем половину (фиксируем прибыль) 3. `Open{Side}(half_qty, price)` - открываем половину на новой цене 4. `did_cut_once = true` - первая резка сделана 5. `first_rebuild_price = price` - запоминаем цену пересборки **Результат:** - Размер позиции НЕ ИЗМЕНЯЕТСЯ (qty остаётся прежним) - Прибыль зафиксирована (закрыта половина) - Entry price обновляется (средняя цена между старой и новой) - Теперь работаем как "после первой резки" (ждем +1.5% или -1.5%) **Пример:** ``` Вход: - LONG qty = 1000 контрактов - entry_price = 0.002000 - current_price = 0.002060 - pnl_pct = +3.0% Первая резка: 1. Закрываем 500 LONG по 0.002060 → прибыль 500 × (0.002060 - 0.002000) = 0.03 USDT 2. Открываем 500 LONG по 0.002060 Результат: - LONG qty = 1000 (как было) - Новый entry_price = (500 × 0.002000 + 500 × 0.002060) / 1000 = 0.002030 - Зафиксирована прибыль 0.03 USDT - did_cut_once = true - first_rebuild_price = 0.002060 ``` --- ### Этап 6: СЦЕНАРИЙ 2 - ПОСЛЕ ПЕРВОЙ РЕЗКИ #### 6A: Расчёт дельты от точки пересборки ```rust if self.did_cut_once && self.first_rebuild_price > 0.0 { // Считаем delta от точки пересборки let delta_from_rebuild = if is_long { (price - self.first_rebuild_price) / self.first_rebuild_price * 100.0 } else { (self.first_rebuild_price - price) / self.first_rebuild_price * 100.0 }; ``` **Формула:** - **LONG:** `delta = (current_price - rebuild_price) / rebuild_price × 100` - **SHORT:** `delta = (rebuild_price - current_price) / rebuild_price × 100` **Значение:** - `delta > 0` → цена пошла в нашу сторону (прибыль) - `delta < 0` → цена пошла против нас (убыток) **Пример LONG:** ``` first_rebuild_price = 0.002060 current_price = 0.002090 delta_from_rebuild = (0.002090 - 0.002060) / 0.002060 × 100 = 0.000030 / 0.002060 × 100 = +1.46% ``` **Пример SHORT:** ``` first_rebuild_price = 0.002060 current_price = 0.002030 delta_from_rebuild = (0.002060 - 0.002030) / 0.002060 × 100 = 0.000030 / 0.002060 × 100 = +1.46% ``` --- #### 6B: Вариант A - Ещё +1.5% → CLOSE ALL ```rust if delta_from_rebuild >= NEXT_TRIGGER_PCT { println!( "✂️ ВТОРАЯ РЕЗКА: ещё {:.2}% от точки пересборки → CLOSE ALL", delta_from_rebuild ); // Закрываем ВСЁ if is_long { actions.push(LiveAction::CloseLong(qty, price, true)); } else { actions.push(LiveAction::CloseShort(qty, price, true)); } // Сбрасываем и ищем новую пару self.reset(); return actions; } ``` **Условия:** - `did_cut_once == true` (была первая резка) - `delta_from_rebuild >= +1.5%` (цена пошла дальше в нашу сторону) **Действия:** 1. `Close{Side}(qty, price, true)` - закрываем ВСЮ позицию 2. `reset()` - сбрасываем состояние стратегии 3. `main.rs` завершает сессию → ищет новую пару **Пример LONG:** ``` До резки: - first_rebuild_price = 0.002060 - current_price = 0.002090 (delta = +1.46%) Через 30 секунд: - current_price = 0.002091 (delta = +1.51%) Вторая резка: - Закрываем 1000 LONG по 0.002091 - Зафиксирована прибыль с момента пересборки: 1000 × (0.002091 - 0.002060) = 0.031 USDT - reset() → did_cut_once = false, first_rebuild_price = 0.0 - Сессия завершена ``` --- #### 6C: Вариант B - -1.5% → ПОЛНЫЙ ХЕДЖ ```rust if delta_from_rebuild <= HEDGE_TRIGGER_PCT { println!( "🚨 ХЕДЖ: {:.2}% от точки пересборки → полный хедж", delta_from_rebuild ); // Открываем противоположную сторону того же размера if is_long { actions.push(LiveAction::OpenShort(qty, price)); } else { actions.push(LiveAction::OpenLong(qty, price)); } // Переходим в BOTH режим self.reset(); return actions; } ``` **Условия:** - `did_cut_once == true` (была первая резка) - `delta_from_rebuild <= -1.5%` (цена пошла ПРОТИВ нас) **Действия:** 1. `Open{OppositeSide}(qty, price)` - открываем противоположную сторону того же размера 2. `reset()` - сбрасываем состояние SingleStrategy 3. Позиция становится BOTH → управление переходит к BothStrategy **Пример LONG:** ``` До хеджа: - LONG qty = 1000 - first_rebuild_price = 0.002060 - current_price = 0.002030 (delta = -1.46%) Через 30 секунд: - current_price = 0.002029 (delta = -1.51%) Хедж: - Открываем SHORT 1000 по 0.002029 - Теперь: LONG 1000 + SHORT 1000 = BOTH - reset() → did_cut_once = false - BothStrategy берёт управление ``` **Почему хедж?** - Цена пошла против нас → убыток растёт - Открываем зеркальную позицию → фиксируем убыток - BOTH режим позволяет: - Ждать разворота цены - Резать прибыльную сторону - Постепенно сокращать убыточную сторону --- ### Этап 7: Логирование состояния ```rust // Логируем текущее состояние раз в 5 секунд if now - pos.last_cooldown_log_time >= 5 { println!( " 📊 От точки пересборки: {:.2}% (need +{:.1}% to close, {:.1}% to hedge)", delta_from_rebuild, NEXT_TRIGGER_PCT, HEDGE_TRIGGER_PCT.abs() ); pos.last_cooldown_log_time = now; } ``` **Периодичность:** Раз в 5 секунд **Показывает:** - Текущая дельта от точки пересборки - Сколько нужно до CLOSE ALL (+1.5%) - Сколько нужно до хеджа (-1.5%) **Пример лога:** ``` 📊 От точки пересборки: -0.45% (need +1.5% to close, 1.5% to hedge) ``` --- ## 🔄 Функция `reset()` **Строки:** 36-39 ```rust fn reset(&mut self) { self.did_cut_once = false; self.first_rebuild_price = 0.0; } ``` **Назначение:** Сброс состояния стратегии **Когда вызывается:** 1. Когда позиция становится пустой (Flat) 2. После ВТОРОЙ РЕЗКИ (CLOSE ALL) 3. После ХЕДЖА (переход в BOTH) --- ## 🔄 Функция `new()` **Строки:** 29-34 ```rust pub fn new() -> Self { Self { did_cut_once: false, first_rebuild_price: 0.0, } } ``` **Назначение:** Создание новой инстанции стратегии **Начальное состояние:** - `did_cut_once = false` → ждем первую резку +3% - `first_rebuild_price = 0.0` → не установлена --- ## 📊 Полный граф состояний ``` ┌─────────────────────────────────────────────────────────────┐ │ SINGLE │ │ (только LONG или SHORT) │ └─────────────────────────────────────────────────────────────┘ │ │ ┌─────────────────────┐ │ │ did_cut_once=false │ │ │ Ждём +3% │ │ └─────────────────────┘ │ ┌────────────────────┼────────────────────┐ │ │ │ +3% PnL -1.5% от точки +1.5% от точки (перезапуск) пересборки пересборки │ │ │ ▼ ▼ ▼ ┌────────────┐ ┌────────────┐ ┌────────────┐ │ ПЕРВАЯ │ │ ХЕДЖ │ │ ВТОРАЯ │ │ РЕЗКА │ │ │ │ РЕЗКА │ │ │ │ OpenOpposite│ │ CLOSE ALL │ │ Close 50% │ │ qty │ │ │ │ Open 50% │ │ │ │ Close 100% │ │ │ │ │ │ │ │ did_cut_ │ │ reset() │ │ reset() │ │ once=true │ │ │ │ │ │ first_ │ │ → BOTH │ │ → Flat │ │ rebuild_ │ │ │ │ (новая │ │ price=price│ │ │ │ пара) │ └────────────┘ └────────────┘ └────────────┘ │ ▼ ┌──────────────────────────────────┐ │ did_cut_once=true │ │ Ждём +1.5% (CLOSE ALL) │ │ или -1.5% (ХЕДЖ) │ └──────────────────────────────────┘ ``` --- ## 🎯 Сценарии работы ### Сценарий 1: Идеальный сценарий (две резки, прибыль) ``` 00:00 → Вход в LONG 1000 по 0.002000 00:30 → Цена 0.002060 (+3.0%) ✂️ ПЕРВАЯ РЕЗКА - Закрываем 500 LONG по 0.002060 (прибыль 0.03 USDT) - Открываем 500 LONG по 0.002060 - did_cut_once = true, first_rebuild_price = 0.002060 01:00 → Цена 0.002091 (+1.51% от пересборки) ✂️ ВТОРАЯ РЕЗКА - Закрываем 1000 LONG по 0.002091 (прибыль 0.031 USDT) - reset() - Сессия завершена, ищем новую пару ИТОГО: - Зафиксировано две прибыли: 0.03 + 0.031 = 0.061 USDT - Длительность: 1 минута ``` --- ### Сценарий 2: Хедж после первой резки ``` 00:00 → Вход в LONG 1000 по 0.002000 00:30 → Цена 0.002060 (+3.0%) ✂️ ПЕРВАЯ РЕЗКА - Закрываем 500 LONG по 0.002060 (прибыль 0.03 USDT) - Открываем 500 LONG по 0.002060 - did_cut_once = true, first_rebuild_price = 0.002060 01:00 → Цена 0.002029 (-1.51% от пересборки) 🚨 ХЕДЖ - Открываем SHORT 1000 по 0.002029 - reset() - Теперь BOTH: LONG 1000 + SHORT 1000 - BothStrategy берёт управление ИТОГО: - Зафиксирована прибыль: 0.03 USDT - Позиция BOTH: LONG (entry ~0.002030), SHORT (0.002029) ``` --- ### Сценарий 3: Переход из BOTH режима ``` ... (предыдущие резки в BOTH режиме) ... 05:00 → BOTH режим полностью резал убыточную сторону Теперь только LONG 1000 pos.transitioning_to_single_from_both = true 05:01 → SingleStrategy::process() 🎯 SINGLE СТРАТЕГИЯ: Переход из BOTH режима - Устанавливаем did_cut_once = true (пропускаем +3%) - Устанавливаем first_rebuild_price = 0.002050 (текущая цена) - Ждём: +1.5% → CLOSE ALL, -1.5% → ХЕДЖ 05:30 → Цена 0.002080 (+1.46% от пересборки) 📊 От точки пересборки: +1.46% (need +1.5% to close, 1.5% to hedge) 05:35 → Цена 0.002081 (+1.51% от пересборки) ✂️ ВТОРАЯ РЕЗКА - Закрываем 1000 LONG по 0.002081 - reset() - Сессия завершена ИТОГО: - Пропустили +3% (были уже в BOTH) - Резали сразу от точки пересборки (+1.5%) ``` --- ### Сценарий 4: Cooldown защита ``` 00:00 → Вход в LONG 1000 по 0.002000 pos.last_entry_time = 1645560000 pos.hedge_cooldown_sec = 5 00:00:05 → Цена 0.002060 (+3.0%) SingleStrategy::process() elapsed = 1645560005 - 1645560000 = 5 секунд ✅ ✂️ ПЕРВАЯ РЕЗКА 00:00:01 → Если бы была цена 0.002060 elapsed = 1 секунда < 5 секунд ❌ НЕ режем (защита от немедленных действий) ``` --- ## ⚠️ Критические моменты ### 1. Cooldown защита **Зачем:** Защита от немедленных резок/хеджов сразу после входа **Если не работает:** - ❌ Может закрывать позицию сразу после входа (проскальзывание) - ❌ Может хеджироваться сразу на проскальзывании **Параметр:** `pos.hedge_cooldown_sec` (обычно 5 секунд) --- ### 2. Расчёт дельты для LONG vs SHORT **Для LONG:** ```rust delta_from_rebuild = (price - rebuild_price) / rebuild_price × 100 ``` - `delta > 0` → цена выше → прибыль - `delta < 0` → цена ниже → убыток **Для SHORT:** ```rust delta_from_rebuild = (rebuild_price - price) / rebuild_price × 100 ``` - `delta > 0` → цена ниже → прибыль - `delta < 0` → цена выше → убыток **Ошибка:** Если перепутать формулы → неправильные триггеры! --- ### 3. Учёт комиссий **Формула:** ```rust cost_pct = (fee_rate * 2.0 + funding * 2.0) * 100.0 ``` **Что учитывается:** - `fee_rate × 2` → taker_fee туда-обратно (enter + exit) - `funding × 2` → funding туда-обратно - `× 100` → перевод в % **Если не учитывать:** - ❌ Будет резать раньше (сырой PnL > чистый) - ❌ Может получить убыток вместо прибыли --- ### 4. Флаг `transitioning_to_single_from_both` **Зачем:** Обозначает что мы перешли из BOTH после полной резки убыточной стороны. **Когда устанавливается:** - В `BothStrategy` при полной резке убыточной стороны (qty = 0) **Когда сбрасывается:** - В `SingleStrategy` при обработке (строка 68) **Ошибка:** Если не сбросить → будет думать что это переход → не правильно рассчитывать дельту --- ## 📊 Параметры и их источники | Параметр | Значение | Источник | |----------|----------|----------| | `FIRST_TRIGGER_PCT` | 3.0% | Hardcoded const | | `NEXT_TRIGGER_PCT` | 1.5% | Hardcoded const | | `HEDGE_TRIGGER_PCT` | -1.5% | Hardcoded const | | `hedge_cooldown_sec` | 5 сек | PositionSnapshot | | `did_cut_once` | false/true | SingleStrategy (внутреннее состояние) | | `first_rebuild_price` | цена в момент пересборки | SingleStrategy (внутреннее состояние) | | `taker_fee_rate` | из TradingMeta | Redis / Gate.io API | | `maker_fee_rate` | из TradingMeta | Redis / Gate.io API | | `funding_rate` | из TradingMeta | Redis / Gate.io API | | `contract_value` | из PositionSnapshot | Redis / расчёты | | `entry_long` / `entry_short` | из PositionSnapshot | Redis / расчёты | | `long_qty` / `short_qty` | из PositionSnapshot | Redis / биржа | | `last_price` | из PositionSnapshot | Redis / биржа | --- ## 🔗 Интеграция с другими модулями ### 1. BothStrategy **Вызывается когда:** Обе стороны активны **Переход в SingleStrategy:** - When `short_qty = 0` или `long_qty = 0` в BOTH - Устанавливает `pos.transitioning_to_single_from_both = true` **Принимает управление:** SingleStrategy обработает флаг --- ### 2. PositionSnapshot **Используемые поля:** ```rust pos.long_qty pos.short_qty pos.entry_long pos.entry_short pos.unrealized_long() pos.unrealized_short() pos.last_price pos.last_entry_time pos.hedge_cooldown_sec pos.transitioning_to_single_from_both pos.last_cooldown_log_time ``` **Изменяемые поля:** ```rust pos.transitioning_to_single_from_both = false // Сбрасываем флаг pos.last_cooldown_log_time = now // Обновляем время лога ``` --- ### 3. TradingMeta **Используемые поля:** ```rust meta.price meta.taker_fee_rate meta.maker_fee_rate meta.funding_rate ``` --- ## 🔄 Поток данных ``` live_step() ↓ SingleStrategy::process() ↓ ├─→ Проверка режима позиции │ ├─→ BOTH → пропускаем │ └─→ Flat → reset() │ ├─→ Проверка флага transitioning_to_single_from_both │ └─→ Установка did_cut_once = true │ ├─→ Проверка cooldown │ └─→ elapsed < 5 сек → пропускаем │ ├─→ Расчёт PnL% │ ├─→ notional = qty × entry × contract_value │ ├─→ pnl_pct_raw = unrealized / notional × 100 │ ├─→ cost_pct = (fee × 2 + funding × 2) × 100 │ └─→ pnl_pct = pnl_pct_raw - cost_pct │ ├─→ ПЕРВАЯ РЕЗКА (если did_cut_once=false AND pnl_pct >= 3.0%) │ ├─→ Close{Side}(qty/2, price, true) │ ├─→ Open{Side}(qty/2, price) │ ├─→ did_cut_once = true │ └─→ first_rebuild_price = price │ ├─→ ВТОРАЯ РЕЗКА (если did_cut_once=true AND delta >= +1.5%) │ ├─→ Close{Side}(qty, price, true) │ └─→ reset() → Flat → новая пара │ ├─→ ХЕДЖ (если did_cut_once=true AND delta <= -1.5%) │ ├─→ Open{OppositeSide}(qty, price) │ └─→ reset() → BothStrategy │ └─→ Логирование (раз в 5 сек) ``` --- ## 📝 Логирование ### Уровни логов: **Первая резка:** ``` ✂️ ПЕРВАЯ РЕЗКА: LONG side profit hit 3.00% (raw=3.15%, fees=0.152%) 🔄 ПЕРЕСБОРКА: зафиксировали прибыль, размер остался 1000.0 (как был) ``` **Вторая резка:** ``` ✂️ ВТОРАЯ РЕЗКА: ещё 1.51% от точки пересборки → CLOSE ALL ``` **Хедж:** ``` 🚨 ХЕДЖ: -1.51% от точки пересборки → полный хедж ``` **Переход из BOTH:** ``` 🎯 SINGLE СТРАТЕГИЯ: Переход из BOTH режима (после полной резки убыточной стороны) - Устанавливаем did_cut_once = true (пропускаем +3%) - Устанавливаем first_rebuild_price = 0.002060 (текущая цена) - Ждём: +1.5% → CLOSE ALL, -1.5% → ХЕДЖ ``` **Состояние:** ``` 📊 От точки пересборки: -0.45% (need +1.5% to close, 1.5% to hedge) ``` --- ## 🚀 Использование в коде ### Пример вызова в live_step.rs: ```rust // Проверяем что это SINGLE режим if (pos.long_qty > 0.0 && pos.short_qty == 0.0) || (pos.short_qty > 0.0 && pos.long_qty == 0.0) { // Вызываем SingleStrategy let actions = single_strategy.process(&mut pos, meta); if !actions.is_empty() { return actions; // Исполняем действия } } ``` --- ## 📊 Важные формулы ### 1. Delta от точки пересборки (LONG) ``` delta_from_rebuild = (current_price - rebuild_price) / rebuild_price × 100 ``` **Пример:** ``` rebuild_price = 0.002060 current_price = 0.002090 delta = (0.002090 - 0.002060) / 0.002060 × 100 = +1.46% ``` ### 2. Delta от точки пересборки (SHORT) ``` delta_from_rebuild = (rebuild_price - current_price) / rebuild_price × 100 ``` **Пример:** ``` rebuild_price = 0.002060 current_price = 0.002030 delta = (0.002060 - 0.002030) / 0.002060 × 100 = +1.46% ``` ### 3. Комиссии и funding ``` cost_pct = (taker_fee_rate × 2.0 + funding_rate × 2.0) × 100.0 ``` **Пример:** ``` taker_fee_rate = 0.00075 funding_rate = 0.000012 cost_pct = (0.00075 × 2.0 + 0.000012 × 2.0) × 100.0 = (0.0015 + 0.000024) × 100.0 = 0.1524% ``` ### 4. Чистый PnL ``` pnl_pct = pnl_pct_raw - cost_pct ``` **Пример:** ``` pnl_pct_raw = +3.0% cost_pct = 0.1524% pnl_pct = 3.0 - 0.1524 = 2.8476% ``` --- ## 🔍 Проверка работы ### Проверка 1: Правильная работа первой резки 1. Открыть позицию LONG 2. Дождаться +3% PnL 3. Проверить лог: ``` ✂️ ПЕРВАЯ РЕЗКА: LONG side profit hit 3.00% (raw=3.15%, fees=0.152%) 🔄 ПЕРЕСБОРКА: зафиксировали прибыль, размер остался 1000.0 (как был) ``` 4. Проверить вручную: - Qty должен остаться тем же - Entry price должен измениться (средняя цена) ### Проверка 2: Правильная работа второй резки 1. После первой резки дождаться +1.5% от rebuild_price 2. Проверить лог: ``` ✂️ ВТОРАЯ РЕЗКА: ещё 1.51% от точки пересборки → CLOSE ALL ``` 3. Проверить: - Позиция должна быть пустой (Flat) - Сессия должна завершиться ### Проверка 3: Правильная работа хеджа 1. После первой резки дождаться -1.5% от rebuild_price 2. Проверить лог: ``` 🚨 ХЕДЖ: -1.51% от точки пересборки → полный хедж ``` 3. Проверить: - Позиция должна стать BOTH (LONG + SHORT) - BothStrategy должен взять управление ### Проверка 4: Cooldown защита 1. Открыть позицию LONG 2. Сразу получить +3% PnL (в течение 1 секунды) 3. Проверить лог: НЕ должно быть "ПЕРВАЯ РЕЗКА" 4. Подождать 5 секунд 5. Если PnL всё ещё +3% → должна быть первая резка --- ## 📚 Связанные файлы | Файл | Связь | |------|-------| | `src/live/state.rs` | PositionSnapshot структура | | `src/live/strategy_both.rs` | BothStrategy (передача управления) | | `src/live/actions.rs` | LiveAction enum | | `src/live/meta.rs` | TradingMeta структура | | `src/live/step.rs` | live_step orchestration | --- ## 🎯 Резюме **Что делает SingleStrategy:** 1. ✅ Работает ТОЛЬКО когда одна сторона активна (LONG или SHORT) 2. ✅ Первая резка при +3% → зафиксировать прибыль 3. ✅ Вторая резка при +1.5% от rebuild → CLOSE ALL 4. ✅ Хедж при -1.5% от rebuild → открыть противоположную сторону → BOTH 5. ✅ Cooldown защита (5 сек после входа) 6. ✅ Поддерживает переход из BOTH режима 7. ✅ Учитывает комиссии и funding **Когда пропускает:** - ❌ BOTH режим (обе стороны) - ❌ Flat (пустая позиция) - ❌ Cooldown не прошёл (менее 5 сек) - ❌ Не достигнут порог резки/хеджа **Переходы:** - `→ CLOSE ALL` → Flat → новая пара - `→ ХЕДЖ` → Both → BothStrategy - `→ From BOTH` → Single (did_cut_once=true) --- **Дата создания:** 2026-02-22 **Автор:** Claude Code Assistant **Версия:** 1.0