← Назад к главному
# strategy_both.rs - Модуль управления BOTH позицией ## 📋 Назначение модуля `BothStrategy` управляет позицией когда открыты ОБЕ стороны (LONG + SHORT). **Основная логика:** 1. **Резки по PnL%** через `VolatilityCycle` (3% первый цикл, 1.5% последующие) 2. **Авто-ре-хедж** при развороте тренда против бОльшей стороны (-1.5%) 3. **Выравнивание объёмов** при развороте на -1% от цены последней перезакупки 4. **Фазовое управление** через `PositionMachine` (Both, Boosted, WindDown, ExitReady) 5. **Выход** в фазе ExitReady при +0.5% прибыли **Фазы:** - `Both` - базовый BOTH режим - `Boosted` - после первого BOOST-цикла - `WindDown` - последующие резки (сужение позиции) - `ExitReady` - финальный выход при небольшом плюсе --- ## 🏗️ Структура модуля ### Константы ```rust const REHEDGE_PNL_TRIGGER_PCT: f64 = -1.5; // Авто-ре-хедж при -1.5% PnL большей стороны const EXIT_READY_PROFIT_PCT: f64 = 0.5; // Выход в ExitReady при +0.5% прибыли ``` ### Структура ```rust pub struct BothStrategy; ``` **Особенности:** - Unit struct (без полей) - Вся логика в функциях - Состояние хранится в `PositionSnapshot` и `PositionMachine` --- ## 🔄 Основные функции ### Функция `process()` **Строки:** 132-313 **Сигнатура:** ```rust pub fn process( &self, pos: &mut PositionSnapshot, // Состояние позиции (изменяемое) meta: &TradingMeta, // Метаданные контракта machine: &mut PositionMachine, // Фазовая машина (изменяемая) ) -> Vec ``` **Алгоритм (по фазам):** #### Общий блок - актуализация фазы ```rust machine.update_phase(pos); ``` --- #### Фаза 1: Single ```rust PositionPhase::Single => { return actions; // Пропускаем - обрабатывается SingleStrategy } ``` **Условие:** Одна сторона активна **Действие:** Возвращаем пустой actions → SingleStrategy берёт управление --- #### Фаза 2: Both / Boosted / WindDown **Строки:** 150-273 **Основной блок обработки BOTH позиции** ##### Блок 1: Подготовка цены ```rust let price = if pos.last_price > 0.0 { pos.last_price } else { meta.price }; if price <= 0.0 { return actions; } ``` --- ##### Блок 2: Расчёт PnL% по сторонам ```rust // 1. PnL LONG в % let pnl_long_abs = pos.unrealized_long(); let pnl_long_pct = if pos.long_qty > 0.0 && pos.entry_long > 0.0 { let notional_long = pos.long_qty * pos.entry_long * pos.contract_value.max(1.0); (pnl_long_abs / notional_long) * 100.0 } else { 0.0 }; // 2. PnL SHORT в % let pnl_short_abs = pos.unrealized_short(); let pnl_short_pct = if pos.short_qty > 0.0 && pos.entry_short > 0.0 { let notional_short = pos.short_qty * pos.entry_short * pos.contract_value.max(1.0); (pnl_short_abs / notional_short) * 100.0 } else { 0.0 }; // 3. Дельта (разница) let delta_pct = pnl_long_pct + pnl_short_pct; ``` **Формулы:** - `notional = qty × entry_price × contract_value` - `pnl_pct = unrealized_pnl / notional × 100` - `delta = pnl_long_pct + pnl_short_pct` (всегда суммируется!) **Пример:** ``` LONG: +3.0% SHORT: -2.0% delta = 3.0 + (-2.0) = +1.0% ``` --- ##### Блок 3: Порог резки ```rust let trigger_pct = if machine.boosted_once { 1.5 } else { 3.0 }; ``` **Логика:** - Первый цикл: `trigger_pct = 3.0%` - После BOOST: `trigger_pct = 1.5%` --- ##### Блок 4: Комиссии и funding ```rust 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; // Чистые PnL (за вычетом комиссий) let pnl_long_clean = pnl_long_pct - cost_pct; let pnl_short_clean = pnl_short_pct - cost_pct; ``` **Пример:** ``` taker_fee_rate = 0.00075 funding_rate = 0.000012 cost_pct = (0.00075 × 2 + 0.000012 × 2) × 100 = 0.1524% pnl_long_pct = +3.0% pnl_long_clean = 3.0 - 0.1524 = 2.8476% ``` --- ##### Блок 5: Логирование статуса ```rust if now - pos.last_cooldown_log_time >= 5 { println!(" 📊 BOTH СТАТУС:"); println!(" LONG: {:+.2}% (сырой: {:+.2}%, чистый: {:+.2}%)", pnl_long_pct, pnl_long_pct, pnl_long_clean); println!(" SHORT: {:+.2}% (сырой: {:+.2}%, чистый: {:+.2}%)", pnl_short_pct, pnl_short_pct, pnl_short_clean); println!(" Дельта: {:+.2}%", delta_pct); println!(" Порог резки: {:.1}% (boosted_once={})", trigger_pct, machine.boosted_once); println!(" Комиссии+фандинг: {:.3}%", cost_pct); // Индикатор готовности к резке let profit_side_clean = if pnl_long_clean > pnl_short_clean { pnl_long_clean } else { pnl_short_clean }; if profit_side_clean > 0.0 && profit_side_clean >= trigger_pct * 0.8 { let progress_pct = (profit_side_clean / trigger_pct) * 100.0; println!(" ⚠️ БЛИЗО К РЕЗКЕ: {:.1}% от порога!", progress_pct); } pos.last_cooldown_log_time = now; } ``` **Периодичность:** Раз в 5 секунд **Показывает:** - PnL по сторонам (сырой и чистый) - Дельта (сумма) - Порог резки (3% или 1.5%) - Комиссии + funding - Индикатор близости к резке (если > 80%) **Пример лога:** ``` 📊 BOTH СТАТУС: LONG: +2.85% (сырой: +3.00%, чистый: +2.85%) SHORT: -1.85% (сырой: -1.70%, чистый: -1.85%) Дельта: +1.00% Порог резки: 3.0% (boosted_once=false) Комиссии+фандинг: 0.152% ⚠️ БЛИЗО К РЕЗКЕ: 95.0% от порога! ``` --- ##### Блок 6: 🔥 Проверка резки через VolatilityCycle ```rust if let Some(sig) = VolatilityCycle::check( pos, meta, pos.entry_long, pos.entry_short, machine.boosted_once, ) { let cut_actions = VolatilityCycle::execute(pos, &sig, price, meta); actions.extend(cut_actions); machine.on_cut(); machine.update_phase(pos); } ``` **Вызов `VolatilityCycle::check()`:** - Проверяет условие резки - Возвращает `Some(CutSignal)` если резка нужна - Возвращает `None` если ещё не время **Вызов `VolatilityCycle::execute()`:** - Генерирует actions для резки - Закрывает прибыльную сторону на 100% - Закрывает убыточную сторону на динамический % - Перезакупает прибыльную сторону на 100% **Обновление машины:** - `machine.on_cut()` - увеличивает cut_count - `machine.update_phase(pos)` - пересчитывает фазу **См. подробнее в `volatility_cycle.rs`** --- ##### Блок 7: 🔥 Проверка разворота на -1% от last_profit_rebuy_price ```rust if pos.last_profit_rebuy_price > 0.0 { let reverse_threshold = pos.last_profit_rebuy_price * 0.99; // -1% if price <= reverse_threshold { // Цена развернулась на -1% → выравниваем позиции (зеркальный хедж) println!("🔄 РАЗВОРОТ НА -1%: цена {:.6} <= {:.6} (last_profit_rebuy_price * 0.99)", price, reverse_threshold); // Определяем какая сторона больше if pos.long_qty > pos.short_qty && pos.short_qty > 0.0 { // LONG больше → докупаем SHORT до LONG let need_short = (pos.long_qty - pos.short_qty).max(0.0); if need_short > 0.0 { actions.push(LiveAction::OpenShort(need_short, price)); println!(" ✅ Выравнивание: добавлено {:.0} SHORT, теперь {:.0}/{:.0}", need_short, pos.long_qty, pos.short_qty); } } else if pos.short_qty > pos.long_qty && pos.long_qty > 0.0 { // SHORT больше → докупаем LONG до SHORT let need_long = (pos.short_qty - pos.long_qty).max(0.0); if need_long > 0.0 { actions.push(LiveAction::OpenLong(need_long, price)); println!(" ✅ Выравнивание: добавлено {:.0} LONG, теперь {:.0}/{:.0}", need_long, pos.long_qty, pos.short_qty); } } // Обновляем базовый юнит pos.base_qty = pos.long_qty.min(pos.short_qty); } } ``` **Назначение:** Защита от сильного разворота цены против прибыльной стороны **Логика:** 1. Если есть `last_profit_rebuy_price` (цена последней перезакупки прибыльной стороны) 2. Если `current_price <= last_profit_rebuy_price × 0.99` (цена упала на -1%) 3. Выравниваем объёмы (зеркальный хедж) **Пример:** ``` До резки: - LONG = 1000, SHORT = 500 - last_profit_rebuy_price = 0.002000 После резки: - LONG = 1000, SHORT = 500 - last_profit_rebuy_price = 0.002100 (перезакупили LONG) Разворот: - current_price = 0.002080 (на -0.95%) - Ещё падает до 0.002079 (на -1.0%) - Триггер: 0.002079 <= 0.002100 × 0.99 = 0.002079 ✅ Действия: - need_short = 1000 - 500 = 500 - OpenShort(500, 0.002079) - Теперь LONG = 1000, SHORT = 1000 (зеркальный хедж) - pos.base_qty = 1000 ``` --- ##### Блок 8: 🔥 Проверка авто-ре-хеджа на развороте против бОльшей стороны ```rust let rehedge_actions = self.auto_rehedge_on_reverse(pos, meta, machine); actions.extend(rehedge_actions); ``` **Вызов `auto_rehedge_on_reverse()`:** - Проверяет PnL% бОльшей стороны - Если PnL% ≤ -1.5% → выравнивает объёмы (зеркальный хедж) **См. подробнее ниже** --- #### Фаза 3: ExitReady **Строки:** 276-309 **Назначение:** Финальный выход при небольшом плюсе ##### Блок 1: Подготовка цены и базового номинала ```rust let price = if pos.last_price > 0.0 { pos.last_price } else { meta.price }; if price <= 0.0 { return actions; } let cv = pos.contract_value.max(1.0); let base = pos.long_qty.min(pos.short_qty); // базовый номинал по минимальной стороне let base_notional = base * price * cv; if base_notional <= 0.0 { return actions; } ``` **Логика:** - `base = min(long_qty, short_qty)` - минимальная сторона - `base_notional = base × price × contract_value` - номинал минимальной стороны --- ##### Блок 2: Расчёт прибыли ```rust let net = pos.total_pnl_at_price(price); let profit_pct = (net / base_notional) * 100.0; ``` **Логика:** - `net = total_pnl_at_price(price)` - общий PnL (LONG + SHORT) - `profit_pct = net / base_notional × 100` - прибыль в % от базового номинала --- ##### Блок 3: Выход при +0.5% ```rust // маленький, но уверенный плюс → закрываем всё if profit_pct >= EXIT_READY_PROFIT_PCT { // Создаём actions для закрытия if pos.long_qty > 0.0 { actions.push(LiveAction::CloseLong(pos.long_qty, price, false)); } if pos.short_qty > 0.0 { actions.push(LiveAction::CloseShort(pos.short_qty, price, false)); } machine.on_exit(); } ``` **Логика:** - Если `profit_pct >= 0.5%` → CLOSE ALL - Закрываем LONG и SHORT полностью - `machine.on_exit()` - сбрасываем машину **Пример:** ``` LONG = 100, SHORT = 100 (base = 100) price = 0.002000 base_notional = 100 × 0.002000 × 1.0 = 0.2 USDT PnL: - LONG: +0.001 USDT (были 100, стали ~100.5) - SHORT: -0.0005 USDT (были 100, стали ~99.5) - net = 0.001 + (-0.0005) = 0.0005 USDT profit_pct = 0.0005 / 0.2 × 100 = 0.25% Ещё подождём... profit_pct >= 0.5% нужен После падения: - LONG: -0.002 USDT - SHORT: +0.003 USDT - net = -0.002 + 0.003 = 0.001 USDT - profit_pct = 0.001 / 0.2 × 100 = 0.5% ✅ CLOSE ALL → сессия завершена ``` --- ### Функция `auto_rehedge_on_reverse()` **Строки:** 47-128 **Сигнатура:** ```rust fn auto_rehedge_on_reverse( &self, pos: &mut PositionSnapshot, meta: &TradingMeta, machine: &mut PositionMachine, ) -> Vec ``` **Назначение:** Авто-ре-хедж при развороте тренда против бОльшей стороны (-1.5% PnL%) **Алгоритм (7 этапов):** --- #### Этап 1: Проверка что обе стороны активны ```rust if !(pos.long_qty > 0.0 && pos.short_qty > 0.0) { return actions; } ``` --- #### Этап 2: Проверка что объёмы не равны ```rust if (pos.long_qty - pos.short_qty).abs() < f64::EPSILON { return actions; } ``` **Почему:** Если объёмы равны → уже зеркальный хедж → не нужно --- #### Этап 3: Подготовка цены ```rust let price = if pos.last_price > 0.0 { pos.last_price } else { meta.price }; if price <= 0.0 { return actions; } ``` --- #### Этап 4: Определение "бОльшей" стороны ```rust let cv = pos.contract_value.max(1.0); // определяем "бОльшую" сторону let (bigger_is_long, bigger_qty, bigger_entry) = if pos.long_qty > pos.short_qty { (true, pos.long_qty, pos.entry_long) } else { (false, pos.short_qty, pos.entry_short) }; ``` **Логика:** - `long_qty > short_qty` → LONG больше - `short_qty > long_qty` → SHORT больше --- #### Этап 5: Расчёт PnL% бОльшей стороны ```rust if bigger_qty <= 0.0 || bigger_entry <= 0.0 { return actions; } let notional = bigger_qty * bigger_entry * cv; if notional <= 0.0 { return actions; } // PnL% бОльшей стороны let pnl_abs = if bigger_is_long { (price - bigger_entry) * bigger_qty * cv } else { (bigger_entry - price) * bigger_qty * cv }; let pnl_pct = (pnl_abs / notional) * 100.0; ``` **Формула:** - `notional = bigger_qty × bigger_entry × contract_value` - `pnl_abs = (price - entry) × qty × contract_value` (LONG) - `pnl_abs = (entry - price) × qty × contract_value` (SHORT) - `pnl_pct = pnl_abs / notional × 100` **Пример LONG больше:** ``` LONG = 1000, SHORT = 500 (LONG больше) entry_long = 0.002000 current_price = 0.001970 notional = 1000 × 0.002000 × 1.0 = 2.0 USDT pnl_abs = (0.001970 - 0.002000) × 1000 × 1.0 = -0.03 USDT pnl_pct = -0.03 / 2.0 × 100 = -1.5% ``` --- #### Этап 6: Проверка порога резки (-1.5%) ```rust // Если ещё не достигли отрицательных -1.5% — ничего не делаем if pnl_pct > REHEDGE_PNL_TRIGGER_PCT { return actions; } ``` **Логика:** - Если `pnl_pct > -1.5%` → ещё не время → пропускаем - Если `pnl_pct <= -1.5%` → ре-хедж нужен **Пример:** ``` pnl_pct = -1.4% → -1.4 > -1.5 → пропускаем pnl_pct = -1.5% → -1.5 <= -1.5 → ре-хедж ✅ pnl_pct = -2.0% → -2.0 <= -1.5 → ре-хедж ✅ ``` --- #### Этап 7: Зеркальный хедж - выравнивание объёмов ```rust // Зеркальный хедж: выравниваем объёмы if bigger_is_long { // LONG больше, чем SHORT → нарастить SHORT до LONG let need_short = (pos.long_qty - pos.short_qty).max(0.0); if need_short > 0.0 { actions.push(LiveAction::OpenShort(need_short, price)); } } else { // SHORT больше, чем LONG → нарастить LONG до SHORT let need_long = (pos.short_qty - pos.long_qty).max(0.0); if need_long > 0.0 { actions.push(LiveAction::OpenLong(need_long, price)); } } // обновляем базовый юнит и состояние машины pos.base_qty = pos.long_qty.min(pos.short_qty); machine.phase = PositionPhase::Both; machine.cut_count = 0; machine.boosted_once = false; ``` **Логика:** 1. Определяем сколько нужно добавить: - `need_short = long_qty - short_qty` (если LONG больше) - `need_long = short_qty - long_qty` (если SHORT больше) 2. Открываем противоположную сторону: - `OpenShort(need_short, price)` (если LONG больше) - `OpenLong(need_long, price)` (если SHORT больше) 3. Обновляем базовый юнит: `base_qty = min(long_qty, short_qty)` 4. Сбрасываем машину: `phase = Both, cut_count = 0, boosted_once = false` **Пример LONG больше:** ``` До ре-хеджа: - LONG = 1000, SHORT = 500 - entry_long = 0.002000 - current_price = 0.001970 (pnl = -1.5%) Ре-хедж: - need_short = 1000 - 500 = 500 - OpenShort(500, 0.001970) После ре-хеджа: - LONG = 1000, SHORT = 1000 - base_qty = 1000 - machine.phase = Both - machine.cut_count = 0 - machine.boosted_once = false ``` **Пример SHORT больше:** ``` До ре-хеджа: - LONG = 500, SHORT = 1000 - entry_short = 0.002000 - current_price = 0.002030 (pnl = -1.5%) Ре-хедж: - need_long = 1000 - 500 = 500 - OpenLong(500, 0.002030) После ре-хеджа: - LONG = 1000, SHORT = 1000 - base_qty = 1000 - machine.phase = Both - machine.cut_count = 0 - machine.boosted_once = false ``` --- ## 📊 Полный граф состояний ``` ┌─────────────────────────────────────────────────────────┐ │ BOTH (LONG + SHORT) │ └─────────────────────────────────────────────────────────┘ │ ├─→ process() │ ▼ ┌─────────────────────┐ │ update_phase() │ │ (PositionMachine) │ └─────────────────────┘ │ ┌────────────────┼────────────────┐ │ │ │ Single Both/Boosted ExitReady (пропускаем) /WindDown (финальный выход) │ │ │ │ ┌──────┴──────┐ │ │ │ │ │ │ Резка Разворот │ │ (3%/1.5%) (-1% / -1.5%)│ │ │ │ │ │ ▼ ▼ │ │ VolatilityCycle Ре-хедж │ │ (Close) (OpenOpposite)│ │ │ │ │ │ ▼ │ │ │ on_cut() │ │ │ update_phase() │ │ │ │ │ └──────────────────────┴───────┘ │ ▼ Выход по фазе: - Both → Boosted - Boosted → WindDown - WindDown → ExitReady - ExitReady → CLOSE ALL (+0.5%) ``` --- ## 🎯 Сценарии работы ### Сценарий 1: Обычная резка в BOTH ``` 01:00 → BOTH: LONG 1000, SHORT 1000 📊 BOTH СТАТУС: LONG: +3.00% (сырой: +3.15%, чистый: +3.00%) SHORT: -2.00% (сырой: -1.85%, чистый: -2.00%) Дельта: +1.00% Порог резки: 3.0% (boosted_once=false) ⚠️ БЛИЗО К РЕЗКЕ: 100.0% от порога! 01:05 → ✂️ РЕЗКА через VolatilityCycle - Закрываем LONG 1000 (прибыльная сторона) - Закрываем SHORT 300 (убыточная сторона, динамический %) - Открываем LONG 1000 (перезакупаем прибыльную) - on_cut() → cut_count = 1 - update_phase() → phase = Boosted 01:10 → BOTH: LONG 1000, SHORT 700 (base = 700) 📊 BOTH СТАТУС: LONG: +2.50% SHORT: -1.00% Дельта: +1.50% Порог резки: 1.5% (boosted_once=true) ``` --- ### Сценарий 2: Разворот на -1% от last_profit_rebuy_price ``` 00:00 → BOTH: LONG 1000, SHORT 500 last_profit_rebuy_price = 0.002000 00:30 → Резка LONG (прибыльная) - Close LONG 1000 - Close SHORT 200 - Open LONG 1000 - last_profit_rebuy_price = 0.002100 01:00 → BOTH: LONG 1000, SHORT 300 current_price = 0.002085 (-0.95% от last_profit_rebuy_price) 01:05 → current_price = 0.002079 (-1.0%) 🔄 РАЗВОРОТ НА -1%: цена 0.002079 <= 0.002079 (last_profit_rebuy_price * 0.99) Выравнивание: - need_short = 1000 - 300 = 700 - OpenShort(700, 0.002079) 01:05 → BOTH: LONG 1000, SHORT 1000 (зеркальный хедж) base_qty = 1000 ``` --- ### Сценарий 3: Авто-ре-хедж на -1.5% PnL бОльшей стороны ``` 00:00 → BOTH: LONG 1000, SHORT 500 (LONG больше) entry_long = 0.002000 00:30 → current_price = 0.001970 PnL% LONG = (0.001970 - 0.002000) / 0.002000 × 100 = -1.5% 🚨 АВТО-РЕ-ХЕДЖ: LONG PnL = -1.5% Ре-хедж: - need_short = 1000 - 500 = 500 - OpenShort(500, 0.001970) 00:30 → BOTH: LONG 1000, SHORT 1000 (зеркальный хедж) base_qty = 1000 machine.phase = Both machine.cut_count = 0 machine.boosted_once = false ``` --- ### Сценарий 4: Выход в фазе ExitReady ``` ... (последовательные резки) ... 05:00 → BOTH: LONG 100, SHORT 100 (base = 100) machine.phase = ExitReady 05:05 → Расчёт прибыли: - base_notional = 100 × 0.002000 × 1.0 = 0.2 USDT - net PnL = 0.001 USDT - profit_pct = 0.001 / 0.2 × 100 = 0.5% ✅ 05:05 → CLOSE ALL: - CloseLong(100, price, false) - CloseShort(100, price, false) - machine.on_exit() - Сессия завершена ``` --- ## ⚠️ Критические моменты ### 1. Авто-ре-хедж на -1.5% PnL бОльшей стороны **Зачем:** Защита от разворота против большей стороны **Логика:** 1. Определяем бОльшую сторону (по объёму) 2. Рассчитываем её PnL% 3. Если PnL% ≤ -1.5% → выравниваем объёмы **Пример проблемы:** ``` LONG = 1000, SHORT = 500 (LONG больше) entry_long = 0.002000 current_price = 0.001950 (PnL = -2.5%) Если не ре-хеджироваться: - Продолжаем терять на LONG - SMALL SHORT не компенсирует С ре-хеджированием: - OpenShort(500, 0.001950) - LONG = 1000, SHORT = 1000 - Зеркальный хедж фиксирует убыток - Ждём разворота ``` --- ### 2. Разворот на -1% от last_profit_rebuy_price **Зачем:** Защита от резкого разворота после резки **Логика:** 1. Сохраняем цену при перезакупке прибыльной стороны 2. Если цена падает на -1% от этой цены → выравниваем **Пример:** ``` До резки: - LONG = 1000, SHORT = 500 Резка: - Закрываем LONG 1000 (прибыль) - Закрываем SHORT 200 (убыток) - Открываем LONG 1000 (перезакупка) - last_profit_rebuy_price = 0.002100 Если цена упадёт до 0.002079 (-1%): - Выравниваем LONG и SHORT - Фиксируем прибыль ``` --- ### 3. Учёт комиссий и funding **Формула:** ```rust cost_pct = (taker_fee_rate × 2.0 + funding_rate × 2.0) × 100.0 ``` **Что учитывается:** - `taker_fee × 2` → туда-обратно (enter + exit) - `funding × 2` → туда-обратно **Почему важно:** - Сырой PnL может быть +3.0% - Чистый PnL = +2.85% (после комиссий) - Если резать по сырому → можем получить убыток --- ### 4. Динамический порог резки (3% / 1.5%) **Логика:** - Первый цикл: `trigger_pct = 3.0%` - После BOOST: `trigger_pct = 1.5%` **Почему:** - Первый цикл → высокая неопределённость → нужен запас - После BOOST → позиция уже сузилась → можно резать чаще --- ## 📊 Параметры и их источники | Параметр | Значение | Источник | |----------|----------|----------| | `REHEDGE_PNL_TRIGGER_PCT` | -1.5% | Hardcoded const | | `EXIT_READY_PROFIT_PCT` | 0.5% | Hardcoded const | | `trigger_pct` | 3.0% / 1.5% | machine.boosted_once | | `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 / биржа | | `last_profit_rebuy_price` | из PositionSnapshot | Redis / резки | | `base_qty` | из PositionSnapshot | Redis / выравнивания | | `cut_count` | из PositionMachine | внутренний счётчик | | `boosted_once` | из PositionMachine | внутренний флаг | | `phase` | из PositionMachine | update_phase() | --- ## 🔗 Интеграция с другими модулями ### 1. VolatilityCycle **Используется:** Для определения момента резки **Вызовы:** - `VolatilityCycle::check()` - проверяет условие резки - `VolatilityCycle::execute()` - генерирует actions для резки **См. подробнее в `volatility_cycle.md`** --- ### 2. PositionMachine **Используется:** Для фазового управления **Вызовы:** - `machine.update_phase(pos)` - актуализация фазы - `machine.on_cut()` - обработка резки (увеличение cut_count) - `machine.on_exit()` - обработка выхода **Фазы:** - `Single` - обрабатывается SingleStrategy - `Both` - базовый BOTH - `Boosted` - после первого BOOST-цикла - `WindDown` - последующие резки - `ExitReady` - финальный выход --- ### 3. PositionSnapshot **Используемые поля:** ```rust pos.long_qty pos.short_qty pos.entry_long pos.entry_short pos.last_price pos.contract_value pos.unrealized_long() pos.unrealized_short() pos.total_pnl_at_price(price) pos.base_qty pos.last_profit_rebuy_price pos.last_cooldown_log_time ``` **Изменяемые поля:** ```rust pos.base_qty = pos.long_qty.min(pos.short_qty) pos.last_cooldown_log_time = now ``` --- ### 4. TradingMeta **Используемые поля:** ```rust meta.price meta.taker_fee_rate meta.maker_fee_rate meta.funding_rate ``` --- ## 🔄 Поток данных ``` live_step() ↓ BothStrategy::process() ↓ ├─→ update_phase() (PositionMachine) │ ├─→ match phase: │ ├─→ Single → пропускаем (SingleStrategy) │ ├─→ Both/Boosted/WindDown: │ │ ├─→ Расчёт PnL% по сторонам │ │ ├─→ Расчёт комиссий │ │ ├─→ Логирование (раз в 5 сек) │ │ ├─→ Проверка резки (VolatilityCycle::check) │ │ ├─→ Проверка разворота (-1%) │ │ └─→ Проверка авто-ре-хеджа (-1.5%) │ │ │ └─→ ExitReady: │ ├─→ Расчёт прибыли │ ├─→ Проверка выхода (+0.5%) │ └─→ CLOSE ALL │ └─→ return actions ``` --- ## 📝 Логирование ### BOTH статус: ``` 📊 BOTH СТАТУС: LONG: +2.85% (сырой: +3.00%, чистый: +2.85%) SHORT: -1.85% (сырой: -1.70%, чистый: -1.85%) Дельта: +1.00% Порог резки: 3.0% (boosted_once=false) Комиссии+фандинг: 0.152% ⚠️ БЛИЗО К РЕЗКЕ: 95.0% от порога! ``` ### Разворот на -1%: ``` 🔄 РАЗВОРОТ НА -1%: цена 0.002079 <= 0.002079 (last_profit_rebuy_price * 0.99) ✅ Выравнивание: добавлено 500 SHORT, теперь 1000/1000 ``` ### Авто-ре-хедж: ``` 🚨 АВТО-РЕ-ХЕДЖ: LONG PnL = -1.5% ✅ Ре-хедж: добавлено 500 SHORT, теперь 1000/1000 ``` --- ## 🚀 Использование в коде ### Пример вызова в live_step.rs: ```rust // Проверяем что это BOTH режим if pos.long_qty > 0.0 && pos.short_qty > 0.0 { // Вызываем BothStrategy let actions = both_strategy.process(&mut pos, meta, &mut machine); if !actions.is_empty() { return actions; // Исполняем действия } } ``` --- ## 📊 Важные формулы ### 1. PnL% LONG ``` pnl_long_pct = unrealized_long / (long_qty × entry_long × contract_value) × 100 ``` **Пример:** ``` 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_long_pct = 0.06 / 2.0 × 100 = +3.0% ``` ### 2. PnL% SHORT ``` pnl_short_pct = unrealized_short / (short_qty × entry_short × contract_value) × 100 ``` **Пример:** ``` short_qty = 1000 entry_short = 0.002000 contract_value = 1.0 current_price = 0.001940 notional = 1000 × 0.002000 × 1.0 = 2.0 USDT unrealized_short = (0.002000 - 0.001940) × 1000 × 1.0 = 0.06 USDT pnl_short_pct = 0.06 / 2.0 × 100 = +3.0% ``` ### 3. Дельта (сумма PnL%) ``` delta_pct = pnl_long_pct + pnl_short_pct ``` **Пример:** ``` pnl_long_pct = +3.0% pnl_short_pct = -2.0% delta = 3.0 + (-2.0) = +1.0% ``` ### 4. Комиссии и 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% ``` ### 5. Чистый PnL ``` pnl_clean = pnl_raw - cost_pct ``` **Пример:** ``` pnl_long_raw = +3.0% cost_pct = 0.1524% pnl_long_clean = 3.0 - 0.1524 = 2.8476% ``` ### 6. Порог разворота на -1% ``` reverse_threshold = last_profit_rebuy_price × 0.99 ``` **Пример:** ``` last_profit_rebuy_price = 0.002100 reverse_threshold = 0.002100 × 0.99 = 0.002079 ``` --- ## 🔍 Проверка работы ### Проверка 1: Правильная работа резки 1. Открыть BOTH позицию 2. Дождаться +3% PnL по одной стороне 3. Проверить лог: ``` 📊 BOTH СТАТУС: LONG: +3.00% (сырой: +3.15%, чистый: +3.00%) SHORT: -2.00% (сырой: -1.85%, чистый: -2.00%) ⚠️ БЛИЗО К РЕЗКЕ: 100.0% от порога! ``` 4. Проверить что произошла резка через VolatilityCycle ### Проверка 2: Правильная работа разворота на -1% 1. Сделать резку (фиксация прибыли) 2. Проверить `last_profit_rebuy_price` в Redis 3. Дождаться падения цены на -1% от этого значения 4. Проверить лог: ``` 🔄 РАЗВОРОТ НА -1%: цена 0.002079 <= 0.002079 ✅ Выравнивание: добавлено 500 SHORT, теперь 1000/1000 ``` ### Проверка 3: Правильная работа авто-ре-хеджа 1. Открыть несбалансированную BOTH (LONG 1000, SHORT 500) 2. Дождаться PnL% LONG = -1.5% 3. Проверить лог: ``` 🚨 АВТО-РЕ-ХЕДЖ: LONG PnL = -1.5% ✅ Ре-хедж: добавлено 500 SHORT, теперь 1000/1000 ``` ### Проверка 4: Правильная работа ExitReady 1. Сузить позицию через несколько резок до маленького объёма 2. Дождаться прибыли +0.5% 3. Проверить что произошло CLOSE ALL --- ## 📚 Связанные файлы | Файл | Связь | |------|-------| | `src/live/state.rs` | PositionSnapshot структура | | `src/live/volatility_cycle.rs` | VolatilityCycle (резки) | | `src/live/position_machine.rs` | PositionMachine (фазы) | | `src/live/actions.rs` | LiveAction enum | | `src/live/meta.rs` | TradingMeta структура | | `src/live/step.rs` | live_step orchestration | --- ## 🎯 Резюме **Что делает BothStrategy:** 1. ✅ Работает ТОЛЬКО когда обе стороны активны (LONG + SHORT) 2. ✅ Резки через VolatilityCycle (3% первый, 1.5% после BOOST) 3. ✅ Авто-ре-хедж при развороте против бОльшей стороны (-1.5%) 4. ✅ Выравнивание при развороте на -1% от last_profit_rebuy_price 5. ✅ Фазовое управление через PositionMachine 6. ✅ Выход в ExitReady при +0.5% прибыли 7. ✅ Учёт комиссий и funding **Когда пропускает:** - ❌ Single режим (одна сторона) - ❌ Не достигнут порог резки - ❌ Не достигнут порог разворота **Особенности:** - 🔥 Динамический порог (3% / 1.5%) - 🔥 Двойная защита (разворот на -1% И ре-хедж на -1.5%) - 🔥 Фазовое управление (Both → Boosted → WindDown → ExitReady) --- **Дата создания:** 2026-02-22 **Автор:** Claude Code Assistant **Версия:** 1.0