← Назад к документации
balance
Исходный код Rust - Trading AI
📄 Rust
📦 Модуль
🔧 Исходный код
// ===========================
// 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(())
}