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}