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