Skip to main content

engine/
simulation_export.rs

1use std::fs::File;
2use std::path::Path;
3use std::sync::Arc;
4
5use arrow::array::{Float64Array, Int32Array};
6use arrow::datatypes::{DataType, Field, Schema};
7use arrow::record_batch::RecordBatch;
8use parquet::arrow::ArrowWriter;
9use parquet::file::properties::WriterProperties;
10
11/// Write pre-computed market paths to a Parquet file.
12///
13/// `paths` is a flat list of per-path rows.  Each row is
14/// `(path_id, step, time, price, volatility, hawkes_lambda)` where:
15/// - `volatility`    is `f64::NAN` for GBM paths
16/// - `hawkes_lambda` is `f64::NAN` for non-Hawkes processes; for HawkesGBM it
17///   holds the effective fill-arrival intensity lambda(t) = a + excitation(t).
18///
19/// Schema
20/// ------
21/// path_id      : Int32
22/// step         : Int32
23/// time         : Float64
24/// price        : Float64
25/// volatility   : Float64  (NaN when not applicable)
26/// hawkes_lambda: Float64  (NaN when not applicable)
27///
28/// The parent directory is created automatically.
29pub fn write_market_paths_to_parquet(
30    paths: Vec<(i32, i32, f64, f64, f64, f64)>,
31    output_path: &Path,
32) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
33    let total = paths.len();
34    let mut path_ids = Vec::with_capacity(total);
35    let mut steps = Vec::with_capacity(total);
36    let mut times = Vec::with_capacity(total);
37    let mut prices = Vec::with_capacity(total);
38    let mut volatilities = Vec::with_capacity(total);
39    let mut hawkes_lambdas = Vec::with_capacity(total);
40
41    for (pid, s, t, p, v, lam) in paths {
42        path_ids.push(pid);
43        steps.push(s);
44        times.push(t);
45        prices.push(p);
46        volatilities.push(v);
47        hawkes_lambdas.push(lam);
48    }
49
50    let schema = Arc::new(Schema::new(vec![
51        Field::new("path_id", DataType::Int32, false),
52        Field::new("step", DataType::Int32, false),
53        Field::new("time", DataType::Float64, false),
54        Field::new("price", DataType::Float64, false),
55        Field::new("volatility", DataType::Float64, true),
56        Field::new("hawkes_lambda", DataType::Float64, true),
57    ]));
58
59    let batch = RecordBatch::try_new(
60        schema.clone(),
61        vec![
62            Arc::new(Int32Array::from(path_ids)),
63            Arc::new(Int32Array::from(steps)),
64            Arc::new(Float64Array::from(times)),
65            Arc::new(Float64Array::from(prices)),
66            Arc::new(Float64Array::from(volatilities)),
67            Arc::new(Float64Array::from(hawkes_lambdas)),
68        ],
69    )?;
70
71    if let Some(parent) = output_path.parent()
72        && !parent.as_os_str().is_empty()
73    {
74        std::fs::create_dir_all(parent)?;
75    }
76
77    let file = File::create(output_path)?;
78    let props = WriterProperties::builder().build();
79    let mut writer = ArrowWriter::try_new(file, batch.schema(), Some(props))?;
80    writer.write(&batch)?;
81    writer.close()?;
82
83    Ok(())
84}