From a52caacd5afd1a4ee6815d1148347fe7dd42126c Mon Sep 17 00:00:00 2001 From: zombie-einstein <13398815+zombie-einstein@users.noreply.github.com> Date: Sat, 9 Mar 2024 15:41:46 +0000 Subject: [PATCH] Doc fixes (#16) * Update step-sim example and tweak macro * Add docstrings * Fix rust docstrings * Fix Python docstrings --- crates/macros/src/lib.rs | 6 +- crates/order_book/src/orderbook.rs | 4 + crates/order_book/src/types.rs | 15 ++++ .../step_sim/examples/random_agents/main.rs | 2 +- crates/step_sim/src/agents/mod.rs | 57 -------------- crates/step_sim/src/agents/momentum_agent.rs | 9 ++- crates/step_sim/src/agents/noise_agent.rs | 8 ++ crates/step_sim/src/agents/random_agent.rs | 13 +++- crates/step_sim/src/env.rs | 2 + crates/step_sim/src/lib.rs | 75 ++++++++++--------- crates/step_sim/tests/test_macros.rs | 55 ++++++++++++++ docs/source/pages/example.rst | 7 +- docs/source/pages/usage.rst | 15 +++- 13 files changed, 161 insertions(+), 107 deletions(-) create mode 100644 crates/step_sim/tests/test_macros.rs diff --git a/crates/macros/src/lib.rs b/crates/macros/src/lib.rs index 94799df..1b28c7f 100644 --- a/crates/macros/src/lib.rs +++ b/crates/macros/src/lib.rs @@ -1,6 +1,8 @@ use proc_macro::TokenStream; use quote::quote; +extern crate self as bourse_de; + /// Agent iteration macro /// /// Implements the `AgentSet` trait for a struct @@ -65,8 +67,8 @@ fn impl_agents_macro(ast: &syn::DeriveInput) -> TokenStream { } let output = quote! { - impl AgentSet for #name { - fn update(&mut self, env: &mut Env, rng: &mut R) { + impl bourse_de::agents::AgentSet for #name { + fn update(&mut self, env: &mut bourse_de::Env, rng: &mut R) { #call_tokens } } diff --git a/crates/order_book/src/orderbook.rs b/crates/order_book/src/orderbook.rs index ea9ec19..74ddd00 100644 --- a/crates/order_book/src/orderbook.rs +++ b/crates/order_book/src/orderbook.rs @@ -86,8 +86,12 @@ pub struct OrderBook { trading: bool, } +/// Order rejection errors +/// +/// Errors raised when error creation fails. #[derive(Debug, Clone)] pub enum OrderError { + /// Price not a multiple of market tick-size PriceError { price: Price, tick_size: Price }, } diff --git a/crates/order_book/src/types.rs b/crates/order_book/src/types.rs index 903ec4b..a2a500c 100644 --- a/crates/order_book/src/types.rs +++ b/crates/order_book/src/types.rs @@ -216,21 +216,36 @@ pub enum Event { /// Level 1 market data pub struct Level1Data { + /// Bid touch price pub bid_price: Price, + /// Ask touch price pub ask_price: Price, + /// Bid total volume pub bid_vol: Vol, + /// Ask total volume pub ask_vol: Vol, + /// Bid touch volume pub bid_touch_vol: Vol, + /// Ask touch volume pub ask_touch_vol: Vol, + /// Number of bid orders at touch pub bid_touch_orders: OrderCount, + /// Number of ask orders at touch pub ask_touch_orders: OrderCount, } +/// Level 2 market data pub struct Level2Data { + /// Bid touch price pub bid_price: Price, + /// Ask touch price pub ask_price: Price, + /// Bid total volume pub bid_vol: Vol, + /// Ask total volume pub ask_vol: Vol, + /// Volume and number of bid orders at price-levels pub bid_price_levels: [(Vol, OrderCount); N], + /// Volume and number of ask orders at price-levels pub ask_price_levels: [(Vol, OrderCount); N], } diff --git a/crates/step_sim/examples/random_agents/main.rs b/crates/step_sim/examples/random_agents/main.rs index ffbe49b..cb0c161 100644 --- a/crates/step_sim/examples/random_agents/main.rs +++ b/crates/step_sim/examples/random_agents/main.rs @@ -1,4 +1,4 @@ -use bourse_de::agents::{Agent, AgentSet, RandomAgents}; +use bourse_de::agents::{Agent, RandomAgents}; use bourse_de::{sim_runner, Env}; use bourse_macros::Agents; diff --git a/crates/step_sim/src/agents/mod.rs b/crates/step_sim/src/agents/mod.rs index c6e015e..e483989 100644 --- a/crates/step_sim/src/agents/mod.rs +++ b/crates/step_sim/src/agents/mod.rs @@ -130,60 +130,3 @@ pub trait AgentSet { /// fn update(&mut self, env: &mut Env, rng: &mut R); } - -#[cfg(test)] -mod tests { - use super::*; - use bourse_book::types::{Price, Side}; - use rand_xoshiro::rand_core::SeedableRng; - use rand_xoshiro::Xoroshiro128StarStar; - - struct TestAgent { - side: Side, - price: Price, - } - - impl TestAgent { - pub fn new(side: Side, price: Price) -> Self { - Self { side, price } - } - } - - impl Agent for TestAgent { - fn update(&mut self, env: &mut Env, _rng: &mut R) { - env.place_order(self.side, 10, 101, Some(self.price)) - .unwrap(); - } - } - - #[test] - fn test_agent_macro() { - #[derive(Agents)] - struct TestAgents { - pub a: TestAgent, - pub b: TestAgent, - } - - let mut env = Env::new(0, 1, 1000, true); - let mut rng = Xoroshiro128StarStar::seed_from_u64(101); - - let mut test_agents = TestAgents { - a: TestAgent::new(Side::Bid, 20), - b: TestAgent::new(Side::Ask, 40), - }; - - test_agents.update(&mut env, &mut rng); - env.step(&mut rng); - - assert!(env.get_orderbook().ask_vol() == 10); - assert!(env.get_orderbook().bid_vol() == 10); - assert!(env.get_orderbook().bid_ask() == (20, 40)); - - test_agents.update(&mut env, &mut rng); - env.step(&mut rng); - - assert!(env.get_orderbook().ask_vol() == 20); - assert!(env.get_orderbook().bid_vol() == 20); - assert!(env.get_orderbook().bid_ask() == (20, 40)); - } -} diff --git a/crates/step_sim/src/agents/momentum_agent.rs b/crates/step_sim/src/agents/momentum_agent.rs index b732307..4e4788c 100644 --- a/crates/step_sim/src/agents/momentum_agent.rs +++ b/crates/step_sim/src/agents/momentum_agent.rs @@ -30,10 +30,11 @@ pub struct MomentumParams { pub price_dist_sigma: f64, } -/// Agent that places trades conditioned on price history +/// Agents that place trades conditioned on price history /// -/// A group of agents that track trends in price movements -/// the momentum of the price is updated each step +/// A group of agents that track trends in price movements. +/// +/// The momentum of the price, `M`, is updated each step /// /// ```notrust /// M = m * (1 - decay) + decay * (P - p) @@ -54,7 +55,7 @@ pub struct MomentumParams { /// p_limit = p_market * order_ratio /// ``` /// -// Agents will then place a buy/sell order if `M` is +/// Agents will then place a buy/sell order if `M` is /// greater/less than 0.0 respectively. /// /// Each step the agent(s) diff --git a/crates/step_sim/src/agents/noise_agent.rs b/crates/step_sim/src/agents/noise_agent.rs index f4b4f8b..b922ba6 100644 --- a/crates/step_sim/src/agents/noise_agent.rs +++ b/crates/step_sim/src/agents/noise_agent.rs @@ -7,13 +7,21 @@ use rand::Rng; use rand::RngCore; use rand_distr::LogNormal; +/// Noise agent parameters pub struct NoiseAgentParams { + /// Tick-size of the market pub tick_size: Price, + /// Probability of placing a limit order pub p_limit: f32, + /// Probability of placing a market order pub p_market: f32, + /// Probability of cancelling a live order pub p_cancel: f32, + /// Size of trades that are placed pub trade_vol: Vol, + /// Log-normal price distribution mean pub price_dist_mu: f64, + /// Log-normal price distribution width pub price_dist_sigma: f64, } diff --git a/crates/step_sim/src/agents/random_agent.rs b/crates/step_sim/src/agents/random_agent.rs index 4143078..da71182 100644 --- a/crates/step_sim/src/agents/random_agent.rs +++ b/crates/step_sim/src/agents/random_agent.rs @@ -29,9 +29,8 @@ use rand::RngCore; /// # Examples /// /// ``` -/// use bourse_de::agents::{Agent, AgentSet, RandomAgents}; +/// use bourse_de::agents::{Agent, RandomAgents, Agents}; /// use bourse_de::{sim_runner, Env}; -/// use bourse_macros::Agents; /// /// #[derive(Agents)] /// struct SimAgents { @@ -55,6 +54,16 @@ pub struct RandomAgents { } impl RandomAgents { + /// Initialise a set of random agents + /// + /// # Arguments + /// + /// - `n_agents` - Number of agents in the set + /// - `tick_range` - Range of ticks to place orders over + /// - `vol_range` - Order volume range to sample from + /// - `tick_size` - Market tick size + /// - `activity_rate` - Agent activity rate + /// pub fn new( n_agents: usize, tick_range: (Price, Price), diff --git a/crates/step_sim/src/env.rs b/crates/step_sim/src/env.rs index c0bdc36..5cdb1b0 100644 --- a/crates/step_sim/src/env.rs +++ b/crates/step_sim/src/env.rs @@ -106,6 +106,7 @@ pub struct Env { } impl Env { + /// Number of price levels recorded during simulation pub const N_LEVELS: usize = N; /// Initialise an empty environment @@ -113,6 +114,7 @@ impl Env { /// # Arguments /// /// - `start_time` - Simulation start time + /// - `tick_size` - Market tick size /// - `step_size` - Simulated step time-length /// - `trading` - Flag if `true` orders will be matched, /// otherwise no trades will take place diff --git a/crates/step_sim/src/lib.rs b/crates/step_sim/src/lib.rs index 197e725..0b47f8d 100644 --- a/crates/step_sim/src/lib.rs +++ b/crates/step_sim/src/lib.rs @@ -29,62 +29,69 @@ //! updating, and have no guarantee of the ordering //! of transactions. //! +//! See [bourse_book] for details of the limit +//! order-book used in this environment. +//! //! # Examples //! //! ``` //! use bourse_de::types::{Price, Side, Vol}; -//! use bourse_de::agents::AgentSet; +//! use bourse_de::agents; +//! use bourse_de::agents::Agent; //! use bourse_de::{sim_runner, Env}; //! use rand::{RngCore, Rng}; //! -//! struct Agents { -//! pub offset: Price, -//! pub vol: Vol, -//! pub n_agents: usize, +//! // Define a set of agents using built +//! // in definitions +//! #[derive(agents::Agents)] +//! struct SimAgents { +//! pub a: agents::MomentumAgent, +//! pub b: agents::NoiseAgent, //! } //! -//! impl AgentSet for Agents { -//! // Agents place an order on a random side -//! // a fixed distance above/below the mid -//! fn update( -//! &mut self, env: &mut Env, rng: &mut R -//! ) { -//! let bid = env.level_2_data().bid_price; -//! let ask = env.level_2_data().ask_price; -//! let mid = (ask - bid) / 2; -//! let mid_price = bid + mid; -//! for _ in (0..self.n_agents) { -//! let side = rng.gen_bool(0.5); -//! match side { -//! true => { -//! let p = mid_price - self.offset; -//! env.place_order(Side::Ask, self.vol, 101, Some(p)); -//! } -//! false => { -//! let p = mid_price + self.offset; -//! env.place_order(Side::Bid, self.vol, 101, Some(p)); -//! } -//! } -//! } -//! } -//! } +//! // Initialise agent parameters +//! let m_params = agents::MomentumParams { +//! tick_size: 2, +//! p_cancel: 0.1, +//! trade_vol: 100, +//! decay: 1.0, +//! demand: 5.0, +//! scale: 0.5, +//! order_ratio: 1.0, +//! price_dist_mu: 0.0, +//! price_dist_sigma: 10.0, +//! }; +//! +//! let n_params = agents::NoiseAgentParams{ +//! tick_size: 2, +//! p_limit: 0.2, +//! p_market: 0.2, +//! p_cancel: 0.1, +//! trade_vol: 100, +//! price_dist_mu: 0.0, +//! price_dist_sigma: 1.0, +//! }; +//! +//! let mut agents = SimAgents { +//! a: agents::MomentumAgent::new(0, 10, m_params), +//! b: agents::NoiseAgent::new(10, 20, n_params), +//! }; //! //! // Initialise the environment and agents //! let mut env = Env::new(0, 1, 1_000_000, true); -//! let mut agents = Agents{offset: 6, vol: 50, n_agents: 10}; //! //! // Run the simulation //! sim_runner(&mut env, &mut agents, 101, 50, true); //! -//! // Get history of prices over the course of the simulation -//! let price_data = env.get_prices(); +//! // Get history of level 2 data over the course of the simulation +//! let data = env.level_2_data(); //! ``` //! //! # Implementing Your Own Agents //! //! For use in [sim_runner] simulation agents should implement the [agents::AgentSet] //! trait. For a set of homogeneous agents (i.e. all the agents are the -//! same type) this can be implemented directly as in the above example. +//! same type) this can be implemented directly. //! //! For a mixture of agent types, the [agents::Agents] macro can be used //! to automatically implement [agents::AgentSet] for a struct of agents diff --git a/crates/step_sim/tests/test_macros.rs b/crates/step_sim/tests/test_macros.rs new file mode 100644 index 0000000..b9c81fe --- /dev/null +++ b/crates/step_sim/tests/test_macros.rs @@ -0,0 +1,55 @@ +use bourse_book::types::{Price, Side}; +use bourse_de::agents::{Agent, AgentSet, Agents}; +use bourse_de::Env; +use rand::RngCore; +use rand_xoshiro::rand_core::SeedableRng; +use rand_xoshiro::Xoroshiro128StarStar; + +struct TestAgent { + side: Side, + price: Price, +} + +impl TestAgent { + pub fn new(side: Side, price: Price) -> Self { + Self { side, price } + } +} + +impl Agent for TestAgent { + fn update(&mut self, env: &mut Env, _rng: &mut R) { + env.place_order(self.side, 10, 101, Some(self.price)) + .unwrap(); + } +} + +#[test] +fn test_agent_macro() { + #[derive(Agents)] + struct TestAgents { + pub a: TestAgent, + pub b: TestAgent, + } + + let mut env = Env::new(0, 1, 1000, true); + let mut rng = Xoroshiro128StarStar::seed_from_u64(101); + + let mut test_agents = TestAgents { + a: TestAgent::new(Side::Bid, 20), + b: TestAgent::new(Side::Ask, 40), + }; + + test_agents.update(&mut env, &mut rng); + env.step(&mut rng); + + assert!(env.get_orderbook().ask_vol() == 10); + assert!(env.get_orderbook().bid_vol() == 10); + assert!(env.get_orderbook().bid_ask() == (20, 40)); + + test_agents.update(&mut env, &mut rng); + env.step(&mut rng); + + assert!(env.get_orderbook().ask_vol() == 20); + assert!(env.get_orderbook().bid_vol() == 20); + assert!(env.get_orderbook().bid_ask() == (20, 40)); +} diff --git a/docs/source/pages/example.rst b/docs/source/pages/example.rst index 4b081d0..7c4076b 100644 --- a/docs/source/pages/example.rst +++ b/docs/source/pages/example.rst @@ -37,16 +37,15 @@ define an ``update`` method that takes a :py:class:`numpy.random.Generator` and :py:class:`bourse.core.StepEnv` as arguments. -In this example an agent places a randomly order if it +In this example an agent places a random order if it does not have an existing one, and otherwise attempts to -cancel its current order. +cancel its existing order. We then initialise an environment and set of agents .. testcode:: random_example seed = 101 - n_steps = 50 agents = [RandomAgent(i, (10, 100)) for i in range(50)] env = bourse.core.StepEnv(seed, 0, 1, 100_000) @@ -56,6 +55,8 @@ simulation .. testcode:: random_example + n_steps = 50 + market_data = bourse.step_sim.run(env, agents, n_steps, seed) ``market_data`` is a dictionary of Numpy arrays containing market diff --git a/docs/source/pages/usage.rst b/docs/source/pages/usage.rst index e946e49..d245956 100644 --- a/docs/source/pages/usage.rst +++ b/docs/source/pages/usage.rst @@ -16,13 +16,17 @@ Orderbook --------- An orderbook is initialised with a start time -(this is the time used to record events) +(this is the time used to record events) and a +tick-size .. testcode:: book_usage import bourse - book = bourse.core.OrderBook(0, 1) + start_time = 0 + tick_size = 1 + + book = bourse.core.OrderBook(start_time, tick_size) The state of the orderbook an then be directly updated, for example placing a limit bid order @@ -77,7 +81,7 @@ Discrete Event Simulation Environment ------------------------------------- A discrete event simulation environment can be initialised from -a random seed, start-time, and step-size (i.e. how +a random seed, start-time, tick-size, and step-size (i.e. how long in time each simulated step is) .. testcode:: sim_usage @@ -85,8 +89,11 @@ long in time each simulated step is) import bourse seed = 101 + start_time = 0 + tick_size = 2 step_size = 100_000 - env = bourse.core.StepEnv(seed, 0, 1, step_size) + + env = bourse.core.StepEnv(seed, start_time, tick_size, step_size) The state of the simulation is updated in discrete steps, with transactions submitted to a queue to