← Назад к главному
# 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