← Назад к главному
# balance.rs - Глобальный учёт баланса между сессиями ## 📋 Назначение модуля Учёт виртуального депозита между сессиями через файл `balance.jsonl`. **Основная логика:** 1. **Загрузка начального баланса** - читает `balance.jsonl`, возвращает последнюю валидную запись 2. **Запись финального баланса** - добавляет запись в `balance.jsonl` после сессии 3. **Обработка битых строк** - если последние строки битые, ищет предыдущие валидные **Файл:** `/home/iam/ai/logs/balance.jsonl` **Используется в:** - В будущем для учёта баланса между сессиями (в текущей версии не используется) --- ## 🏗️ Структура модуля ### Константа `BALANCE_FILE` **Строка:** 27 ```rust const BALANCE_FILE: &str = "/home/iam/ai/logs/balance.jsonl"; ``` --- ### Структура `BalanceRecord` **Строки:** 29-33 ```rust #[derive(Debug, Serialize, Deserialize)] struct BalanceRecord { ts: String, equity: f64, } ``` **Поля:** | Поле | Тип | Описание | Пример | |------|-----|----------|--------| | `ts` | String | Timestamp в формате RFC3339 | "2025-12-04T10:00:00Z" | | `equity` | f64 | Equity (баланс) | 105.0 | --- ## 🔄 Основные функции ### Функция `load_initial_equity()` **Строки:** 44-79 **Сигнатура:** ```rust pub fn load_initial_equity(default_equity: f64) -> f64 ``` **Параметры:** - `default_equity` - баланс по умолчанию (если файла нет) **Возвращает:** - `f64` - начальный баланс **Алгоритм:** --- #### Этап 1: Проверка существования файла **Строки:** 47-50 ```rust let path = Path::new(BALANCE_FILE); // ФАЙЛА НЕТ → возвращаем из config if !path.exists() { return default_equity; } ``` **Что делает:** - Если файла нет → возвращает `default_equity` --- #### Этап 2: Открытие файла **Строки:** 52-55 ```rust let file = match File::open(path) { Ok(f) => f, Err(_) => return default_equity, }; ``` **Что делает:** - Пытается открыть файл - При ошибке → возвращает `default_equity` --- #### Этап 3: Чтение всех строк **Строки:** 57-70 ```rust let reader = BufReader::new(file); let mut valid_record: Option = None; // ЧИТАЕМ ВСЕ строки, но сохраняем ТОЛЬКО последнюю валидную for line in reader.lines().flatten() { let trimmed = line.trim(); if trimmed.is_empty() { continue; } if let Ok(rec) = serde_json::from_str::(trimmed) { valid_record = Some(rec.equity); } } ``` **Логика:** - Читает все строки по одной - Пропускает пустые строки - Парсит каждую строку в `BalanceRecord` - Если парсинг успешен → сохраняет equity (перезаписывает предыдущее) --- #### Этап 4: Возврат последней валидной записи **Строки:** 72-78 ```rust // Если нашли валидную строку → используем её if let Some(eq) = valid_record { return eq; } // Иначе → default default_equity ``` **Что делает:** - Если нашли валидную запись → возвращает её equity - Иначе → возвращает `default_equity` --- ### Функция `write_final_equity()` **Строки:** 84-112 **Сигнатура:** ```rust pub fn write_final_equity(equity: f64) -> Result<()> ``` **Параметры:** - `equity` - финальный баланс после сессии **Возвращает:** - `Ok(())` - запись успешна - `Err` - ошибка записи **Алгоритм:** --- #### Этап 1: Создание директории **Строки:** 87-91 ```rust let path = Path::new(BALANCE_FILE); // Гарантируем существование директории if let Some(parent) = path.parent() { std::fs::create_dir_all(parent) .with_context(|| format!("Не удалось создать директорию: {:?}", parent))?; } ``` **Что делает:** - Получает родительскую директорию файла - Создаёт директорию если её нет --- #### Этап 2: Формирование записи **Строки:** 93-97 ```rust let ts: DateTime = Utc::now(); let record = BalanceRecord { ts: ts.to_rfc3339(), equity, }; ``` **Что делает:** - Создаёт текущий timestamp - Формирует `BalanceRecord` --- #### Этап 3: Сериализация в JSON **Строки:** 99-100 ```rust let line = serde_json::to_string(&record) .context("Не удалось сериализовать баланс в JSON")?; ``` --- #### Этап 4: Открываем файл для дозаписи **Строки:** 102-106 ```rust let mut file = OpenOptions::new() .create(true) .append(true) .open(path) .context("Не удалось открыть balance.jsonl для дозаписи")?; ``` **Что делает:** - `create(true)` - создаёт файл если его нет - `append(true)` - открывает в режиме дозаписи --- #### Этап 5: Запись строки **Строки:** 108-109 ```rust writeln!(file, "{}", line) .context("Не удалось записать строку баланса")?; ``` --- ## 📊 Пример файла balance.jsonl ``` {"ts":"2025-12-04T10:00:00Z","equity":100.0} {"ts":"2025-12-04T11:30:00Z","equity":108.5} {"ts":"2025-12-04T13:45:00Z","equity":112.3} ``` --- ## ⚠️ Критические моменты ### 1. Валидные строки перезаписываются **Строки:** 67-69 ```rust if let Ok(rec) = serde_json::from_str::(trimmed) { valid_record = Some(rec.equity); } ``` **Что это значит:** - Если несколько валидных строк → возвращает последнюю - Битые строки игнорируются --- ### 2. Пустые строки пропускаются **Строки:** 62-65 ```rust let trimmed = line.trim(); if trimmed.is_empty() { continue; } ``` --- ### 3. Файл открывается в режиме дозаписи **Строки:** 102-106 ```rust let mut file = OpenOptions::new() .create(true) .append(true) .open(path) ``` **Что это значит:** - Новые записи добавляются в конец файла - Старые записи не удаляются --- ## 📚 Связанные файлы | Файл | Связь | |------|-------| | Нет | В текущей версии не используется | --- ## 🎯 Резюме **Что делает balance.rs:** 1. ✅ Загружает начальный баланс из `balance.jsonl` 2. ✅ Записывает финальный баланс после сессии 3. ✅ Обрабатывает битые строки (игнорирует, ищет предыдущие валидные) **Функции:** - `load_initial_equity(default_equity)` - загрузить начальный баланс - `write_final_equity(equity)` - записать финальный баланс **Файл:** `/home/iam/ai/logs/balance.jsonl` **Формат JSONL:** ``` {"ts":"2025-12-04T10:00:00Z","equity":100.0} ``` **Критические моменты:** - 🔥 Валидные строки перезаписываются (возвращает последнюю) - 🔥 Пустые строки пропускаются - 🔥 Файл открывается в режиме дозаписи --- **Дата создания:** 2026-02-22 **Автор:** Claude Code Assistant **Версия:** 1.0