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