// ===========================
// 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;
}
}
}