diff --git a/analysis/prices/output.json b/analysis/prices/output.json new file mode 100644 index 0000000..e69de29 diff --git a/src/agents/block_admin.rs b/src/agents/block_admin.rs index 58146a9..5c56fd1 100644 --- a/src/agents/block_admin.rs +++ b/src/agents/block_admin.rs @@ -30,11 +30,11 @@ impl BlockAdmin { /// /// # Arguments /// * [`Environment`] - The environment containing blockchain node information. - /// * [`SimulationConfig`] - The simulation configuration providing block timestep size. + /// * [`SimulationConfig`] - The simulation configuration providing block timestep size. /// /// # Returns /// * [`Result`] - A result containing the new BlockAdmin or an error. - pub async fn new(environment: &Environment, config: &SimulationConfig) -> Result { + pub async fn new(environment: &Environment, config: &SimulationConfig) -> Result { let client = RevmMiddleware::new(environment, "block_admin".into())?; let timestep_size = config.block.timestep_size; let block_number = client.get_block_number().await?.as_u64(); diff --git a/src/agents/mod.rs b/src/agents/mod.rs index e648eca..909cd24 100644 --- a/src/agents/mod.rs +++ b/src/agents/mod.rs @@ -7,8 +7,6 @@ pub mod token_admin; use std::marker::{Send, Sync}; -use crate::settings::parameters::Fixed; - /// Universal agent methods for interacting with the simulation environment or /// loop. /// Agents are expected to be both [`Send`] and [`Sync`]. diff --git a/src/agents/price_changer.rs b/src/agents/price_changer.rs index c870e5f..06b1d35 100644 --- a/src/agents/price_changer.rs +++ b/src/agents/price_changer.rs @@ -1,5 +1,5 @@ use crate::agents::*; -use crate::settings::{parameters::GBMParameters, SimulationConfig}; +use crate::settings::{GBMParameters, SimulationConfig}; use arbiter_core::bindings::liquid_exchange::LiquidExchange; use arbiter_core::environment::Environment; use arbiter_core::math::{float_to_wad, GeometricBrownianMotion, StochasticProcess, Trajectories}; @@ -29,7 +29,7 @@ impl PriceChanger { pub async fn new( environment: &Environment, token_admin: &token_admin::TokenAdmin, - config: &SimulationConfig, + config: &SimulationConfig, ) -> Result { let client = RevmMiddleware::new(environment, "price_changer".into())?; let liquid_exchange = LiquidExchange::deploy( @@ -37,7 +37,7 @@ impl PriceChanger { ( token_admin.arbx.address(), token_admin.arby.address(), - float_to_wad(config.trajectory.initial_price.0), + float_to_wad(config.trajectory.initial_price), ), )? .send() @@ -54,11 +54,11 @@ impl PriceChanger { let trajectory_params = &config.trajectory; let trajectory = match trajectory_params.process.as_str() { "gbm" => { - let GBMParameters { drift, volatility } = config.gbm.unwrap(); - GeometricBrownianMotion::new(drift.0, volatility.0).seedable_euler_maruyama( - trajectory_params.initial_price.0, - trajectory_params.t_0.0, - trajectory_params.t_n.0, + let GBMParameters { drift, volatility } = config.gbm; + GeometricBrownianMotion::new(drift, volatility).seedable_euler_maruyama( + trajectory_params.initial_price, + trajectory_params.t_0, + trajectory_params.t_n, trajectory_params.num_steps, 1, false, diff --git a/src/config/counter.toml b/src/config/counter.toml index 28b5157..0647caa 100644 --- a/src/config/counter.toml +++ b/src/config/counter.toml @@ -1,5 +1,6 @@ simulation = "Counter" output_directory = "analysis/counter" +output_file_name = "output" [trajectory] # The type of price process to use. @@ -11,28 +12,17 @@ seed = 10 # The number of distinct paths to use num_paths = 10 # The initial price of the asset. -[trajectory.initial_price] -fixed = 1.0 +initial_price = 1.0 # The start time of the process. -[trajectory.t_0] -fixed = 0.0 +t_0 = 0.0 # The end time of the process. -[trajectory.t_n] -fixed = 10.0 +t_n = 10.0 [gbm] # The drift of the process. -[gbm.drift] -fixed = 0.0 -# start = -1.0 -# end = 1.0 -# steps = 11 +drift = 0.0 # The volatility of the process. -[gbm.volatility] -# start = 0.1 -# end = 1.0 -# steps = 10 -fixed = 0.5 +volatility = 0.5 [block] timestep_size = 15 diff --git a/src/config/gbm.toml b/src/config/gbm.toml index 67d7b7c..b104276 100644 --- a/src/config/gbm.toml +++ b/src/config/gbm.toml @@ -1,5 +1,6 @@ simulation = "SimulatedPricePath" output_directory = "analysis/prices" +output_file_name = "output" [trajectory] # The type of price process to use. @@ -11,28 +12,17 @@ seed = 10 # The number of distinct paths to use num_paths = 10 # The initial price of the asset. -[trajectory.initial_price] -fixed = 1.0 +initial_price = 1.0 # The start time of the process. -[trajectory.t_0] -fixed = 0.0 +t_0 = 0.0 # The end time of the process. -[trajectory.t_n] -fixed = 10.0 +t_n = 10.0 [gbm] # The drift of the process. -[gbm.drift] -fixed = 0.0 -# start = -1.0 -# end = 1.0 -# steps = 11 +drift = 0.0 # The volatility of the process. -[gbm.volatility] -# start = 0.1 -# end = 1.0 -# steps = 10 -fixed = 0.5 +volatility = 0.5 [block] timestep_size = 15 diff --git a/src/main.rs b/src/main.rs index 404f906..3906197 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,7 @@ -use std::time::Instant; +use std::{ + fs::{self, DirEntry}, + time::Instant, +}; use anyhow::Result; use clap::{ArgAction, CommandFactory, Parser, Subcommand}; @@ -29,7 +32,7 @@ struct Args { enum Commands { /// Represents the `Bind` subcommand. Simulate { - #[clap(index = 1, default_value = "src/config/gbm.toml")] + #[clap(index = 1, default_value = "src/config/")] config_path: String, }, } @@ -47,7 +50,7 @@ enum Commands { /// $ cargo run simulate [path_to_config] /// ``` /// -/// By default, if no configuration path is provided, it will read from "src/config/gbm.toml". +/// By default, if no configuration path is provided, it will read from "src/config/". /// /// These simulations are performed in Arbiter's in memory revm instance and with the exposed RevmMiddleware. fn main() -> Result<()> { @@ -58,7 +61,9 @@ fn main() -> Result<()> { println!("Reading from config path: {}", config_path); let start = Instant::now(); // This is the entry point for the simulation - simulations::batch(config_path)?; + let files = read_toml_files(config_path)?; + println!("files: {:?}", files); + simulations::batch(files)?; let duration = start.elapsed(); println!("Total duration of simulations: {:?}", duration); } @@ -66,3 +71,18 @@ fn main() -> Result<()> { } Ok(()) } + +// Function to read .toml files from a directory and return their paths +fn read_toml_files(dir: &str) -> Result, std::io::Error> { + let paths = fs::read_dir(dir)? + .filter_map(Result::ok) + .filter(is_toml_file) + .map(|entry| entry.path().to_string_lossy().into_owned()) + .collect(); + Ok(paths) +} + +// Helper function to check if a DirEntry is a .toml file +fn is_toml_file(entry: &DirEntry) -> bool { + entry.path().extension().map_or(false, |ext| ext == "toml") +} diff --git a/src/settings/mod.rs b/src/settings/mod.rs index 85d3cd7..33987d6 100644 --- a/src/settings/mod.rs +++ b/src/settings/mod.rs @@ -1,7 +1,3 @@ -pub mod parameters; - -use parameters::*; - use crate::simulations::SimulationType; use config::{Config, ConfigError}; use serde::{Deserialize, Serialize}; @@ -11,82 +7,81 @@ use serde::{Deserialize, Serialize}; /// This struct holds all the necessary parameters and configurations needed to run a simulation. /// It encompasses several sub-configurations such as `TrajectoryParameters` and `GBMParameters`. #[derive(Clone, Debug, Serialize, Deserialize)] -pub struct SimulationConfig> { +pub struct SimulationConfig { /// The type of simulation to run, defined by an enum `SimulationType`. pub simulation: SimulationType, - /// Maximum number of parallel simulations to run. - pub max_parallel: Option, - /// Directory where the simulation output will be stored. pub output_directory: String, /// Name of the file where the simulation results will be written. - pub output_file_name: Option, + pub output_file_name: String, /// Parameters specific to the trajectory of the simulation. - pub trajectory: TrajectoryParameters

, + pub trajectory: TrajectoryParameters, /// Parameters specific to the Geometric Brownian Motion (GBM) if applicable. - pub gbm: Option>, + pub gbm: GBMParameters, /// Parameters related to block configurations. pub block: BlockParameters, } -impl SimulationConfig { +impl SimulationConfig { /// Creates a new `SimulationConfig` instance from a configuration file. /// /// Reads the specified configuration file and deserializes it into a `SimulationConfig` object. /// The `config_path` is the path to the configuration file in question. - pub fn new(config_path: &str) -> Result { + pub fn new(config_path: String) -> Result { let s = Config::builder() - .add_source(config::File::with_name(config_path)) + .add_source(config::File::with_name(&config_path)) .build()?; s.try_deserialize() } } -impl Parameterized> for SimulationConfig { - /// Generates a list of `SimulationConfig` instances with fixed parameters. - /// - /// This method is responsible for taking the meta parameters defined in the configuration, - /// generating the actual fixed parameters, and creating a list of complete `SimulationConfig` instances. - fn generate(&self) -> Vec> { - let mut result = vec![]; - let trajectories = self.trajectory.generate(); - - let gbms = self - .gbm - .as_ref() - .map(|gbm| gbm.generate()) - .unwrap_or_default(); - - if gbms.is_empty() { - panic!("You must supply either a gbm configuration."); - } - - for trajectory in &trajectories { - for gbm in &gbms { - let output_directory = self.output_directory.clone() - + "/gbm_drift=" - + &gbm.drift.0.to_string() - + "_vol=" - + &gbm.volatility.0.to_string(); - let output_file_name = - format!("trajectory={}", trajectory.output_tag.clone().unwrap()); - result.push(SimulationConfig { - simulation: self.simulation, - max_parallel: None, - output_directory, - output_file_name: Some(output_file_name), - trajectory: trajectory.clone(), - gbm: Some(*gbm), - block: self.block, - }); - } - } - - result - } +#[derive(Copy, Clone, Debug, Serialize, Deserialize)] +pub struct BlockParameters { + pub timestep_size: u64, +} + +/// Defines parameters for a trajectory in the simulation. +/// +/// Contains information like initial price, start and end times, +/// and number of steps and paths in the simulation. +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct TrajectoryParameters { + /// The name. + pub process: String, + + /// The initial price of the asset. + pub initial_price: f64, + + /// The start time of the process. + pub t_0: f64, + + /// The end time of the process. + pub t_n: f64, + + /// The number of steps in the process. + pub num_steps: usize, + + /// The number of paths in the process. + pub num_paths: usize, + + /// The seed for the process. + pub seed: u64, + + /// The tag for the output file. + pub output_tag: Option, +} + +/// Contains the parameters for the Geometric Brownian Motion (GBM) process. +#[derive(Copy, Clone, Debug, Serialize, Deserialize)] +pub struct GBMParameters { + // The drift of the process. + pub drift: f64, + + // The volatility of the process. + pub volatility: f64, } diff --git a/src/settings/parameters.rs b/src/settings/parameters.rs deleted file mode 100644 index d5d3e41..0000000 --- a/src/settings/parameters.rs +++ /dev/null @@ -1,196 +0,0 @@ -use std::{collections::hash_map::DefaultHasher, hash::Hasher}; - -use super::*; - -/// A trait defining objects that can generate a set of parameters. -/// -/// This trait is implemented by various parameter structs that provide a method -/// to produce a vector of parameters based on their internal state. -pub trait Parameterized { - fn generate(&self) -> Vec; -} - -/// Represents a fixed parameter value. -/// -/// This struct holds a fixed value of type `f64` that can be generated -/// directly without any modification. -#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq)] -pub struct Fixed(pub f64); -impl Parameterized for Fixed { - fn generate(&self) -> Vec { - vec![self.0] - } -} - -/// Represents meta parameter configuration. -/// -/// This struct wraps around the `LinspaceParameters` to facilitate parameter generation -/// in a certain defined space. -#[derive(Copy, Clone, Debug, Serialize, Deserialize)] -pub struct Meta(LinspaceParameters); -impl Parameterized for Meta { - fn generate(&self) -> Vec { - self.0.generate() - } -} - -/// Contains the parameters for generating a linear space of values. -/// -/// This struct can be configured to generate a sequence of evenly spaced values -/// between a start and end point, or a single fixed value. -#[derive(Copy, Clone, Debug, Serialize, Deserialize)] -pub struct LinspaceParameters { - pub start: Option, - pub end: Option, - pub steps: Option, - pub fixed: Option, -} - -impl LinspaceParameters { - fn generate(&self) -> Vec { - // Check if start, end, steps are all Some - match (self.start, self.end, self.steps) { - (Some(start), Some(end), Some(steps)) => { - if self.fixed.is_some() { - panic!("Both linspace and fixed parameters are set"); - } - let step_size = (end - start) / (steps as f64 - 1.0); - (0..steps).map(|i| start + step_size * i as f64).collect() - } - // If only fixed is Some, return a vec with that fixed value - (_, _, _) if self.fixed.is_some() => vec![self.fixed.unwrap()], - // Otherwise, configuration is invalid - _ => panic!("Invalid configuration for LinspaceParameters. Please provide a `start`, `end`, and `steps` or alternatively just provide a `fixed` value."), - } - } -} - -#[derive(Copy, Clone, Debug, Serialize, Deserialize)] -pub struct BlockParameters { - pub timestep_size: u64, -} - -/// Defines parameters for a trajectory in the simulation. -/// -/// Contains information like initial price, start and end times, -/// and number of steps and paths in the simulation. -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct TrajectoryParameters> { - /// The name. - pub process: String, - - /// The initial price of the asset. - pub initial_price: P, - - /// The start time of the process. - pub t_0: P, - - /// The end time of the process. - pub t_n: P, - - /// The number of steps in the process. - pub num_steps: usize, - - /// The number of paths in the process. - pub num_paths: usize, - - /// The seed for the process. - pub seed: u64, - - /// The tag for the output file. - pub output_tag: Option, -} - -impl Parameterized> for TrajectoryParameters { - fn generate(&self) -> Vec> { - let initial_price = self.initial_price.generate(); - let t_0 = self.t_0.generate(); - let t_n = self.t_n.generate(); - let mut result = vec![]; - let mut hasher = DefaultHasher::new(); - let mut seed = self.seed; - for p in initial_price { - for t0 in t_0.clone() { - for tn in t_n.clone() { - for index in 0..self.num_paths { - result.push(TrajectoryParameters { - process: self.process.clone(), - initial_price: Fixed(p), - t_0: Fixed(t0), - t_n: Fixed(tn), - num_steps: self.num_steps, - num_paths: 1, - seed, - output_tag: Some(index.to_string()), - }); - hasher.write_u64(seed); - seed = hasher.finish(); - } - } - } - } - result - } -} - -/// Contains the parameters for the Geometric Brownian Motion (GBM) process. -#[derive(Copy, Clone, Debug, Serialize, Deserialize)] -pub struct GBMParameters> { - // The drift of the process. - pub drift: P, - - // The volatility of the process. - pub volatility: P, -} - -impl Parameterized> for GBMParameters { - fn generate(&self) -> Vec> { - let drift = self.drift.generate(); - let volatility = self.volatility.generate(); - let mut result = vec![]; - for d in drift { - for v in volatility.clone() { - result.push(GBMParameters { - drift: Fixed(d), - volatility: Fixed(v), - }); - } - } - result - } -} - -/// Contains the parameters for the Ornstein–Uhlenbeck (OU) process. -#[derive(Copy, Clone, Debug, Serialize, Deserialize)] -pub struct OUParameters> { - /// The mean (price) of the process. - pub mean: P, - - /// The standard deviation of the process. - pub std_dev: P, - - /// The theta parameter of the process. - /// This describes how strongly the process will revert to the mean. - pub theta: P, -} - -impl Parameterized> for OUParameters { - fn generate(&self) -> Vec> { - let mean = self.mean.generate(); - let std_dev = self.std_dev.generate(); - let theta = self.theta.generate(); - let mut result = vec![]; - for m in mean { - for s in std_dev.clone() { - for t in theta.clone() { - result.push(OUParameters { - mean: Fixed(m), - std_dev: Fixed(s), - theta: Fixed(t), - }); - } - } - } - result - } -} diff --git a/src/simulations/counter.rs b/src/simulations/counter.rs index 3e11422..3885ace 100644 --- a/src/simulations/counter.rs +++ b/src/simulations/counter.rs @@ -16,12 +16,12 @@ use crate::{ /// /// # Arguments /// -/// * `config` - The configuration for the simulation based on `SimulationConfig`. +/// * `config` - The configuration for the simulation based on `SimulationConfig`. /// /// # Returns /// /// * A `Result` containing the fully initialized `Simulation` or an error if any step fails. -pub async fn setup(config: SimulationConfig) -> Result { +pub async fn setup(config: SimulationConfig) -> Result { let environment = EnvironmentBuilder::new() .block_settings(BlockSettings::UserControlled) .build(); @@ -31,7 +31,7 @@ pub async fn setup(config: SimulationConfig) -> Result { EventLogger::builder() .directory(config.output_directory) - .file_name(config.output_file_name.unwrap()) + .file_name(config.output_file_name) .add(counter_agent.counter.events(), "counter") .run()?; diff --git a/src/simulations/mod.rs b/src/simulations/mod.rs index 34281dd..ce4ff95 100644 --- a/src/simulations/mod.rs +++ b/src/simulations/mod.rs @@ -8,14 +8,13 @@ use std::sync::Arc; use tokio::sync::Semaphore; use super::*; -use crate::{agents::Agents, settings::parameters::Fixed}; +use crate::agents::Agents; pub mod counter; pub mod price_path_simulation; use crate::settings::SimulationConfig; use anyhow::Result; -use settings::parameters::Parameterized; use tokio::runtime::Builder; /// Represents the main Simulation structure. @@ -44,7 +43,7 @@ impl SimulationType { /// /// This function matches on the `SimulationType` to determine which simulation setup to use, /// then executes the chosen simulation. - async fn run(config: SimulationConfig) -> Result<()> { + async fn run(config: SimulationConfig) -> Result<()> { let simulation = match config.simulation { SimulationType::SimulatedPricePath => { price_path_simulation::setup(config.clone()).await? @@ -68,48 +67,29 @@ impl SimulationType { /// /// This function sets up multiple simulations to run in parallel, manages available resources using a semaphore, /// and handles any errors that arise during execution. -pub fn batch(config_path: &str) -> Result<()> { - // - let config = SimulationConfig::new(config_path)?; - - let direct_configs: Vec> = config.generate(); - +pub fn batch(config_paths: Vec) -> Result<()> { // Create a multi-threaded runtime let rt = Builder::new_multi_thread().build()?; - // Create a semaphore with a given number of permits - let semaphore = config - .max_parallel - .map(|max_parallel| Arc::new(Semaphore::new(max_parallel))); + let mut configs = vec![]; + for path in config_paths { + configs.push(SimulationConfig::new(path)?); + } rt.block_on(async { let mut handles = vec![]; let errors = Arc::new(tokio::sync::Mutex::new(vec![])); - for config in direct_configs { + for config in configs { let errors_clone = errors.clone(); - let semaphore_clone = semaphore.clone(); handles.push(tokio::spawn(async move { - // Acquire a permit inside the spawned task - let permit = if let Some(ref semaphore_clone) = semaphore_clone { - // Acquire a permit outside the spawned task - let permit = semaphore_clone.acquire().await.unwrap(); - Some(permit) - } else { - None - }; - let result = SimulationType::run(config).await; match result { Err(e) => { let mut errors_clone_lock = errors_clone.lock().await; errors_clone_lock.push(e); - // Drop the permit when the simulation is done. - drop(permit); - } - Result::Ok(_) => { - drop(permit); } + Result::Ok(_) => {} } })); } diff --git a/src/simulations/price_path_simulation.rs b/src/simulations/price_path_simulation.rs index 56f51d0..c9747e4 100644 --- a/src/simulations/price_path_simulation.rs +++ b/src/simulations/price_path_simulation.rs @@ -19,12 +19,12 @@ use crate::{ /// /// # Arguments /// -/// * `config` - The configuration for the simulation based on `SimulationConfig`. +/// * `config` - The configuration for the simulation based on `SimulationConfig`. /// /// # Returns /// /// * A `Result` containing the fully initialized `Simulation` or an error if any step of the setup fails. -pub async fn setup(config: SimulationConfig) -> Result { +pub async fn setup(config: SimulationConfig) -> Result { let environment = EnvironmentBuilder::new() .block_settings(BlockSettings::UserControlled) .build(); @@ -35,7 +35,7 @@ pub async fn setup(config: SimulationConfig) -> Result { EventLogger::builder() .directory(config.output_directory) - .file_name(config.output_file_name.unwrap()) + .file_name(config.output_file_name) .add(price_changer.liquid_exchange.events(), "lex") .run()?;