// ===========================
// File: src/balance.rs
// ===========================
//
// Глобальный учёт виртуального депозита между сессиями.
//
// Файл: /home/iam/ai/logs/balance.jsonl
//
// Формат JSONL:
//
// {"ts":"2025-12-04T10:00:00Z","equity":105.0}
//
// ЛОГИКА:
// - ЕСЛИ файла НЕТ → возвращаем default_equity (из config)
// - ЕСЛИ файл есть → читаем ПОСЛЕДНЮЮ валидную строку
// - если последние строки битые → ищем предыдущие
// ===========================
use std::fs::{File, OpenOptions};
use std::io::{BufRead, BufReader, Write};
use std::path::Path;
use anyhow::{Context, Result};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
const BALANCE_FILE: &str = "/home/iam/ai/logs/balance.jsonl";
#[derive(Debug, Serialize, Deserialize)]
struct BalanceRecord {
ts: String,
equity: f64,
}
/// ==========================================
/// Загружаем стартовый баланс.
/// ==========================================
///
/// ВАЖНО:
/// - если ФАЙЛА НЕТ → default_equity (cfg.start_equity)
/// - если файл есть → ищем последнюю ВАЛИДНУЮ строку
/// - если всё битое → default_equity
///
pub fn load_initial_equity(default_equity: f64) -> f64 {
let path = Path::new(BALANCE_FILE);
// ФАЙЛА НЕТ → возвращаем из config
if !path.exists() {
return default_equity;
}
let file = match File::open(path) {
Ok(f) => f,
Err(_) => return default_equity,
};
let reader = BufReader::new(file);
let mut valid_record: Option<f64> = None;
// ЧИТАЕМ ВСЕ строки, но сохраняем ТОЛЬКО последнюю валидную
for line in reader.lines().flatten() {
let trimmed = line.trim();
if trimmed.is_empty() {
continue;
}
if let Ok(rec) = serde_json::from_str::<BalanceRecord>(trimmed) {
valid_record = Some(rec.equity);
}
}
// Если нашли валидную строку → используем её
if let Some(eq) = valid_record {
return eq;
}
// Иначе → default
default_equity
}
/// ==========================================
/// Записываем итоговый баланс после сессии.
/// ==========================================
pub fn write_final_equity(equity: f64) -> Result<()> {
let path = Path::new(BALANCE_FILE);
// Гарантируем существование директории
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent)
.with_context(|| format!("Не удалось создать директорию: {:?}", parent))?;
}
let ts: DateTime<Utc> = Utc::now();
let record = BalanceRecord {
ts: ts.to_rfc3339(),
equity,
};
let line = serde_json::to_string(&record)
.context("Не удалось сериализовать баланс в JSON")?;
let mut file = OpenOptions::new()
.create(true)
.append(true)
.open(path)
.context("Не удалось открыть balance.jsonl для дозаписи")?;
writeln!(file, "{}", line)
.context("Не удалось записать строку баланса")?;
Ok(())
}