// ================================
// 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
}
}