-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
312 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(()) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
}) | ||
} |