Skip to main content

schwab_cli/agent/
journal.rs

1use std::fs::OpenOptions;
2use std::io::Write;
3use std::path::Path;
4
5use anyhow::{Context, Result};
6use chrono::{DateTime, Utc};
7use serde_json::Value;
8
9use super::paths::{backtest_journal_path, journal_path, sim_journal_path};
10
11pub fn append_event(rules_path: &Path, simulate: bool, event_type: &str, payload: Value) -> Result<()> {
12    append_event_at(rules_path, simulate, Utc::now(), event_type, payload)
13}
14
15pub fn append_backtest_event_at(
16    rules_path: &Path,
17    at: DateTime<Utc>,
18    event_type: &str,
19    payload: Value,
20) -> Result<()> {
21    write_journal_line(&backtest_journal_path(rules_path), at, event_type, payload)
22}
23
24pub fn append_event_at(
25    rules_path: &Path,
26    simulate: bool,
27    at: DateTime<Utc>,
28    event_type: &str,
29    payload: Value,
30) -> Result<()> {
31    let path = if simulate {
32        sim_journal_path(rules_path)
33    } else {
34        journal_path(rules_path)
35    };
36    write_journal_line(&path, at, event_type, payload)
37}
38
39fn write_journal_line(
40    path: &Path,
41    at: DateTime<Utc>,
42    event_type: &str,
43    payload: Value,
44) -> Result<()> {
45    if let Some(parent) = path.parent() {
46        std::fs::create_dir_all(parent)?;
47    }
48    let line = serde_json::json!({
49        "ts": at.to_rfc3339(),
50        "type": event_type,
51        "payload": payload,
52    });
53    let mut file = OpenOptions::new()
54        .create(true)
55        .append(true)
56        .open(path)
57        .with_context(|| format!("open journal {}", path.display()))?;
58    writeln!(file, "{line}")?;
59    Ok(())
60}
61
62pub fn read_all_backtest(rules_path: &Path) -> Result<Vec<Value>> {
63    let path = backtest_journal_path(rules_path);
64    if !path.exists() {
65        return Ok(vec![]);
66    }
67    let raw = std::fs::read_to_string(&path)
68        .with_context(|| format!("read backtest journal {}", path.display()))?;
69    let mut out = Vec::new();
70    for line in raw.lines() {
71        let line = line.trim();
72        if line.is_empty() {
73            continue;
74        }
75        out.push(serde_json::from_str(line)?);
76    }
77    Ok(out)
78}
79
80pub fn clear_backtest_journal(rules_path: &Path) -> Result<()> {
81    let path = backtest_journal_path(rules_path);
82    if path.exists() {
83        std::fs::remove_file(&path)
84            .with_context(|| format!("clear backtest journal {}", path.display()))?;
85    }
86    Ok(())
87}