tesser-cli 0.9.1

Command-line interface for running and operating the Tesser framework
Documentation
use std::fs;
use std::path::{Path, PathBuf};
use std::process::Command;

use anyhow::Result;
use assert_cmd::prelude::*;
use chrono::{TimeZone, Utc};
use parquet::arrow::ArrowWriter;
use parquet::file::properties::WriterProperties;
use rust_decimal::Decimal;
use tempfile::tempdir;

use tesser_core::{Side, Tick};
use tesser_data::encoding::ticks_to_batch;

const STRATEGY_CONFIG: &str = r#"
strategy_name = "SmaCross"

[params]
symbol = "BTCUSDT"
fast_period = 3
slow_period = 5
min_samples = 5
"#;

#[test]
fn backtest_ticks_stream_from_parquet() -> Result<()> {
    let temp = tempdir()?;
    let strategy_path = temp.path().join("strategy.toml");
    fs::write(&strategy_path, STRATEGY_CONFIG)?;

    let flight_dir = temp.path().join("flight");
    write_ticks_dataset(&flight_dir)?;

    let workspace_root = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("..");
    let markets_file = workspace_root.join("config/markets.toml");
    let binary = assert_cmd::cargo::cargo_bin!("tesser-cli");
    let mut cmd = Command::new(binary);
    cmd.current_dir(&workspace_root);
    cmd.args([
        "--env",
        "default",
        "backtest",
        "run",
        "--strategy-config",
        strategy_path.to_str().unwrap(),
        "--mode",
        "tick",
        "--lob-data",
        flight_dir.to_str().unwrap(),
        "--markets-file",
        markets_file.to_str().unwrap(),
        "--quantity",
        "0.01",
    ]);
    cmd.assert().success();
    Ok(())
}

fn write_ticks_dataset(root: &Path) -> Result<()> {
    let day_dir = root.join("ticks/2024-01-01");
    fs::create_dir_all(&day_dir)?;
    let ticks = sample_ticks();
    let batch = ticks_to_batch(&ticks)?;
    write_parquet(day_dir.join("ticks.parquet"), &batch)?;
    Ok(())
}

fn sample_ticks() -> Vec<Tick> {
    let base = Utc.with_ymd_and_hms(2024, 1, 1, 0, 0, 0).unwrap();
    (0..3)
        .map(|idx| Tick {
            symbol: "BTCUSDT".into(),
            price: Decimal::new(20_000 + idx as i64, 0),
            size: Decimal::new(1, 0),
            side: Side::Buy,
            exchange_timestamp: base + chrono::Duration::seconds(idx as i64),
            received_at: base + chrono::Duration::seconds(idx as i64),
        })
        .collect()
}

fn write_parquet(path: PathBuf, batch: &arrow::record_batch::RecordBatch) -> Result<()> {
    let file = std::fs::File::create(path)?;
    let props = WriterProperties::builder().build();
    let mut writer = ArrowWriter::try_new(file, batch.schema(), Some(props))?;
    writer.write(batch)?;
    writer.close()?;
    Ok(())
}