Skip to content

Commit

Permalink
dumb momentum strategy
Browse files Browse the repository at this point in the history
  • Loading branch information
kinrezC committed Oct 25, 2023
1 parent 55b6b38 commit 444067b
Showing 6 changed files with 312 additions and 0 deletions.
53 changes: 53 additions & 0 deletions simulation/configs/momentum/static.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
simulation = "DynamicWeights"
output_directory = "momentum_static"

[trajectory]
# The type of price process to use.
process = "gbm"
# The number of steps in the process.
num_steps = 100
# Seed for the price processes
seed = 2
# The number of distinct paths to use
num_paths = 1
# The initial price of the asset.
[trajectory.initial_price]
fixed = 1.0
# The start time of the process.
[trajectory.t_0]
fixed = 0.0
# The end time of the process.
[trajectory.t_n]
fixed = 1.0

[gbm]
# The drift of the process.
[gbm.drift]
fixed = 0.1
# The volatility of the process.
[gbm.volatility]
fixed = 0.40


[pool]
# The weight for the `token_x` in the pool.
# `weight_y = 1 - weight_x`
weight_x = 0.5
# The swap fee in BPs.
fee_basis_points = 30
# Target volatility for the pool
target_volatility = 0.15


[lp]
x_liquidity = 1.0# in ether

[weight_changer]
target_volatility = 0.15
#update_frequency = 450 # 1 update per 30 timestep day
#update_frequency = 300 # 1 update per 20 timestep day
update_frequency = 150 # 1 update per 10 timestep day
#update_frequency = 75 # 1 update per 5 timestep day

[block]
timestep_size = 15
1 change: 1 addition & 0 deletions simulation/src/agents/mod.rs
Original file line number Diff line number Diff line change
@@ -6,6 +6,7 @@ pub mod liquidity_provider;
pub mod price_changer;
pub mod token_admin;
pub mod weight_changer;
pub mod momentum_strategist;

use std::marker::{Send, Sync};

181 changes: 181 additions & 0 deletions simulation/src/agents/momentum_strategist.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
use super::*;
use crate::math::*;

#[derive(Clone)]
pub struct MomentumStrategist {
pub client: Arc<RevmMiddleware>,
pub lex: LiquidExchange<RevmMiddleware>,
pub g3m: G3M<RevmMiddleware>,
pub next_update_timestamp: u64,
pub update_frequency: u64,
pub portfolio_prices: Vec<(f64, u64)>,
pub asset_prices: Vec<(f64, u64)>,
pub portfolio_returns: Vec<(f64, u64)>,
pub asset_returns: Vec<(f64, u64)>,
}

