← Назад к главному
# strategy_core.rs - Модуль первого входа в позицию
## 📋 Назначение модуля
`CoreStrategy` отвечает за **первый вход** в позицию (SINGLE режим). Это критический модуль, который:
1. Проверяет что позиция полностью пустая (no LONG, no SHORT)
2. Загружает/рассчитывает параметры входа (qty, leverage, margin)
3. Проверяет что `DistanceToLIQ ≥ 100%` (безопасность от ликвидации)
4. **Принудительно устанавливает DUAL+CROSS режим** с проверкой
5. Открывает первую позицию (LONG или SHORT)
Если условия не выполняются - возвращает пустой `actions[]` → `main.rs` завершает сессию → ищет новую пару.
---
## 🏗️ Структура модуля
### Объекты
```rust
pub struct CoreStrategy { }
```
**Параметры входа (EntryParams):**
```rust
pub struct EntryParams {
pub margin_limit_usd: f64, // 2.0 USDT - лимит маржи на вход
pub min_distance_to_liq_pct: f64, // 100.0% - минимальное расстояние до ликвидации
}
```
### Основные функции
#### 1. `ensure_dual_cross_before_trade()`
**Строки:** 44-134
**Назначение:** Принудительная установка DUAL+CROSS режима ПЕРЕД первым ордером
**Критичность:** 🔥🔥🔥 КРИТИЧЕСКИ ВАЖНО! Без этого позиции открываются в ISOLATED режиме!
**Алгоритм (5 шагов):**
```
A) Лог текущего состояния ДО
↓ get_dual_positions(contract)
↓ Проверяем mode и margin
↓ Если margin == 0 → CROSS уже установлен
B) Включить DUAL mode
↓ set_dual_mode_enabled(true)
C) Установить CROSS через dual_comp endpoint
↓ set_dual_cross_mode(contract, "CROSS")
D) Установить leverage (ПОСЛЕ cross_mode!)
↓ update_dual_leverage(contract, leverage)
↓ Важно: leverage устанавливается ПОСЛЕ cross_mode!
E) Лог состояния ПОСЛЕ + КРИТИЧЕСКАЯ ПРОВЕРКА
↓ get_dual_positions(contract)
↓ Проверяем margin == 0
↓ Если margin != 0 → КРИТИЧЕСКАЯ ОШИБКА (ISOLATED вместо CROSS)
```
**Проверки:**
- ✅ **margin == 0** → CROSS режим подтверждён
- ❌ **margin != 0** → ISOLATED режим → ОШИБКА → НЕ торгуем
**Возвращает:**
- `Ok(())` - режим установлен корректно
- `Err(String)` - ошибка, торговля запрещена
**Пример лога:**
```
🔧 === ENSURE DUAL+CROSS ДО ТОРГОВЛИ ===
📊 A) ТЕКУЩЕЕ СОСТОЯНИЕ ДО:
contract=MEMES_USDT, mode=dual, margin=0
✅ CROSS уже установлен! (margin=0)
🔧 B) ВКЛЮЧАЕМ DUAL MODE:
✅ DUAL mode включен
🔧 C) УСТАНАВЛИВАЕМ CROSS для DUAL:
✅ CROSS для DUAL установлен
🔧 D) УСТАНАВЛИВАЕМ LEVERAGE 50.0x (DUAL+CROSS):
✅ DUAL leverage установлен (leverage=0, cross_leverage_limit=50.0)
📊 E) СОСТОЯНИЕ ПОСЛЕ (критическая проверка):
contract=MEMES_USDT, mode=dual, margin=0
✅ ПОДТВЕРЖДЕНО: Контракт в CROSS режиме! (margin=0)
🔧 === КОНЕЦ ENSURE DUAL+CROSS (УСПЕХ) ===
```
---
#### 2. `CoreStrategy::process_with_real_limits()`
**Строки:** 144-282
**Назначение:** Основная логика первого входа в позицию
**Сигнатура:**
```rust
pub async fn process_with_real_limits(
&self,
redis: Arc, // Redis клиент
gate: &GateClient, // Gate.io API
pos: &PositionSnapshot, // Текущее состояние позиции
meta: &TradingMeta, // Метаданные контракта
current_balance: f64, // Текущий баланс (equity)
) -> Vec
```
**Алгоритм (11 этапов):**
### Этап 1: Проверка пустой позиции
```rust
if pos.long_qty > 0.0 || pos.short_qty > 0.0 {
return actions; // CoreStrategy выключен если что-то открыто
}
```
### Этап 2: Проверка баланса и цены
```rust
let equity_now = (current_balance + pos.realized_pnl).max(0.0);
if equity_now <= 0.0 || meta.price <= 0.0 {
return actions;
}
```
### Этап 3: Чтение сохранённого расчёта из Redis
```rust
let _saved_calc = match entry_calculator::load_calculation(redis.clone(), contract).await {
Ok(Some(calc)) => Some(calc),
Ok(None) => {
println!("⚠️ Сохранённый расчёт не найден, пересчитываем...");
None
}
Err(e) => {
println!("❌ Ошибка загрузки расчёта из Redis: {:?}", e);
None
}
};
```
**Redis ключ:** `hb:entry_calc:{symbol}`
### Этап 4: Расчёт параметров входа
```rust
let entry_params = EntryParams {
margin_limit_usd: 2.0, // Макс маржа 2$
min_distance_to_liq_pct: 100.0, // DistanceToLIQ ≥ 100%
};
let calc = match entry_calculator::calculate_entry(meta, equity_now, Some(entry_params)) {
Ok(c) => c,
Err(e) => {
println!("❌ Пара {} не подходит для входа: {}", contract, e);
return actions; // Пустой actions = не входить
}
};
```
**Расчёт в entry_calculator:**
```
1. max_leverage = meta.leverage_max (например, 50x)
2. price_contract = meta.price × meta.contract_multiplier
3. margin_per_contract = price_contract / max_leverage
4. qty_by_margin = floor(margin_limit / margin_per_contract)
5. qty = max(qty_by_margin, min_contracts)
6. qty = min(qty, max_contracts)
7. qty = floor(qty / min_contracts) × min_contracts (кратность)
8. qty = min(qty, 4300) (MARKET лимит)
9. margin_used = qty × margin_per_contract
10. distance_to_liq = (equity - margin_used) / margin_used × 100
11. Итеративное уменьшение qty до distance_to_liq ≥ 100%
```
### Этап 5: Проверка DistanceToLIQ ≥ 100%
```rust
if !calc.meets_liq_threshold {
println!("❌ Пара {} не соответствует условиям: DistanceToLIQ {:.2}% < 100%",
contract, calc.distance_to_liq);
return actions;
}
```
### Этап 6: Сохранение расчёта в Redis
```rust
if let Err(e) = entry_calculator::save_calculation(redis.clone(), contract, &calc).await {
println!("⚠️ Ошибка сохранения расчёта в Redis: {:?}", e);
}
```
**Redis ключ:** `hb:entry_calc:{symbol}`
### Этап 7: Проверка открытых позиций на бирже
```rust
match gate.get_positions(contract).await {
Ok(positions) => {
if let Some(pos_array) = positions.as_array() {
for p in pos_array {
if let Some(size) = p["size"].as_i64() {
if size != 0 {
println!("⚠️ НАЙДЕНА ОТКРЫТАЯ ПОЗИЦИЯ на бирже: size={}, contract={}", size, contract);
println!("⚠️ Бот НЕ открывает новые позиции. Закрой старые вручную или дай боту управлять.");
return actions;
}
}
}
}
}
Err(e) => {
println!("⚠️ Не удалось проверить позиции: {}", e);
}
}
```
**Зачем:** Защита от дублирования позиций (если пользователь открыл вручную)
### Этап 8: Принудительная установка DUAL+CROSS + MAX LEVERAGE
```rust
if let Err(e) = ensure_dual_cross_before_trade(gate, contract, calc.leverage).await {
println!("❌ КРИТИЧЕСКАЯ ОШИБКА: Не удалось установить DUAL+CROSS: {}", e);
println!("❌ ТОРГОВЛЯ ПРЕКРАЩЕНА для защиты депозита!");
return actions;
}
```
**Критично:** Без этого позиции открываются в ISOLATED режиме → отдельная маржа → неэффективно!
### Этап 9: Проверка qty
```rust
if qty <= 0.0 {
return actions;
}
```
### Этап 10: Логирование параметров входа
```rust
println!(
"🔹 CORE ENTRY: equity={:.2}, price={:.6}, lev={:.1}x (MAX), qty={:.1}, margin={:.2} ({:.2}%), DistanceToLIQ={:.2}%",
equity_now,
meta.price,
calc.leverage,
qty,
margin,
(margin / equity_now * 100.0),
calc.distance_to_liq
);
```
### Этап 11: Генерация действий (actions)
```rust
// Leverage уже установлен в ensure_dual_cross_before_trade
actions.push(LiveAction::SetBaseQty(qty));
if meta.contract_multiplier > 0.0 {
actions.push(LiveAction::SetContractValue(meta.contract_multiplier));
}
if meta.chg_pct_24h > 0.0 {
actions.push(LiveAction::OpenLong(qty, 0.0));
} else {
actions.push(LiveAction::OpenShort(qty, 0.0));
}
```
**Логика направления:**
- `chg_pct_24h > 0` → LONG (пара росла за 24ч)
- `chg_pct_24h < 0` → SHORT (пара падала за 24ч)
---
## 🔗 Интеграция с другими модулями
### 1. Redis
**Читает:**
- `hb:entry_calc:{symbol}` - сохранённый расчёт входа
**Записывает:**
- `hb:entry_calc:{symbol}` - новый расчёт входа
### 2. Gate.io API
**Использует:**
- `get_positions(contract)` - проверка открытых позиций
- `get_dual_positions(contract)` - проверка DUAL режима
- `set_dual_mode_enabled(true)` - включение DUAL режима
- `set_dual_cross_mode(contract, "CROSS")` - установка CROSS
- `update_dual_leverage(contract, leverage)` - установка плеча
### 3. entry_calculator
**Использует:**
- `calculate_entry()` - расчёт параметров входа
- `save_calculation()` - сохранение в Redis
- `load_calculation()` - загрузка из Redis
### 4. main.rs / live_step
**Вызывается:**
- Из `live_step()` когда позиция пустая
- В начале каждой сессии LIVE
**Возвращает:**
- `Vec` - список действий для исполнения
- Пустой вектор → не входить → завершить сессию
---
## 📊 Параметры и их источники
| Параметр | Значение | Источник |
|----------|----------|----------|
| `margin_limit_usd` | 2.0 USDT | Hardcoded в EntryParams |
| `min_distance_to_liq_pct` | 100.0% | Hardcoded в EntryParams |
| `max_leverage` | meta.leverage_max | Gate.io API (TradingMeta) |
| `price` | meta.price | Redis / Gate.io API |
| `contract_multiplier` | meta.contract_multiplier | Redis / Gate.io API |
| `min_contracts` | meta.min_contracts | Gate.io API |
| `max_contracts` | meta.max_contracts | Gate.io API |
| `qty` | Рассчитывается | entry_calculator |
| `margin_used` | Рассчитывается | entry_calculator |
| `distance_to_liq` | Рассчитывается | entry_calculator |
| `chg_pct_24h` | meta.chg_pct_24h | Redis / Gate.io API |
| Направление | LONG/SHORT | chg_pct_24h > 0 ? |
---
## 🔧 Пример работы
### Сценарий 1: Успешный вход в LONG
**Входные данные:**
```
contract = "MEMES_USDT"
price = 0.00223650
contract_multiplier = 100
leverage_max = 50.0
min_contracts = 1.0
max_contracts = 6500
equity_now = 100.0 USDT
chg_pct_24h = +50.0%
```
**Шаги:**
1. ✅ Позиция пустая (no LONG, no SHORT)
2. ✅ Баланс = 100.0 USDT
3. ⚠️ Redis нет saved_calc → пересчитываем
4. 📐 Расчёт в entry_calculator:
- price_contract = 0.00223650 × 100 = 0.22365 USDT
- margin_per_contract = 0.22365 / 50.0 = 0.004473 USDT
- qty_by_margin = floor(2.0 / 0.004473) = 447
- qty = max(447, 1) = 447
- qty = min(447, 6500) = 447
- qty = floor(447 / 1) × 1 = 447
- qty = min(447, 4300) = 447 (MARKET лимит OK)
- margin_used = 447 × 0.004473 = 2.0 USDT
- distance_to_liq = (100 - 2) / 2 × 100 = 4900% ✅
5. ✅ DistanceToLIQ = 4900% ≥ 100%
6. 💾 Сохраняем calc в Redis
7. ✅ Позиций на бирже нет
8. 🔧 Установка DUAL+CROSS:
- ✅ margin = 0 до (CROSS уже установлен)
- ✅ DUAL mode включён
- ✅ CROSS установлен
- ✅ Leverage 50.0x установлен
- ✅ margin = 0 после (подтверждён CROSS)
9. 📊 Лог: `🔹 CORE ENTRY: equity=100.00, price=0.002237, lev=50.0x (MAX), qty=447.0, margin=2.00 (2.00%), DistanceToLIQ=4900.00%`
10. ✅ chg_pct_24h = +50% → LONG
11. 📦 Actions:
- `SetBaseQty(447.0)`
- `SetContractValue(100.0)`
- `OpenLong(447.0, 0.0)`
### Сценарий 2: Пара не подходит (DistanceToLIQ < 100%)
**Входные данные:**
```
contract = "DUMB_USDT"
price = 0.5 (дорогая монета)
contract_multiplier = 1
leverage_max = 10.0
min_contracts = 1.0
equity_now = 100.0 USDT
```
**Шаги:**
1. ✅ Позиция пустая
2. ✅ Баланс = 100.0 USDT
3. 📐 Расчёт в entry_calculator:
- price_contract = 0.5 × 1 = 0.5 USDT
- margin_per_contract = 0.5 / 10.0 = 0.05 USDT
- qty_by_margin = floor(2.0 / 0.05) = 40
- margin_used = 40 × 0.05 = 2.0 USDT
- distance_to_liq = (100 - 2) / 2 × 100 = 4900% ✅
4. ✅ OK (успешный вход)
**Но если equity_now = 2.1 USDT:**
```
margin_used = 40 × 0.05 = 2.0 USDT
distance_to_liq = (2.1 - 2) / 2 × 100 = 5% ❌
```
5. ❌ DistanceToLIQ = 5% < 100% → Пустой actions → Сессия завершена
### Сценарий 3: КРИТИЧЕСКАЯ ОШИБКА - ISOLATED вместо CROSS
**Входные данные:**
```
contract = "FAIL_USDT"
```
**Шаги:**
1-7. ✅ Все проверки пройдены
8. 🔧 Установка DUAL+CROSS:
- ✅ DUAL mode включён
- ❌ CROSS установить НЕ удалось (API ошибка)
- ❌ margin = 2.0 после (ISOLATED!)
9. ❌ КРИТИЧЕСКАЯ ОШИБКА: margin != 0
10. ❌ ТОРГОВЛЯ ПРЕКРАЩЕНА для защиты депозита!
11. 📦 Actions: пустой вектор → Сессия завершена
---
## ⚠️ Критические моменты
### 1. DistanceToLIQ ≥ 100%
**Зачем:** Защита от ликвидации
**Если DistanceToLIQ < 100%:**
- ❌ Бот НЕ входит в позицию
- ❌ Сессия завершается
- ✅ Ищет новую пару
**Пример проблемы:**
```
equity = 2.1 USDT
margin_used = 2.0 USDT
distance_to_liq = 5%
Минимальное движение цены на 5% → ЛИКВИДАЦИЯ!
```
### 2. DUAL+CROSS обязательный!
**Зачем:**
- **ISOLATED:** Каждая сторона (LONG/SHORT) использует отдельную маржу → неэффективно
- **CROSS:** Общая маржа для обеих сторон → можно хеджировать эффективно
**Если CROSS не установлен:**
- ❌ КРИТИЧЕСКАЯ ОШИБКА
- ❌ Торгогля ПРЕКРАЩЕНА
- ✅ Защита депозита
### 3. MARKET лимит 4300 контрактов
**Ограничение Gate.io:** MARKET ордер не может превышать 4300 контрактов
**В коде (строки 154-159):**
```rust
const MARKET_ORDER_MAX_QTY: f64 = 4300.0;
if qty > MARKET_ORDER_MAX_QTY {
println!(" ⚠️ Qty ({:.0}) > MARKET лимит ({:.0}), округляем вниз", qty, MARKET_ORDER_MAX_QTY);
qty = (MARKET_ORDER_MAX_QTY / step_contracts).floor() * step_contracts;
}
```
### 4. Проверка открытых позиций
**Зачем:** Защита от дублирования
**Если позиция уже открыта вручную:**
```
⚠️ НАЙДЕНА ОТКРЫТАЯ ПОЗИЦИЯ на бирже: size=500, contract=MEMES_USDT
⚠️ Бот НЕ открывает новые позиции. Закрой старые вручную или дай боту управлять.
```
**Actions:** пустой → Сессия завершена
---
## 🔄 Поток данных
```
main.rs
↓ live_step()
↓ CoreStrategy::process_with_real_limits()
↓
├─→ Redis (load_calculation)
│ └─→ hb:entry_calc:{symbol}
│
├─→ entry_calculator::calculate_entry()
│ ├─→ TradingMeta (из Redis)
│ ├─→ EntryParams (hardcoded)
│ └─→ EntryCalculation
│
├─→ Redis (save_calculation)
│ └─→ hb:entry_calc:{symbol}
│
├─→ Gate.io API
│ ├─→ get_positions() (проверка)
│ ├─→ ensure_dual_cross_before_trade()
│ │ ├─→ get_dual_positions() (проверка ДО)
│ │ ├─→ set_dual_mode_enabled()
│ │ ├─→ set_dual_cross_mode()
│ │ ├─→ update_dual_leverage()
│ │ └─→ get_dual_positions() (проверка ПОСЛЕ)
│ └─→ create_market_order() (в main.rs)
│
└─→ Vec
├─→ SetBaseQty(qty)
├─→ SetContractValue(multiplier)
└─→ OpenLong(qty, price) ИЛИ OpenShort(qty, price)
```
---
## 📝 Логирование
### Уровни логов:
**INFO:**
```
📐 РАСЧЁТ ВХОДА:
Пара: MEMES_USDT
Цена монеты: 0.00223650
Монет в контракте (contract_multiplier): 100
Плечо: 50.0x (max из биржи)
...
```
**SUCCESS:**
```
✅ РАСЧЁТ ВХОДА ЗАВЕРШЁН УСПЕШНО:
Qty: 447 контрактов
Leverage: 50.0x
Margin: 2.000000 USDT
DistanceToLIQ: 4900.00% (порог ≥ 100.00%)
```
**WARNING:**
```
⚠️ Сохранённый расчёт не найден, пересчитываем...
⚠️ Ошибка сохранения расчёта в Redis: ...
⚠️ Не удалось проверить позиции: ...
```
**ERROR:**
```
❌ Пара DUMB_USDT не подходит для входа: DistanceToLIQ 5.00% < 100%
❌ КРИТИЧЕСКАЯ ОШИБКА: Не удалось установить DUAL+CROSS: ...
❌ КРИТИЧЕСКАЯ ОШИБКА: margin не равен 0 - значит ISOLATED вместо CROSS
```
**ENTRY LOG:**
```
🔹 CORE ENTRY: equity=100.00, price=0.002237, lev=50.0x (MAX), qty=447.0, margin=2.00 (2.00%), DistanceToLIQ=4900.00%
```
---
## 🚀 Использование в коде
### Пример вызова в live_step.rs:
```rust
// Проверка пустой позиции
if pos.long_qty == 0.0 && pos.short_qty == 0.0 {
// Первый вход через CoreStrategy
let core_actions = core_strategy.process_with_real_limits(
redis.clone(),
gate,
pos,
meta,
current_balance,
).await;
if !core_actions.is_empty() {
return core_actions; // Исполняем действия
} else {
// Пара не подходит → завершаем сессию
println!("⚠️ Пара не подходит для входа");
session.end(SessionEndReason::PairNotSuitable);
return vec![];
}
}
```
---
## 📊 Важные формулы
### 1. DistanceToLIQ
```
DistanceToLIQ = (Equity - MarginUsed) / MarginUsed × 100
```
**Пример:**
```
Equity = 100.0 USDT
MarginUsed = 2.0 USDT
DistanceToLIQ = (100 - 2) / 2 × 100 = 4900%
```
**Значение:** Цена может упасть/расти на 4900% до ликвидации
### 2. Price Contract
```
Price_contract = Price_coin × Contract_multiplier
```
**Пример:**
```
Price_coin = 0.00223650 USDT (1 монета)
Contract_multiplier = 100 (100 монет в 1 контракте)
Price_contract = 0.00223650 × 100 = 0.22365 USDT (1 контракт)
```
### 3. Margin per Contract
```
Margin_per_contract = Price_contract / Leverage
```
**Пример:**
```
Price_contract = 0.22365 USDT
Leverage = 50.0x
Margin_per_contract = 0.22365 / 50.0 = 0.004473 USDT
```
**Значение:** На 1 контракт нужно 0.004473 USDT маржи при 50x плече
---
## 🔍 Проверка работы
### Проверка 1: Правильная работа DUAL+CROSS
1. Запустить бота с пустой позицией
2. Проверить лог:
```
🔧 === ENSURE DUAL+CROSS ДО ТОРГОВЛИ ===
📊 A) ТЕКУЩЕЕ СОСТОЯНИЕ ДО:
contract=MEMES_USDT, mode=dual, margin=0
✅ CROSS уже установлен! (margin=0)
...
📊 E) СОСТОЯНИЕ ПОСЛЕ (критическая проверка):
contract=MEMES_USDT, mode=dual, margin=0
✅ ПОДТВЕРЖДЕНО: Контракт в CROSS режиме! (margin=0)
```
3. Проверить вручную в Gate.io:
- Futures → Positions
- Убедиться что mode = "dual_cross"
### Проверка 2: Проверка DistanceToLIQ
1. Проверить лог:
```
✅ РАСЧЁТ ВХОДА ЗАВЕРШЁН УСПЕШНО:
Qty: 447 контрактов
Leverage: 50.0x
Margin: 2.000000 USDT
DistanceToLIQ: 4900.00% (порог ≥ 100.00%)
```
2. Проверить вручную:
- Futures → Positions
- Убедиться что "Distance to liquidation" ≥ 100%
### Проверка 3: Redis integration
1. Проверить что расчёт сохранён:
```bash
redis-cli GET "hb:entry_calc:MEMES_USDT" | jq
```
2. Ожидаемый JSON:
```json
{
"qty": 447.0,
"leverage": 50.0,
"margin_used": 2.0,
"distance_to_liq": 4900.0,
"meets_liq_threshold": true
}
```
---
## 📚 Связанные файлы
| Файл | Связь |
|------|-------|
| `src/live/entry_calculator.rs` | Расчёт параметров входа |
| `src/live/meta.rs` | TradingMeta структура |
| `src/live/actions.rs` | LiveAction enum |
| `src/gate.rs` | Gate.io API клиент |
| `src/redis.rs` | Redis клиент |
| `src/main.rs` | Вызов CoreStrategy |
| `src/live/step.rs` | live_step orchestration |
---
## 🎯 Резюме
**Что делает CoreStrategy:**
1. ✅ Входит в позицию ТОЛЬКО когда позиция пустая
2. ✅ Проверяет DistanceToLIQ ≥ 100%
3. ✅ Принудительно устанавливает DUAL+CROSS с проверкой
4. ✅ Ограничивает маржу до 2$
5. ✅ Использует МАКСИМАЛЬНОЕ плечо из биржи
6. ✅ Проверяет что нет открытых позиций на бирже
7. ✅ Определяет направление по chg_pct_24h
8. ✅ Генерирует actions для исполнения
**Если условия не выполнены:**
- ❌ Возвращает пустой actions[]
- ❌ main.rs завершает сессию
- ✅ Ищет новую пару
**Критичность:** 🔥🔥🔥 Без правильной работы CoreStrategy бот может:
- Открыть позицию в ISOLATED режиме (отдельная маржа)
- Войти с недостаточным DistanceToLIQ (риск ликвидации)
- Дублировать позиции
---
**Дата создания:** 2026-02-22
**Автор:** Claude Code Assistant
**Версия:** 1.0