← Назад к главному
# main.rs - Точка входа в программу ## 📋 Назначение модуля `main()` - точка входа в trading AI систему. Оркестрирует весь цикл работы: 1. Инициализация системы 2. Поиск пары через MONITOR 3. Торговля выбранной парой через LIVE 4. Обновление баланса 5. Повтор цикла с новой парой **Основная логика:** - Внешний бесконечный цикл ищет памп-пары и торгует ими - Каждая пара торгуется в отдельной сессии - Сессия завершается когда все позиции закрыты - Баланс обновляется после каждой сессии с Gate.io - TP по сессии = 8% от equity (Stop Loss отключён) --- ## 🏗️ Структура модуля ### Константа `SESSION_TP_PCT` **Строка:** 29 ```rust const SESSION_TP_PCT: f64 = 0.08; ``` **Назначение:** Цель по прибыли за сессию (8% от session_start_equity) **Пример:** ``` session_start_equity = 100.0 USDT tp_level = 0.08 × 100.0 = 8.0 USDT Если session_total_pnl >= 8.0 USDT → закрываем всё ``` --- ### Функция `main()` **Строки:** 33-403 **Сигнатура:** ```rust async fn main() ``` **Возвращает:** - Ничего (бесконечный цикл) **Алгоритм (3 фазы):** --- ## 🔄 Основные этапы ### Фаза 1: Инициализация системы **Строки:** 34-188 #### 1.1 Инициализация логгера и конфигурации **Строки:** 35-37 ```rust env_logger::init(); let cfg = Config::load(); ``` **Что делает:** - Инициализирует env_logger для логирования - Загружает конфигурацию из файла/переменных окружения --- #### 1.2 Создание Gate.io клиента **Строки:** 39-40 ```rust let gate = GateClient::new(); ``` **Что делает:** - Создаёт клиент для Gate.io API - API ключи загружаются внутри --- #### 1.3 Создание глобального логгера **Строки:** 42-51 ```rust 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(); ``` **Что делает:** - Создаёт глобальный логгер для системных событий - Записывает сообщение о старте системы --- #### 1.4 Подключение к Redis **Строки:** 53-70 ```rust 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 - Проверяет что сервер работает через PING - Если ошибка → выводит сообщение установки Redis и завершает программу **Пример успешного подключения:** ``` ✅ Redis PING/PONG - сервер работает! ``` **Пример ошибки:** ``` ❌ Connection refused ❌ Установи Redis: sudo apt install redis-server && sudo systemctl start redis-server ``` --- #### 1.5 Очистка Redis **Строки:** 72-88 ```rust // ОЧИЩАЕМ 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); // Не выходим, продолжаем работу } } ``` **Что делает:** - Удаляет все ключи бота (с префиксом `hb:*`) - Логирует количество удалённых ключей - При ошибке продолжает работу (критическая ошибка) **Пример:** ``` 🧹 Redis очищен: удалено 42 старых ключей ``` --- #### 1.6 Первичный запрос к бирже **Строки:** 93-138 **1.6.1 Баланс фьючерсного аккаунта** ```rust 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); } } ``` **Что делает:** - Получает информацию о фьючерсном аккаунте - Логирует результат --- **1.6.2 Открытые ордера** ```rust 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); } } ``` **Что делает:** - Получает список открытых ордеров по всем контрактам - Логирует результат --- #### 1.7 Загрузка реального баланса **Строки:** 141-167 ```rust 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); } }; ``` **Что делает:** - Получает реальный баланс USDT с Gate.io - Логирует результат - При ошибке → критическая ошибка, завершает программу **Пример:** ``` 💰 РЕАЛЬНЫЙ баланс с Gate.io: 100.123456 USDT ``` **Пример ошибки:** ``` ❌ КРИТИЧЕСКАЯ ОШИБКА: Не удалось получить реальный баланс: ... 💀 Система не может работать без реального баланса! ``` --- #### 1.8 Логирование параметров сканирования **Строки:** 169-188 ```rust 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 ); ``` **Что делает:** - Логирует параметры сканирования - Выводит приветственное сообщение **Пример:** ``` 🚀 Trading AI (NEW LIVE) запущен! 🎯 Ищу сверхволатильные пары: 10% - 100% 📊 Количество пар для сканирования: 20 ``` --- ### Фаза 2: Внешний цикл (SCAN + LIVE) **Строки:** 199-402 **Цикл:** ``` loop { // 1) Создаём новую сессию // 2) MONITOR → выбираем лучшую пару // 3) LIVE → торгуем выбранную пару // 4) Обновляем баланс // 5) Повторяем } ``` --- #### 2.1 Создание новой сессии **Строки:** 203-212 ```rust 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); ``` **Что делает:** - Создаёт уникальный ID сессии на основе timestamp - Создаёт отдельный логгер для этой сессии - Логирует старт сессии **Пример session_id:** ``` sess-1771747635 ``` --- #### 2.2 Выбор пары через MONITOR **Строки:** 215-242 ```rust let selected_pair = monitor::run_monitor( redis.clone(), &session_id, gate_base, cfg.scan_pairs_count as usize, cfg.tick_delay_ms, 3600, // max_scan_time_sec 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); ``` **Что делает:** - Запускает MONITOR режим для поиска пары - Ожидает пока MONITOR не выберет лучшую пару - Логирует выбранную пару **Параметры MONITOR:** - `redis` - клиент Redis - `session_id` - ID текущей сессии - `gate_base` - базовый URL Gate.io API - `scan_pairs_count` - количество пар для сканирования - `tick_delay_ms` - задержка между тиками - `max_scan_time_sec` - максимальное время сканирования (3600 сек = 1 час) - `min_volatility_pct` - минимальная волатильность для поиска - `max_volatility_pct` - максимальная волатильность для поиска **Пример:** ``` 🔥 LIVE режим 📈 Пара: MEMES_USDT ``` --- #### 2.3 Создание состояния позиции и контекста **Строки:** 244-247 ```rust // Создаём состояние позиции 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; ``` **Что делает:** - Создаёт `PositionSnapshot` для отслеживания позиции - Создаёт `LiveStepContext` для LIVE движка - Запоминает equity на старте сессии - Инициализирует флаг `had_position = false` --- #### 2.4 Получение первой меты **Строки:** 254-263 ```rust // Получаем первую мету 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; } } }; ``` **Что делает:** - Ожидает пока `fetch_trading_meta` не вернёт валидные данные - Если ошибка → ждёт 1 секунду и повторяет - Выходит из цикла когда мета получена **Логика:** ``` loop { if fetch_trading_meta Ok → break else → wait 1s and retry } ``` --- ### Фаза 3: LIVE цикл **Строки:** 268-401 #### 3.1 Логирование запуска LIVE **Строки:** 268-276 ```rust logger .log_live( "🔥 LIVE режим запущен", Some(&serde_json::json!({ "pair": selected_pair, "session_equity": equity })), ) .unwrap(); ``` **Что делает:** - Логирует запуск LIVE режима - Записывает пару и equity сессии --- #### 3.2 Основной цикл LIVE **Строки:** 278-401 ```rust 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; // ... (проверки ниже) } sleep(Duration::from_millis(cfg.tick_delay_ms)).await; } ``` **Что делает:** - Получает `TradingMeta` из Redis - Обновляет цену в позиции - Вызывает `live_step()` для обработки логики - Отслеживает была ли позиция (`had_position`) - Сохраняет предыдущую мету для risk_check - Ждёт `tick_delay_ms` между итерациями **Важные моменты:** - НЕ логирует каждый тик в файлы (только значимые события) - Каждотиковое состояние пишется в Redis через `write_live_log` - Передаёт актуальный equity в `live_step` - Передаёт logger для записи actions --- #### 3.3 Проверка: Бот не вшёл в позицию **Строки:** 308-318 ```rust // ПРОВЕРКА: Бот НЕ ВШЁЛ В ПОЗИЦИЮ (несоответствие параметров) // Если позиция пустая НО ранее была позиция → bot не вошёл // Значит параметры не соответствуют (DistanceToLIQ < 100%) // Завершаем сессию → ищем новую пару if (pos.long_qty == 0.0 && pos.short_qty == 0.0) && had_position { println!("❌ Бот НЕ ВШЁЛ В ПОЗИЦИЮ (параметры не соответствуют)"); println!("❌ Завершаем сессию и ищем новую пару..."); break; // Выходим из LIVE → снова SCAN → новая пара } ``` **Логика:** - Если позиция пустая (`long_qty == 0 && short_qty == 0`) - И ранее была позиция (`had_position == true`) - → Бот не вшёл в позицию (параметры не соответствуют, DistanceToLIQ < 100%) - → Завершаем сессию, ищем новую пару **Пример:** ``` ❌ Бот НЕ ВШЁЛ В ПОЗИЦИЮ (параметры не соответствуют) ❌ Завершаем сессию и ищем новую пару... ``` --- #### 3.4 Контроль TP по сессии **Строки:** 320-343 ```rust // Контроль 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(); } ``` **Логика:** - Рассчитывает `session_total_pnl = realized + unrealized` - Рассчитывает `tp_level = 8% × session_start_equity` - Если `session_total_pnl >= tp_level`: - Закрывает все позиции по текущей цене - Логирует выход по TP **Пример:** ``` session_start_equity = 100.0 USDT SESSION_TP_PCT = 0.08 (8%) tp_level = 100.0 × 0.08 = 8.0 USDT session_total_pnl = 8.5 USDT 8.5 >= 8.0 ✅ → Закрываем всё → Логируем: "TP по сессии достигнут (8% от equity). PnL: 8.5" ``` --- #### 3.5 Условие завершения сессии **Строки:** 345-397 ```rust // Условие завершения сессии: // были позиции → и теперь все закрыты 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 } ``` **Логика:** - Если были позиции (`had_position == true`) - И теперь все закрыты (`long_qty == 0 && short_qty == 0`) - → Завершаем сессию **Действия при завершении:** 1. Рассчитывает `final_equity = session_start_equity + realized_pnl` 2. Логирует завершение сессии 3. Получает реальный баланс с Gate.io 4. Логирует обновление баланса 5. Выводит сообщение о завершении 6. Выходит из LIVE цикла → снова SCAN **Пример успешного завершения:** ``` ✅ Баланс обновлён: 100.0 → 108.123456 (PnL: 8.123456) ✅ СЕССИЯ sess-1771747635 ЗАВЕРШЕНА. Начинаем новую... ``` **Пример ошибки получения баланса:** ``` ❌ Ошибка получения баланса с Gate.io: ... (используем final_equity вместо real_balance) ``` --- ## 📊 Параметры конфигурации | Параметр | Описание | Пример | |----------|----------|--------| | `redis_url` | URL подключения к Redis | `redis://127.0.0.1:6379` | | `min_volatility_pct` | Минимальная волатильность для поиска | 10.0 | | `max_volatility_pct` | Максимальная волатильность для поиска | 100.0 | | `scan_pairs_count` | Количество пар для сканирования | 20 | | `tick_delay_ms` | Задержка между тиками (мс) | 200 | --- ## ⚠️ Критические моменты ### 1. Stop Loss отключён **Строка:** 31 ```rust // Stop Loss полностью исключен из системы ``` **Что это значит:** - Нет автоматического выхода по убытку - Только TP по сессии (8%) - Бот может торговать сколько угодно долго --- ### 2. Redis обязателен **Строки:** 53-70 ```rust match redis.ping().await { Err(e) => { eprintln!("❌ {}", e); eprintln!("❌ Установи Redis: sudo apt install redis-server && sudo systemctl start redis-server"); std::process::exit(1); } } ``` **Что это значит:** - Если Redis недоступен → программа завершается - Выводит инструкции по установке Redis --- ### 3. Реальный баланс обязателен **Строки:** 143-167 ```rust let mut equity = match gate.get_real_balance_usdt().await { Err(e) => { eprintln!("❌ КРИТИЧЕСКАЯ ОШИБКА: Не удалось получить реальный баланс: {:?}", e); eprintln!("💀 Система не может работать без реального баланса!"); std::process::exit(1); } }; ``` **Что это значит:** - Если не удалось получить баланс → критическая ошибка - Программа завершается --- ### 4. Не вошёл в позицию → новая пара **Строки:** 308-318 ```rust if (pos.long_qty == 0.0 && pos.short_qty == 0.0) && had_position { println!("❌ Бот НЕ ВШЁЛ В ПОЗИЦИЮ (параметры не соответствуют)"); break; // Выходим из LIVE → снова SCAN → новая пара } ``` **Почему это происходит:** - Параметры не соответствуют (DistanceToLIQ < 100%) - Бот открывает ордер, но сразу закрывает его - Нельзя торговать эту пару → ищем новую --- ### 5. TP по сессии = 8% **Строки:** 29, 324-343 ```rust const SESSION_TP_PCT: f64 = 0.08; let tp_level = SESSION_TP_PCT * session_start_equity; ``` **Что это значит:** - Цель по прибыли = 8% от equity на старте сессии - При достижении → закрываем все позиции - Stop Loss отключён --- ## 📚 Связанные файлы | Файл | Связь | |------|-------| | `src/config.rs` | Загрузка конфигурации | | `src/redis.rs` | Redis клиент | | `src/logger.rs` | Логирование | | `src/monitor.rs` | MONITOR режим (поиск пары) | | `src/gate.rs` | Gate.io API клиент | | `src/live/meta.rs` | fetch_trading_meta() | | `src/live/state.rs` | PositionSnapshot | | `src/live/step.rs` | live_step() и LiveStepContext | | `src/balance.rs` | get_real_balance_usdt() | --- ## 🎯 Резюме **Что делает main.rs:** 1. ✅ Инициализирует систему (логгер, Redis, Gate.io клиент) 2. ✅ Проверяет что Redis работает (PING) 3. ✅ Очищает Redis от старых данных (hb:*) 4. ✅ Загружает реальный баланс с Gate.io 5. ✅ Запускает бесконечный цикл SCAN + LIVE: - Создаёт новую сессию - MONITOR → выбирает лучшую пару - LIVE → торгует выбранную пару - Обновляет баланс - Повторяет **Внешний цикл:** ``` loop { // 1) Создаём новую сессию // 2) MONITOR → выбираем пару // 3) LIVE → торгуем // 4) Обновляем баланс // 5) Повторяем } ``` **LIVE цикл:** ``` loop { // 1) Получаем TradingMeta из Redis // 2) Обновляем цену в позиции // 3) Вызываем live_step() // 4) Проверяем: не вошёл в позицию? // 5) Проверяем: TP достигнут? // 6) Проверяем: все позиции закрыты? // 7) Ждём tick_delay_ms } ``` **Критические моменты:** - 🔥 Stop Loss отключён - 🔥 Redis обязателен - 🔥 Реальный баланс обязателен - 🔥 Не вошёл в позицию → новая пара - 🔥 TP по сессии = 8% **Константы:** - `SESSION_TP_PCT = 0.08` (8% от equity) --- **Дата создания:** 2026-02-22 **Автор:** Claude Code Assistant **Версия:** 1.0