impl MomentumStrategist {
pub async fn new(
environment: &Environment,
config: &SimulationConfig<Fixed>,
liquid_exchange_address: Address,
arbx: Address,
arby: Address,
) -> Result<Self> {
let client = RevmMiddleware::new(environment, "weight_changer".into())?;

let g3m_args = (
arbx,
arby,
ethers::utils::parse_ether(config.pool.weight_x)?,
U256::from(config.pool.fee_basis_points),
);
let g3m = G3M::deploy(client.clone(), g3m_args)?.send().await?;
let lex = LiquidExchange::new(liquid_exchange_address, client.clone());
Ok(Self {
client,
lex,
g3m,
update_frequency: config.weight_changer.update_frequency,
next_update_timestamp: config.weight_changer.update_frequency,
portfolio_prices: Vec::new(),
asset_prices: Vec::new(),
portfolio_returns: Vec::new(),
asset_returns: Vec::new(),
})
}

fn calculate_returns(&mut self) -> Result<()> {
// if self.asset_prices.len() > 15 then only calcualte for the last 15 elements
if self.asset_prices.len() > 15 {
let asset_return = compute_net_returns(
self.asset_prices
.iter()
.skip(self.asset_prices.len() - 15)
.map(|(price, _)| *price)
.collect::<Vec<f64>>(),
);
self.asset_returns.push((asset_return, self.next_update_timestamp));
}
if self.portfolio_prices.len() > 15 {
let portfolio_return = compute_net_returns(
self.portfolio_prices
.iter()
.skip(self.portfolio_prices.len() - 15)
.map(|(price, _)| *price)
.collect::<Vec<f64>>(),
);

self.portfolio_returns
.push((portfolio_return, self.next_update_timestamp));
}
info!(
"hypothetical percent asset return: {}",
(self.asset_prices.last().unwrap().0 - self.asset_prices.first().unwrap().0)
/ self.asset_prices.first().unwrap().0
);
info!(
"portfolio percent return: {}",
(self.portfolio_prices.last().unwrap().0 - self.portfolio_prices.first().unwrap().0)
/ self.portfolio_prices.first().unwrap().0
);
info!(
"initial portfolio price: {}",
self.portfolio_prices.first().unwrap().0
);
info!(
"current portfolio price: {}",
self.portfolio_prices.last().unwrap().0
);

Ok(())
}

// dumb poc, this just checks if the portfolio rv is greater than the target rv
// then changes weight by 1% over the course of a day depending on if rv is
// greater or less than target
async fn execute_smooth_rebalance(&mut self) -> Result<()> {
if self.portfolio_returns.len() < 2 {
return Ok(());
}
let portfolio_return = self.portfolio_returns.last().unwrap().0;
info!("portfolio_rv: {}", portfolio_return);
let current_weight_x = self.g3m.weight_x().call().await?;
let current_weight_float = format_ether(current_weight_x).parse::<f64>().unwrap();
info!("current_weight_float: {}", current_weight_float);
if portfolio_return > 0.0 {
let new_weight = current_weight_float + 0.0025;
info!("new weight: {}", new_weight);
self.g3m
.set_weight_x(
parse_ether(new_weight.to_string()).unwrap(),
U256::from(self.next_update_timestamp),
)
.send()
.await?;
} else {
let new_weight = current_weight_float - 0.0025;
info!("new weight: {}", new_weight);
self.g3m
.set_weight_x(
parse_ether(new_weight.to_string()).unwrap(),
U256::from(self.next_update_timestamp),
)
.send()
.await?;
}
Ok(())
}
}

#[async_trait::async_trait]
impl Agent for MomentumStrategist {
async fn step(&mut self) -> Result<()> {
let timestamp = self.client.get_block_timestamp().await?.as_u64();
if timestamp >= self.next_update_timestamp {
self.next_update_timestamp = timestamp + self.update_frequency;
let asset_price = format_ether(self.lex.price().call().await?)
.parse::<f64>()
.unwrap();

let reserve_x = format_ether(self.g3m.reserve_x_without_precision().call().await?)
.parse::<f64>()
.unwrap();
let reserve_y = format_ether(self.g3m.reserve_y_without_precision().call().await?)
.parse::<f64>()
.unwrap();

let portfolio_price = reserve_x * asset_price + reserve_y;
info!("portfolio_price: {}", portfolio_price);

self.asset_prices.push((asset_price, timestamp));
self.portfolio_prices.push((portfolio_price, timestamp));
// info!("asset_prices: {:?}", self.asset_prices);
// info!("portfolio_prices: {:?}", self.portfolio_prices);
self.calculate_returns()?;
self.execute_smooth_rebalance().await?;
}
Ok(())
}

async fn startup(&mut self) -> Result<()> {
let asset_price = format_ether(self.lex.price().call().await?)
.parse::<f64>()
.unwrap();

let reserve_x = format_ether(self.g3m.reserve_x_without_precision().call().await?)
.parse::<f64>()
.unwrap();
let reserve_y = format_ether(self.g3m.reserve_y_without_precision().call().await?)
.parse::<f64>()
.unwrap();

let portfolio_price = reserve_x * asset_price + reserve_y;
info!("portfolio_price: {}", portfolio_price);

self.portfolio_prices.push((portfolio_price, 0));
self.asset_prices.push((asset_price, 0));

Ok(())
}
}
12 changes: 12 additions & 0 deletions simulation/src/math.rs
Original file line number Diff line number Diff line change
@@ -36,6 +36,12 @@ pub fn compute_simple_returns(values: impl IntoIterator<Item = f64>) -> Vec<f64>
returns
}

