Skip to main content

market_model/
types.rs

1/// Configuration for a batch simulation run.
2#[derive(Debug, Clone)]
3pub struct SimulationConfig {
4    /// Number of independent paths to simulate.
5    pub n_paths: usize,
6    /// Number of time steps per path.
7    pub n_steps: usize,
8    /// Size of each time step in years (e.g. 1/252 for daily).
9    pub dt: f64,
10    /// Seed for reproducible simulation.
11    pub seed: u64,
12}
13
14/// Result of a batch simulation.
15///
16/// Outputs are stored in structure-of-arrays layout.
17/// Each path has `n_steps + 1` entries: the initial state plus `n_steps` steps.
18/// Access via: `outputs[path_idx * (n_steps + 1) + entry]`
19///
20/// Prefer the accessor methods (`get`, `path`, `path_steps`, `terminal`,
21/// `all_outputs`) over computing SOA offsets manually.
22#[derive(Debug, Clone)]
23pub struct SimulationResult<T: Clone> {
24    /// All path outputs, SOA layout.
25    ///
26    /// The flat slice is indexed as `path_idx * (n_steps + 1) + entry`,
27    /// where entry 0 is the initial state and entry `n_steps` is the final.
28    /// For path-agnostic iteration over every output, use `all_outputs()`.
29    pub outputs: Vec<T>,
30    /// Number of paths.
31    pub n_paths: usize,
32    /// Number of steps per path. Total entries per path = n_steps + 1.
33    pub n_steps: usize,
34}
35
36impl<T: Clone> SimulationResult<T> {
37    /// Number of entries per path (initial + n_steps).
38    fn entries_per_path(&self) -> usize {
39        self.n_steps + 1
40    }
41
42    /// Get the output at a specific `(path_idx, entry)` where entry 0 is
43    /// the initial state and entry = n_steps is the final state.
44    pub fn get(&self, path_idx: usize, entry: usize) -> Option<&T> {
45        if path_idx < self.n_paths && entry <= self.n_steps {
46            Some(&self.outputs[path_idx * self.entries_per_path() + entry])
47        } else {
48            None
49        }
50    }
51
52    /// Get all outputs for a specific path (includes initial state).
53    pub fn path(&self, path_idx: usize) -> Option<&[T]> {
54        if path_idx < self.n_paths {
55            let epp = self.entries_per_path();
56            let start = path_idx * epp;
57            Some(&self.outputs[start..start + epp])
58        } else {
59            None
60        }
61    }
62
63    /// Get only the step outputs for a specific path (excludes initial state).
64    pub fn path_steps(&self, path_idx: usize) -> Option<&[T]> {
65        if path_idx < self.n_paths {
66            let epp = self.entries_per_path();
67            let start = path_idx * epp + 1;
68            Some(&self.outputs[start..start + self.n_steps])
69        } else {
70            None
71        }
72    }
73
74    /// Get the final output for a specific path.
75    pub fn terminal(&self, path_idx: usize) -> Option<&T> {
76        self.get(path_idx, self.n_steps)
77    }
78
79    /// Flat access to all outputs for iteration without path-awareness.
80    ///
81    /// Use this when you need to iterate over every output (e.g. checking
82    /// positivity, collecting statistics). Equivalent to reading
83    /// `self.outputs` but safe against accidental offset computation.
84    pub fn all_outputs(&self) -> &[T] {
85        &self.outputs
86    }
87}
88
89/// A trading signal from a price-based strategy.
90#[derive(Debug, Clone, Copy, PartialEq, Eq)]
91pub enum Signal {
92    Long,
93    Short,
94    Flat,
95}