← Назад к документации
strategy_core
Исходный код Rust - Trading AI
📄 Rust
📦 Модуль
🔧 Исходный код
// ===========================
// 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
    }
}