← Назад к документации
hedge_guard
Исходный код Rust - Trading AI
📄 Rust
📦 Модуль
🔧 Исходный код
// ================================
// File: src/live/hedge_guard.rs
// ================================
//
// Авто-хедж при отклонении цены ПРОТИВ текущей позиции,
// когда позиция НЕ находится в BOTH.
// Хедж обязателен и выполняется максимально быстро.
// Плечо и qty фиксируются из текущей сессии.
//
// ВАЖНО:
//   - Раньше хедж срабатывал только в диапазоне 2–20%,
//     из-за этого прострелы >20% вообще НЕ хеджировались.
//   - Теперь хедж включается при |delta| >= 1.5% (без верхнего лимита).
//

use crate::live::state::PositionSnapshot;
use crate::live::meta::TradingMeta;
use crate::live::actions::LiveAction;
use serde::{Serialize, Deserialize};

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct HedgeSignal {
    pub long_qty: f64,
    pub short_qty: f64,
    pub add_long: f64,
    pub add_short: f64,
    pub reason: String,
}

pub struct HedgeGuard;

impl HedgeGuard {
    /// Проверка на необходимость хеджа.
    ///
    /// Логика:
    ///   - если есть LONG (безSHORT или с ним):
    ///       * считаем delta% от LONG entry
    ///       * если delta <= -2.5% → цена ушла против LONG → добавляем/усиливаем SHORT
    ///   - если есть SHORT (безLONG или с ним):
    ///       * считаем delta% от SHORT entry
    ///       * если delta >= 2.5% → цена ушла против SHORT → добавляем/усиливаем LONG
    ///
    /// ВАЖНО: Работает даже если BOTH уже есть - для балансировки/дохеджа
    ///
    /// 🔥 НОВАЯ ЛОГИКА: Cooldown после входа - не хеджировать N секунд
    pub fn check(
        pos: &mut PositionSnapshot,
        meta: &TradingMeta,
        entry_price: f64,
    ) -> Option<HedgeSignal> {
        if entry_price <= 0.0 {
            return None;
        }

        let px = meta.price;
        if px <= 0.0 {
            return None;
        }

        // 🔥🔥 COOLDOWN: Не хеджировать N секунд после входа!
        let now = chrono::Utc::now().timestamp();
        let elapsed = now - pos.last_entry_time;

        if elapsed < pos.hedge_cooldown_sec {
            // Логируем только 1 раз при начале cooldown
            if now - pos.last_cooldown_log_time >= 1 {  // Максимум 1 раз в секунду
                println!("  ⏸️ ХЕДЖ НА ПАУЗЕ: прошло {} сек из {} сек cooldown",
                    elapsed, pos.hedge_cooldown_sec);
                pos.last_cooldown_log_time = now;
            }
            return None;
        }

        // 🚨🚨 ЧРЕЗВЫЧАЙНАЯ ЗАЩИТА: Пустой стакан + резкая свеча
        // Работает ДО обычных проверок, чтобы сработать раньше при опасности
        let total_depth = meta.bid_qty_depth + meta.ask_qty_depth;
        let depth_threshold = 1000.0;  // Порог пустого стакана

        if total_depth < depth_threshold {
            // Определяем delta в зависимости от типа позиции
            let (delta_pct, is_profitable) = if pos.long_qty > 0.0 && pos.entry_long > 0.0 {
                // Для LONG: цена вниз = убыток
                ((px - pos.entry_long) / pos.entry_long * 100.0, px >= pos.entry_long)
            } else if pos.short_qty > 0.0 && pos.entry_short > 0.0 {
                // Для SHORT: цена вниз = прибыль, цена вверх = убыток
                ((pos.entry_short - px) / pos.entry_short * 100.0, px <= pos.entry_short)
            } else {
                (0.0, false)
            };

            // При пустом стакане снижаем порог до ±1.75%
            // Хеджим когда движение ПРОТИВ позиции (delta имеет "неправильный" знак)
            let need_hedge = if pos.long_qty > 0.0 {
                // LONG: хедж когда delta <= -1.75% (цена вниз)
                delta_pct <= -1.75
            } else if pos.short_qty > 0.0 {
                // SHORT: хедж когда delta <= -1.75% (цена вверх, значит delta отрицательный)
                delta_pct <= -1.75
            } else {
                false
            };

            if need_hedge {
                println!("  🚨🚨 ЧРЕЗВЫЧАЙНЫЙ ХЕДЖ!");
                println!("     - Пустой стакан: depth={:.2} < {:.2}", total_depth, depth_threshold);
                println!("     - Резкое движение: {:.2}% (экстренный порог ±1.75%)", delta_pct);

                // Определяем какую сторону хеджировать
                if pos.long_qty > 0.0 && pos.short_qty == 0.0 {
                    // LONG в опасности - добавляем SHORT
                    let hedge_qty = pos.long_qty;
                    return Some(HedgeSignal {
                        long_qty: pos.long_qty,
                        short_qty: hedge_qty,
                        add_long: 0.0,
                        add_short: hedge_qty,
                        reason: format!("🚨 EMERGENCY: Пустой стакан + резкая свеча {:.2}%", delta_pct),
                    });
                } else if pos.short_qty > 0.0 && pos.long_qty == 0.0 {
                    // SHORT в опасности - добавляем LONG
                    let hedge_qty = pos.short_qty;
                    return Some(HedgeSignal {
                        long_qty: hedge_qty,
                        short_qty: pos.short_qty,
                        add_long: hedge_qty,
                        add_short: 0.0,
                        reason: format!("🚨 EMERGENCY: Пустой стакан + резкая свеча {:.2}%", delta_pct),
                    });
                }
            }
        }

        // ЗАЩИТА: Не хеджировать если уже хедж есть
        if pos.long_qty > 0.0 && pos.short_qty > 0.0 {
            // Проверяем нужно ли дочедживание
            if pos.long_qty <= pos.short_qty {
                return None; // Хедж уже достаточный или избыточный
            }
        }

        // -------------------
        // Проверка LONG позиции (одиночная)
        // -------------------
        if pos.long_qty > 0.0 && pos.short_qty == 0.0 {
            let delta_pct = (px - pos.entry_long) / pos.entry_long * 100.0;

            // ОТЛАДКА - логируем только каждые 5 секунд ИЛИ при критическом значении
            let should_log = delta_pct <= -2.0 || (now - pos.last_cooldown_log_time >= 5);
            if should_log {
                println!("  📊 LONG хедж проверка: entry={}, current={}, delta_pct={:.3}%",
                    pos.entry_long, px, delta_pct);
                pos.last_cooldown_log_time = now;
            }

            // Цена ушла против LONG вниз на 1.5% и более (экстренная защита)
            if delta_pct <= -1.5 {
                println!("  🚨🚨 ЭКСТРЕННЫЙ ХЕЖ! delta_pct={:.3}% <= -1.5%", delta_pct);

                // Для одиночной LONG позиции хедж = SHORT того же размера
                let hedge_qty = pos.long_qty;

                return Some(HedgeSignal {
                    long_qty: pos.long_qty,     // LONG остается тем же
                    short_qty: hedge_qty,       // Новый SHORT размера LONG
                    add_long: 0.0,              // НЕ добавляем LONG
                    add_short: hedge_qty,       // Добавляем SHORT
                    reason: format!("Auto-hedge LONG: delta {:.2}%, add SHORT {}", delta_pct, hedge_qty),
                });
            }
        }

        // -------------------
        // Проверка SHORT позиции (одиночная)
        // -------------------
        if pos.short_qty > 0.0 && pos.long_qty == 0.0 {
            // Для SHORT: цена ВНИЗ = прибыль, цена ВВЕРХ = убыток
            let delta_pct = (pos.entry_short - px) / pos.entry_short * 100.0;

            // ОТЛАДКА - логируем только каждые 5 секунд ИЛИ при критическом значении
            let should_log = delta_pct >= 2.0 || (now - pos.last_cooldown_log_time >= 5);
            if should_log {
                println!("  📊 SHORT хедж проверка: entry={}, current={}, delta_pct={:.3}% ({}: {})",
                    pos.entry_short, px, delta_pct,
                    if delta_pct > 0.0 { "ПРИБЫЛЬ" } else { "УБЫТОК" },
                    if delta_pct > 0.0 { "цена вниз" } else { "цена вверх" }
                );
                pos.last_cooldown_log_time = now;
            }

            // Цена ушла против SHORT ВВЕРХ на 1.5% и более (экстренная защита)
            // delta_pct будет ОТРИЦАТЕЛЬНЫМ когда цена растет против SHORT
            if delta_pct <= -1.5 {
                println!("  🚨🚨 ЭКСТРЕННЫЙ ХЕЖ! delta_pct={:.3}% <= -1.5% (цена вверх против SHORT)", delta_pct);

                // Для одиночной SHORT позиции хедж = LONG того же размера
                let hedge_qty = pos.short_qty;

                return Some(HedgeSignal {
                    long_qty: hedge_qty,          // Новый LONG размера SHORT
                    short_qty: pos.short_qty,     // SHORT остается тем же
                    add_long: hedge_qty,          // Добавляем LONG
                    add_short: 0.0,               // НЕ добавляем SHORT
                    reason: format!("Auto-hedge SHORT: delta {:.2}%, add LONG {}", delta_pct, hedge_qty),
                });
            }
        }

        None
    }

    /// Выполнить хедж - возвращает actions для исполнения
    ///
    /// ВАЖНО:
    ///  - Если LONG в убытке → добавляем SHORT
    ///  - Если SHORT в убытке → добавляем LONG
    ///  - Возвращает Vec<LiveAction> для create_real_orders
    pub fn execute(
        pos: &PositionSnapshot,
        sig: HedgeSignal,
        px: f64,
    ) -> Vec<LiveAction> {
        let mut actions = Vec::new();

        println!("🛡️ ХЕДЖ СИГНАЛ: {}", sig.reason);

        // Был незахеджированный LONG — добавляем SHORT
        if pos.long_qty > 0.0 && pos.short_qty == 0.0 {
            if sig.short_qty > 0.0 {
                actions.push(LiveAction::OpenShort(sig.short_qty, px));
            }
        }

        // Был незахеджированный SHORT — добавляем LONG
        if pos.short_qty > 0.0 && pos.long_qty == 0.0 {
            if sig.long_qty > 0.0 {
                actions.push(LiveAction::OpenLong(sig.long_qty, px));
            }
        }

        println!("📊 ХЕДЖ создал {} actions", actions.len());
        actions
    }
}