// ===========================
// File: src/live/strategy_core.rs
// ===========================
//
// CoreStrategy — первый вход в позицию (SINGLE режим)
//
// Интеграция с entry_calculator:
// - Читает сохранённый расчёт из Redis
// - Рассчитывает новые параметры с entry_calculator
// - Проверяет DistanceToLIQ ≥ 100%
// - Использует МАКСИМАЛЬНОЕ плечо из биржи
// - Ограничивает маржу до 2$
//
// Если параметры не соответствуют — возвращает пустой actions
// → main.rs завершает сессию → ищет новую пару
//
// ===========================
use crate::live::state::PositionSnapshot;
use crate::live::meta::TradingMeta;
use crate::live::actions::LiveAction;
use crate::live::entry_calculator::{self, EntryParams};
use crate::gate::GateClient;
use crate::config::Config;
use std::sync::Arc;
use crate::redis::RedisCore;
pub struct CoreStrategy {
}
/// ================================================================
/// ensure_dual_cross_before_trade - ПРИНУДИТЕЛЬНАЯ установка DUAL+CROSS
/// ================================================================
///
/// ВАЖНО: Вызывать ПЕРЕД первым ордером по контракту!
///
/// Порядок операций (критично!):
/// 1. Лог текущего состояния
/// 2. Включить dual_mode (если нужно)
/// 3. Установить CROSS через dual_comp endpoint
/// 4. Лог состояния ПОСЛЕ
/// 5. Установить leverage (ПОСЛЕ cross_mode!)
///
async fn ensure_dual_cross_before_trade(
gate: &GateClient,
contract: &str,
leverage: f64,
) -> Result<(), String> {
println!("\n🔧 === ENSURE DUAL+CROSS ДО ТОРГОВЛИ ===");
// A) Лог текущего состояния ДО
println!("📊 A) ТЕКУЩЕЕ СОСТОЯНИЕ ДО:");
let mut cross_found_before = false;
match gate.get_dual_positions(contract).await {
Ok(positions) => {
// dual_comp возвращает ОДИН объект с двумя полями: long и short
let mode = positions.get("mode").and_then(|m| m.as_str()).unwrap_or("unknown");
let margin = positions.get("margin").and_then(|m| m.as_str()).and_then(|s| s.parse::<f64>().ok()).unwrap_or(0.0);
println!(" contract={}, mode={}, margin={}",
contract, mode, margin);
if margin == 0.0 {
cross_found_before = true;
println!(" ✅ CROSS уже установлен! (margin=0)");
} else {
println!(" ⚠️ ВНИМАНИЕ: margin={} > 0, значит ISOLATED!", margin);
}
}
Err(e) => println!(" ⚠️ Не удалось прочитать позиции: {}", e),
}
// B) Включить DUAL mode
println!("\n🔧 B) ВКЛЮЧАЕМ DUAL MODE:");
match gate.set_dual_mode_enabled(true).await {
Ok(_) => println!(" ✅ DUAL mode включен"),
Err(e) => println!(" ⚠️ Ошибка включения DUAL: {} (возможно уже включён)", e),
}
// C) Установить CROSS через dual_comp endpoint
println!("\n🔧 C) УСТАНАВЛИВАЕМ CROSS для DUAL:");
match gate.set_dual_cross_mode(contract, "CROSS").await {
Ok(resp) => {
println!(" ✅ CROSS для DUAL установлен");
}
Err(e) => {
println!(" ❌ ОШИБКА УСТАНОВКИ CROSS: {}", e);
return Err(format!("Не удалось установить CROSS для DUAL: {}", e));
}
}
// D) 🔥🔥 Установить leverage через DUAL_COMP endpoint!
println!("\n🔧 D) УСТАНАВЛИВАЕМ LEVERAGE {}x (DUAL+CROSS):", leverage);
match gate.update_dual_leverage(contract, leverage).await {
Ok(_) => println!(" ✅ DUAL leverage установлен (leverage=0, cross_leverage_limit={})", leverage),
Err(e) => {
println!(" ❌ ОШИБКА УСТАНОВКИ LEVERAGE: {}", e);
return Err(format!("Не удалось установить DUAL leverage: {}", e));
}
}
// E) Лог состояния ПОСЛЕ + КРИТИЧЕСКАЯ ПРОВЕРКА
println!("\n📊 E) СОСТОЯНИЕ ПОСЛЕ (критическая проверка):");
let mut cross_confirmed = false;
match gate.get_dual_positions(contract).await {
Ok(positions) => {
let mode = positions.get("mode").and_then(|m| m.as_str()).unwrap_or("unknown");
let margin = positions.get("margin").and_then(|m| m.as_str()).and_then(|s| s.parse::<f64>().ok()).unwrap_or(0.0);
println!(" contract={}, mode={}, margin={}",
contract, mode, margin);
// 🔥🔥 ПРАВИЛЬНАЯ ПРОВЕРКА: margin == 0 значит CROSS
if margin == 0.0 {
cross_confirmed = true;
}
}
Err(e) => println!(" ⚠️ Не удалось прочитать позиции: {}", e),
}
// КРИТИЧЕСКАЯ ПРОВЕРКА: Если CROSS НЕ подтверждён - НЕ торгуем!
if !cross_confirmed {
println!("❌ КРИТИЧЕСКАЯ ОШИБКА: margin != 0 после настройки!");
println!("❌ Это значит ISOLATED режим, а не CROSS!");
println!("❌ ТОРГОВЛЯ ПРЕКРАЩЕНА для защиты депозита!");
println!("🔧 === КОНЕЦ ENSURE DUAL+CROSS (ОШИБКА) ===\n");
return Err("margin не равен 0 - значит ISOLATED вместо CROSS".to_string());
}
println!(" ✅ ПОДТВЕРЖДЕНО: Контракт в CROSS режиме! (margin=0)");
println!("🔧 === КОНЕЦ ENSURE DUAL+CROSS (УСПЕХ) ===\n");
Ok(())
}
impl CoreStrategy {
pub fn new(_cfg: &Config) -> Self {
Self { }
}
/// ============================================================
/// ПЕРВЫЙ ВХОД (process_with_real_limits) — с реальными лимитами
/// ============================================================
pub async fn process_with_real_limits(
&self,
redis: Arc<RedisCore>,
gate: &GateClient,
pos: &PositionSnapshot,
meta: &TradingMeta,
current_balance: f64,
) -> Vec<LiveAction> {
let mut actions = Vec::new();
// FIX: если хоть что-то открыто — CoreStrategy выключен
if pos.long_qty > 0.0 || pos.short_qty > 0.0 {
return actions;
}
let equity_now = (current_balance + pos.realized_pnl).max(0.0);
if equity_now <= 0.0 || meta.price <= 0.0 {
return actions;
}
let contract = &meta.symbol;
// =========================================
// 🔥 ЧИТАЕМ СОХРАНЁННЫЙ РАСЧЁТ ИЗ REDIS
// =========================================
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
}
};
// =========================================
// 🔥 РАССЧИТЫВАЕМ НОВЫЕ ПАРАМЕТРЫ ВХОДА
// =========================================
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 = не входить
}
};
// =========================================
// 🔥 ПРОВЕРЯЕМ СООТВЕТСТВИЕ ПАРАМЕТРОВ
// =========================================
if !calc.meets_liq_threshold {
println!("❌ Пара {} не соответствует условиям: DistanceToLIQ {:.2}% < 100%",
contract, calc.distance_to_liq);
return actions; // Пустой actions = не входить
}
// =========================================
// 🔥 СОХРАНЯЕМ РАСЧЁТ В REDIS
// =========================================
if let Err(e) = entry_calculator::save_calculation(redis.clone(), contract, &calc).await {
println!("⚠️ Ошибка сохранения расчёта в Redis: {:?}", e);
}
// =========================================
// 🔥 ПРОВЕРКА: Есть ли уже открытые позиции на бирже?
// =========================================
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);
}
}
// =========================================
// 🔥 ПРИНУДИТЕЛЬНАЯ УСТАНОВКА DUAL+CROSS + MAX LEVERAGE
// =========================================
// КРИТИЧНО: Без этого позиции открываются в ISOLATED!
// 🔥🔥 ИСПОЛЬЗУЕМ МАКСИМАЛЬНОЕ ПЛЕЧО ИЗ БИРЖИ!
if let Err(e) = ensure_dual_cross_before_trade(gate, contract, calc.leverage).await {
println!("❌ КРИТИЧЕСКАЯ ОШИБКА: Не удалось установить DUAL+CROSS: {}", e);
println!("❌ ТОРГОВЛЯ ПРЕКРАЩЕНА для защиты депозита!");
return actions;
}
// =========================================
// 🔥 ВХОДИМ В ПОЗИЦИЮ
// =========================================
let qty = calc.qty;
let margin = calc.margin_used;
if qty <= 0.0 {
return actions;
}
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
);
// 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));
}
actions
}
}