Skip to content

Commit

Permalink
feat(builder): engine builder for buildign the sim loop
Browse files Browse the repository at this point in the history
  • Loading branch information
Alexangelj committed Oct 10, 2023
1 parent 1da1c57 commit a57c317
Show file tree
Hide file tree
Showing 4 changed files with 323 additions and 0 deletions.
118 changes: 118 additions & 0 deletions box-simulation/src/engine/builder.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
//! Builds the core simulation logic into the engine so it's run in the main loop.
use arbiter_core::environment::Environment;

use crate::engine::PriceChanger;
use crate::settings::{self, params::SimulationConfig};
use crate::setup::{self, deploy::Contracts};

/// A specific type for the async action function that uses contracts and environment
/// to run the core simulation logic.
/// Implements the Sized trait so it can be used as a type.
pub type Action = Box<dyn Fn(&Contracts) -> Result<(), anyhow::Error> + Send + Sync>;

/// A builder for creating the simulation loop by specifying the actions to run inside the main sim loop.
/// before_hooks - Async functions that that are run inside the sim loop before the core logic.
/// after_hooks - Async functions that that are run inside the sim loop after the core logic.
pub struct EngineBuilder {
pub before_hooks: Vec<Action>,
pub after_hooks: Vec<Action>,
}

/// Initializes the simulation engine with two vectors of actions to run before and after the core simulation logic.
/// Calling the `run` method on the engine will run the before hooks, then the core simulation logic, then the after hooks within the sim loop.
impl EngineBuilder {
pub fn new() -> Self {
Self {
before_hooks: Vec::new(),
after_hooks: Vec::new(),
}
}

/// Adds a before hook to the engine.
pub fn before(mut self, hook: Action) -> Self {
self.before_hooks.push(hook);
self
}

pub async fn run(
&self,
contracts: &Contracts,
env: &Environment,
config: &SimulationConfig,
) -> Result<(), anyhow::Error> {
// Run the main simulation loop
let mut price_changer = PriceChanger::new(
contracts.exchanges.lex.clone(),
config.price_process_parameters.clone(),
);

for _ in 0..(price_changer.trajectory.paths[0].len() - 1) {
// Run the before hooks
for hook in self.before_hooks.iter() {
hook(contracts)?;
}

// Run the core simulation logic, which is updating the price.
price_changer.update_price().await?;

// Run the after hooks
for hook in self.after_hooks.iter() {
hook(contracts)?;
}
}

Ok(())
}
}

#[cfg(test)]
mod tests {
use super::*;
use arbiter_core::{environment::builder::EnvironmentBuilder, math::wad_to_float};

async fn setup_test_environment(
) -> Result<(Contracts, Environment, SimulationConfig), anyhow::Error> {
let config = settings::params::SimulationConfig::new()?;

let env = EnvironmentBuilder::new().build();
let contracts = setup::deploy::deploy_contracts(&env, &config).await?;

Ok((contracts, env, config))
}

#[tokio::test]
async fn test_engine_builder_vanilla() -> Result<(), anyhow::Error> {
let (contracts, env, config) = setup_test_environment().await?;

let engine_builder = EngineBuilder::new().run(&contracts, &env, &config).await?;

let initial_price_float = config.price_process_parameters.initial_price;
let lex_price_float = wad_to_float(contracts.exchanges.lex.price().call().await?);

// Assert the price of the liquid exchange != initial config price to ensure the sim's core logic was run.
assert_ne!(
initial_price_float, lex_price_float,
"The price of the liquid exchange should have been updated by the simulation's core logic."
);

println!("initial_price_float: {}", initial_price_float);
println!("lex_price_float: {}", lex_price_float);

Ok(())
}

#[tokio::test]
async fn test_engine_builder_add_before_hook() -> Result<(), anyhow::Error> {
let (contracts, env, config) = setup_test_environment().await?;

let engine_builder = EngineBuilder::new()
.before(Box::new(|contracts| {
println!("before hook");
Ok(())
}))
.run(&contracts, &env, &config)
.await?;

Ok(())
}
}
74 changes: 74 additions & 0 deletions box-simulation/src/engine/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
use crate::settings::{self, params::SimulationConfig};
use crate::setup::{self, deploy::Contracts};

use self::price_path::PriceChanger;
use arbiter_core::environment::Environment;

pub mod builder;
pub mod price_path;

/// Runs the main simulation loop.
pub async fn run(
contracts: &Contracts,
env: &Environment,
config: &SimulationConfig,
) -> Result<PriceChanger, anyhow::Error> {
// Get the initial price path from the configuration.
let mut price_changer = price_path::PriceChanger::new(
contracts.exchanges.lex.clone(),
config.price_process_parameters.clone(),
);

for _ in 0..(price_changer.trajectory.paths[0].len() - 1) {
price_changer.update_price().await?;
}

Ok(price_changer)
}

