← Назад к документации
main
Исходный код Rust - Trading AI
📄 Rust
📦 Модуль
🔧 Исходный код
// ===========================
// File: src/main.rs
// ===========================

use std::sync::Arc;
use tokio::time::{sleep, Duration};
use chrono::Utc;

use crate::config::Config;
use crate::redis::RedisCore;
use crate::logger::Logger;
use crate::live::meta::fetch_trading_meta;
use crate::live::state::PositionSnapshot;
use crate::live::step::{live_step, LiveStepContext};
use crate::gate::GateClient;

mod config;
mod redis;
mod logger;
mod monitor;
mod live;
mod types;
mod feed;
mod balance;
mod gate;

/// Цель по прибыли за сессию (TP) как доля от стартового equity сессии.
/// Пример: 0.08 = 8% от session_start_equity.
const SESSION_TP_PCT: f64 = 0.08;

// Stop Loss полностью исключен из системы

#[tokio::main]
async fn main() {
    env_logger::init();

    let cfg = Config::load();

    // Создаем клиент Gate.io (ключи загружаются внутри)
    let gate = GateClient::new();

    // Logger будет создаваться для каждой сессии отдельно
    let mut global_logger = Logger::new("logs", "global")
        .expect("Failed to create global logger");

    global_logger
        .log_system("INFO", "main", "Система trading AI (NEW LIVE) запущена")
        .unwrap();
    global_logger
        .log_monitor("Инициализация подключения к Redis", None)
        .unwrap();

    let redis = Arc::new(
        RedisCore::new(&cfg.redis_url).expect("Не удалось создать Redis клиент"),
    );

    // 🔥 Проверяем что Redis реально работает
    match redis.ping().await {
        Ok(_) => {
            println!("✅ Redis PING/PONG - сервер работает!");
            global_logger
                .log_system("INFO", "main", "Redis PING/PONG - сервер работает")
                .unwrap();
        }
        Err(e) => {
            eprintln!("❌ {}", e);
            eprintln!("❌ Установи Redis: sudo apt install redis-server && sudo systemctl start redis-server");
            std::process::exit(1);
        }
    }

    // 🔥 ОЧИЩАЕМ Redis перед стартом (удаление всех ключей бота)
    match redis.delete_by_prefix("hb:*").await {
        Ok(count) => {
            if count > 0 {
                println!("🧹 Redis очищен: удалено {} старых ключей", count);
                global_logger
                    .log_system("INFO", "main", &format!("Redis очищен: удалено {} старых ключей", count))
                    .unwrap();
            } else {
                println!("✅ Redis чист - нет старых данных");
            }
        }
        Err(e) => {
            eprintln!("⚠️  Очистка Redis не удалась: {:?}", e);
            // Не выходим, продолжаем работу
        }
    }

    // ===========================
    // ПЕРВИЧНЫЙ ЗАПРОС К БИРЖЕ
    // ===========================

    // 1) Баланс фьючерсного аккаунта (USDT-settle)
    match gate.get_futures_account().await {
        Ok(acc) => {
            global_logger
                .log_monitor(
                    "Gate.io futures account loaded",
                    Some(&acc),
                )
                .unwrap();
            println!("📟 Gate.io futures account: {}", acc);
        }
        Err(e) => {
            global_logger
                .log_system(
                    "ERROR",
                    "gate_account",
                    &format!("Не удалось получить аккаунт Gate.io: {:?}", e),
                )
                .unwrap();
            println!("❌ Не удалось получить аккаунт Gate.io: {:?}", e);
        }
    }

    // 2) Открытые ордера (по всем контрактам)
    match gate.list_open_orders().await {
        Ok(orders) => {
            global_logger
                .log_monitor(
                    "Gate.io open futures orders loaded",
                    Some(&orders),
                )
                .unwrap();
            println!("📟 Gate.io open orders: {}", orders);
        }
        Err(e) => {
            global_logger
                .log_system(
                    "ERROR",
                    "gate_open_orders",
                    &format!("Не удалось получить открытые ордера Gate.io: {:?}", e),
                )
                .unwrap();
            println!("❌ Не удалось получить открытые ордера Gate.io: {:?}", e);
        }
    }

    // ===========================
    // ЗАГРУЖАЕМ РЕАЛЬНЫЙ БАЛАНС С GATE.IO
    // ===========================
    let mut equity = match gate.get_real_balance_usdt().await {
        Ok(balance) => {
            global_logger
                .log_system(
                    "INFO",
                    "balance",
                    &format!("Реальный баланс с Gate.io: {:.6} USDT", balance),
                )
                .unwrap();
            println!("💰 РЕАЛЬНЫЙ баланс с Gate.io: {:.6} USDT", balance);
            balance
        }
        Err(e) => {
            global_logger
                .log_system(
                    "ERROR",
                    "balance",
                    &format!("КРИТИЧЕСКАЯ ОШИБКА: Не удалось получить реальный баланс: {:?}", e),
                )
                .unwrap();
            eprintln!("❌ КРИТИЧЕСКАЯ ОШИБКА: Не удалось получить реальный баланс с Gate.io: {:?}", e);
            eprintln!("💀 Система не может работать без реального баланса!");
            std::process::exit(1);
        }
    };

    global_logger
        .log_monitor(
            "🚀 Запуск SCAN режима для поиска пампов",
            Some(&serde_json::json!({
                "min_volatility_pct": cfg.min_volatility_pct,
                "max_volatility_pct": cfg.max_volatility_pct,
                "scan_pairs_count": cfg.scan_pairs_count
            })),
        )
        .unwrap();

    println!("🚀 Trading AI (NEW LIVE) запущен!");
    println!(
        "🎯 Ищу сверхволатильные пары: {}% - {}%",
        cfg.min_volatility_pct, cfg.max_volatility_pct
    );
    println!(
        "📊 Количество пар для сканирования: {}",
        cfg.scan_pairs_count
    );

    let gate_base = "https://api.gateio.ws";

    // -------------------------------------------------
    // ВНЕШНИЙ ЦИКЛ:
    //   1) монитор ищет пару
    //   2) торгуем ЭТУ пару до закрытия всех сделок
    //   3) записываем баланс
    //   4) начинаем скан снова
    // -------------------------------------------------
    loop {
        // ================================
        // 0) СОЗДАЁМ НОВУЮ СЕССИЮ
        // ================================
        let session_id = format!("sess-{}", Utc::now().timestamp());
        let mut logger = Logger::new("logs", &session_id)
            .expect("Failed to create session logger");

        logger
            .log_system("INFO", "main", &format!("Старт новой торговой сессии: {}", session_id))
            .unwrap();

        println!("🆕 НОВАЯ СЕССИЯ: {}", session_id);
        println!("📁 Папка логов: logs/{}", session_id);

        // ================================
        // 1) MONITOR → выбираем лучшую пару
        // ================================

        let selected_pair = monitor::run_monitor(
            redis.clone(),
            &session_id,
            gate_base,
            cfg.scan_pairs_count as usize,
            cfg.tick_delay_ms,
            3600,
            cfg.min_volatility_pct,
            cfg.max_volatility_pct,
        )
        .await
        .expect("❌ Не удалось найти пары!");

        logger
            .log_monitor(
                "✅ Выбрана пара для трейдинга",
                Some(&serde_json::json!({
                    "selected_pair": selected_pair,
                    "mode": "LIVE"
                })),
            )
            .unwrap();

        println!("🔥 LIVE режим");
        println!("📈 Пара: {}", selected_pair);

        // Создаём состояние позиции
        let mut pos = PositionSnapshot::new(&selected_pair);
        // Контекст LIVE-движка (машина фаз + стратегии)
        let mut live_ctx = LiveStepContext::new(&cfg);

        let session_start_equity = equity;
        let mut had_position = false;

        // ---------------------------------------------
        // Получаем первую мету
        // ---------------------------------------------
        let mut prev_meta = loop {
            match fetch_trading_meta(redis.clone(), &selected_pair).await {
                Ok(m) => break m,
                Err(_) => {
                    println!("Ожидание меты для {}...", selected_pair);
                    sleep(Duration::from_millis(1000)).await;
                }
            }
        };

        // ================================
        // 2) LIVE — основной цикл
        // ================================
        logger
            .log_live(
                "🔥 LIVE режим запущен",
                Some(&serde_json::json!({
                    "pair": selected_pair,
                    "session_equity": equity
                })),
            )
            .unwrap();

        loop {
            if let Ok(meta_now) = fetch_trading_meta(redis.clone(), &selected_pair).await {
                // Обновляем цену в позиции для расчёта PNL
                pos.update_price(meta_now.price);

                // 🔥🔥 НЕ логируем каждый тик в файлы - только значимые события!
                // Каждотиковое состояние пишется только в Redis (write_live_log)

                if let Err(e) = live_step(
                    redis.clone(),
                    &mut live_ctx,
                    &mut pos,
                    &prev_meta,
                    &meta_now,
                    equity,  // ← Передаем актуальный баланс!
                    &mut logger,  // 🔥🔥 Передаём logger для записи actions
                ).await {
                    logger.log_system(
                        "ERROR",
                        "live_step",
                        &format!("Ошибка live_step: {:?}", e),
                    ).unwrap();
                }

                if pos.long_qty > 0.0 || pos.short_qty > 0.0 {
                    had_position = true;
                }

                prev_meta = meta_now;

                // =========================================
                // 2.1. ПРОВЕРКА: Бот НЕ ВШЁЛ В ПОЗИЦИЮ (несоответствие параметров)
                // =========================================
                // Если позиция пустая НО ранее была позиция → bot не вошёл
                // Значит параметры не соответствуют (DistanceToLIQ < 100%)
                // Завершаем сессию → ищем новую пару
                if (pos.long_qty == 0.0 && pos.short_qty == 0.0) && had_position {
                    println!("❌ Бот НЕ ВШЁЛ В ПОЗИЦИЮ (параметры не соответствуют)");
                    println!("❌ Завершаем сессию и ищем новую пару...");
                    break; // Выходим из LIVE → снова SCAN → новая пара
                }

                // ================================
                // 2.2. Контроль TP по сессии (Stop Loss отключен)
                // ================================
                let session_total_pnl = pos.total_pnl();
                let tp_level = SESSION_TP_PCT * session_start_equity;

                if session_total_pnl >= tp_level {
                    // Достигли цели по прибыли → закрываем всё и выходим из LIVE
                    let px = pos.last_price;
                    pos.close_all_at_price(px);

                    logger
                        .log_action(
                            "SESSION_TP_EXIT",
                            session_start_equity + pos.realized_pnl,
                            pos.realized_pnl,
                            &format!(
                                "TP по сессии достигнут ({}% от equity). PnL: {}",
                                SESSION_TP_PCT * 100.0,
                                pos.realized_pnl
                            ),
                        )
                        .unwrap();
                }

                // Условие завершения сессии:
                // были позиции → и теперь все закрыты
                if had_position && pos.long_qty == 0.0 && pos.short_qty == 0.0 {
                    let final_equity = session_start_equity + pos.realized_pnl;

                    // ================================
                    // ОБНОВЛЯЕМ БАЛАНС С GATE.IO
                    // ================================

                    // Логируем завершение сессии
                    logger
                        .log_action(
                            "SESSION_END",
                            final_equity,
                            pos.realized_pnl,
                            &format!("Сессия завершена. PnL: {}", pos.realized_pnl),
                        )
                        .unwrap();

                    // Получаем актуальный баланс с Gate.io
                    equity = match gate.get_real_balance_usdt().await {
                        Ok(real_balance) => {
                            logger.log_system(
                                "INFO",
                                "balance",
                                &format!(
                                    "Сессия завершена. Баланс был: {}, стал: {} (PnL: {}), реальный: {:.6}",
                                    session_start_equity,
                                    final_equity,
                                    pos.realized_pnl,
                                    real_balance
                                ),
                            ).unwrap();
                            println!(
                                "✅ Баланс обновлён: {} → {:.6} (PnL: {})",
                                session_start_equity, real_balance, pos.realized_pnl
                            );
                            real_balance
                        }
                        Err(e) => {
                            logger.log_system(
                                "ERROR",
                                "balance",
                                &format!("Ошибка получения реального баланса: {:?}", e),
                            ).unwrap();
                            println!("❌ Ошибка получения баланса с Gate.io: {:?}", e);
                            final_equity
                        }
                    };

                    println!("✅ СЕССИЯ {} ЗАВЕРШЕНА. Начинаем новую...", session_id);
                    break; // выходим из LIVE → снова SCAN
                }
            }

            sleep(Duration::from_millis(cfg.tick_delay_ms)).await;
        }
    }
}