pub fn compute_net_returns(values: impl IntoIterator<Item = f64>) -> f64 {
let values = values.into_iter().collect::<Vec<f64>>();
let net_return = values.last().unwrap_or(&0.0) / values.first().unwrap_or(&1.0) - 1.0;
net_return
}

pub fn compute_variance(values: impl IntoIterator<Item = f64>) -> f64 {
let values = values.into_iter().collect::<Vec<f64>>();
let mean = values.iter().sum::<f64>() / values.len() as f64;
@@ -98,6 +104,12 @@ mod tests {
assert_eq!(returns, [1.0, 0.5]);
}

#[test]
fn test_compute_net_return() {
let returns = compute_net_returns(VALUES);
assert_eq!(returns, 2.0);
}

#[test]
fn test_compute_variance() {
let variance = compute_variance(VALUES);
3 changes: 3 additions & 0 deletions simulation/src/simulations/mod.rs
Original file line number Diff line number Diff line change
@@ -11,6 +11,7 @@ use crate::{
pub mod dynamic_weights;
pub mod errors;
pub mod stable_portfolio;
pub mod momentum;
use settings::parameters::Parameterized;
use tokio::runtime::Builder;

@@ -24,13 +25,15 @@ pub struct Simulation {
pub enum SimulationType {
DynamicWeights,
StablePortfolio,
MomentumStrategy,
}

impl SimulationType {
async fn run(config: SimulationConfig<Fixed>) -> Result<(), SimulationError> {
let simulation = match config.simulation {
SimulationType::DynamicWeights => dynamic_weights::setup(config).await?,
SimulationType::StablePortfolio => stable_portfolio::setup(config).await?,
SimulationType::MomentumStrategy => momentum::setup(config).await?,
};
looper(simulation.agents, simulation.steps).await?;
Ok(())
62 changes: 62 additions & 0 deletions simulation/src/simulations/momentum.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
use arbiter_core::environment::builder::BlockSettings;

use super::{errors::SimulationError, *};
use crate::{
agents::{
arbitrageur::Arbitrageur, block_admin::BlockAdmin, liquidity_provider::LiquidityProvider,
price_changer::PriceChanger, token_admin::TokenAdmin, momentum_strategist::MomentumStrategist, Agent,
Agents,
},
bindings::i_strategy::IStrategy,
settings::SimulationConfig,
};

pub async fn setup(config: SimulationConfig<Fixed>) -> Result<Simulation, SimulationError> {
let environment = EnvironmentBuilder::new()
.block_settings(BlockSettings::UserControlled)
.build();
let mut block_admin = BlockAdmin::new(&environment, &config).await?;

let token_admin = TokenAdmin::new(&environment).await?;
let mut price_changer = PriceChanger::new(&environment, &token_admin, &config).await?;
let mut weight_changer = MomentumStrategist::new(
&environment,
&config,
price_changer.liquid_exchange.address(),
token_admin.arbx.address(),
token_admin.arby.address(),
)
.await?;

let mut lp = LiquidityProvider::<IStrategy<RevmMiddleware>>::new(
&environment,
&token_admin,
weight_changer.g3m.address(),
&config,
)
.await?;
let mut arbitrageur = Arbitrageur::<IStrategy<RevmMiddleware>>::new(
&environment,
&token_admin,
weight_changer.lex.address(),
weight_changer.g3m.address(),
)
.await?;

EventLogger::builder()
.path(config.output_directory)
.add(price_changer.liquid_exchange.events(), "lex")
.add(weight_changer.g3m.events(), "g3m")
.run()?;

Ok(Simulation {
agents: Agents::new()
.add(price_changer)
.add(arbitrageur)
.add(block_admin)
.add(weight_changer)
.add(lp),
steps: config.trajectory.num_steps,
environment,
})
}

0 comments on commit 444067b

Please sign in to comment.