#[cfg(test)]
mod tests {
use super::*;
use arbiter_core::{environment::builder::EnvironmentBuilder, math::wad_to_float};

async fn setup_test_environment(
) -> Result<(Contracts, Environment, SimulationConfig), anyhow::Error> {
let config = settings::params::SimulationConfig::new()?;

let env = EnvironmentBuilder::new().build();
let contracts = setup::deploy::deploy_contracts(&env, &config).await?;

Ok((contracts, env, config))
}

#[tokio::test]
async fn test_price_path_update_in_loop() -> Result<(), anyhow::Error> {
let (contracts, env, config) = setup_test_environment().await?;

let mut price_changer = run(&contracts, &env, &config).await?;

let price_index = price_changer.index;

let new_price_lex = contracts.exchanges.lex.price().call().await?;

// Assert the price of the lex is the same as the last price in the trajectory
assert_eq!(
price_changer.trajectory.paths[0][price_index - 1],
wad_to_float(new_price_lex)
);

// Assert we reached the end of the trajectory
assert_eq!(price_changer.index, price_changer.trajectory.paths[0].len());

// Print the last price in the trajectory
println!(
"last price in trajectory: {}",
price_changer.trajectory.paths[0][price_index - 1]
);

// Print price of lex
println!("price of lex: {}", new_price_lex);

Ok(())
}
}
130 changes: 130 additions & 0 deletions box-simulation/src/engine/price_path.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
//! ALl utilities for building and using a price path.
use arbiter_core::{
math::{ornstein_uhlenbeck::OrnsteinUhlenbeck, StochasticProcess, Trajectories},
middleware::RevmMiddleware,
};
use tracing::info;

use crate::settings::params::PriceProcessParameters;
use arbiter_core::bindings::liquid_exchange::LiquidExchange;

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// PriceChanger
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

/// The `PriceChanger` holds the data and has methods that allow it to update
/// the price of the `LiquidExchange`.
pub struct PriceChanger {
/// The path the price process takes.
pub trajectory: Trajectories,

/// The `LiquidExchange` contract with the admin `Client`.
pub liquid_exchange: LiquidExchange<RevmMiddleware>,

/// The index of the current price in the trajectory.
pub index: usize,
}

impl PriceChanger {
/// Create a new `PriceChanger` with the given `LiquidExchange` contract
/// bound to the admin `Client`. The `PriceChanger` will use the
/// `OrnsteinUhlenbeck` process to generate a price trajectory with the
/// constants defined in `config.rs`.
/// Ornstein-Uhlenbeck processes are useful for modeling the price of stable
/// tokens.
pub fn new(
liquid_exchange: LiquidExchange<RevmMiddleware>,
price_process_params: PriceProcessParameters,
) -> Self {
let PriceProcessParameters {
initial_price,
mean,
std_dev,
theta,
t_0,
t_n,
num_steps,
seed,
} = price_process_params;
let process = OrnsteinUhlenbeck::new(mean, std_dev, theta);

let trajectory = match seed {
Some(seed) => {
process.seedable_euler_maruyama(initial_price, t_0, t_n, num_steps, 1, false, seed)
}
None => process.euler_maruyama(initial_price, t_0, t_n, num_steps, 1, false),
};

Self {
trajectory,
liquid_exchange,
index: 1, /* start after the initial price since it is already set on contract
* deployment */
}
}

/// Update the price of the `LiquidExchange` contract to the next price in
/// the trajectory and increment the index.
/// !!IMPORTANT!! The update price function will have a real integer value that is smaller than the float value.
pub async fn update_price(&mut self) -> Result<(), anyhow::Error> {
let price = self.trajectory.paths[0][self.index];
info!("Updating price of liquid_exchange to: {}", price);
self.liquid_exchange
.set_price(arbiter_core::math::float_to_wad(price))
.send()
.await?
.await?;
self.index += 1;
Ok(())
}
}

#[cfg(test)]
mod tests {
use super::*;
use crate::settings::{self, params::SimulationConfig};
use crate::setup::{self, deploy::Contracts};
use arbiter_core::environment::Environment;
use arbiter_core::{environment::builder::EnvironmentBuilder, math::wad_to_float};

async fn setup_test_environment(
) -> Result<(Contracts, Environment, SimulationConfig), anyhow::Error> {
let config = settings::params::SimulationConfig::new()?;

let env = EnvironmentBuilder::new().build();
let contracts = setup::deploy::deploy_contracts(&env, &config).await?;

Ok((contracts, env, config))
}

#[tokio::test]
async fn test_price_path_update() -> Result<(), anyhow::Error> {
let (contracts, env, config) = setup_test_environment().await?;

let mut price_changer = PriceChanger::new(
contracts.exchanges.lex.clone(),
config.price_process_parameters.clone(),
);

let initial_price = price_changer.trajectory.paths[0][0];
let initial_index = price_changer.index;

let _ = price_changer.update_price().await?;

let new_price = price_changer.trajectory.paths[0][1];
let new_index = price_changer.index;
let new_price_lex = contracts.exchanges.lex.price().call().await?;

assert_ne!(initial_price, new_price);
assert_eq!(initial_index + 1, new_index);
assert_eq!(new_price, wad_to_float(new_price_lex));

// print these prices in the assertions
println!("initial_price: {}", initial_price);
println!("new_price: {}", new_price);
println!("new_price_lex: {}", new_price_lex);

Ok(())
}
}
1 change: 1 addition & 0 deletions box-simulation/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use tracing_subscriber;
use bindings::{atomic_arbitrage::AtomicArbitrage, g3m::G3M};

mod agents;
mod engine;
mod settings;
mod setup;
mod utils;
Expand Down

0 comments on commit a57c317

Please sign in to comment.