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