|
1 | | -Simulation for perpetual trading protocol |
| 1 | +1. Overview and Set up |
| 2 | +This simulation models a perpetuals protocol that includes liquidity providers, traders, and pools, and how they interact. The simulation uses cadCAD to model this complex system over time and under different conditions. |
2 | 3 |
|
3 | | -Install requirements from requirements.txt |
| 4 | +Setting up the venv: |
| 5 | +$ python3.9 -m venv venv |
4 | 6 |
|
5 | | -pip install -r requirements.txt |
| 7 | +Activate the venv (Unix) |
| 8 | +$ source venv/bin/activate |
6 | 9 |
|
7 | | -Use python 3.9.16 |
| 10 | +Install requirements from requirements.txt |
| 11 | +$ pip install -r requirements.txt |
8 | 12 |
|
| 13 | +Use python 3.9.16 |
9 | 14 | perputuals_simulation is the directory which contains the executable |
| 15 | +To execute simulation use |
| 16 | +$ python run.py |
| 17 | + |
| 18 | +All of the simulations will be saved into runs directory |
| 19 | + |
| 20 | +2. Components |
| 21 | + |
| 22 | +2.1 Partial State Update Blocks |
| 23 | +The simulation is divided into several partial state update blocks, each representing a distinct aspect of the protocol: |
| 24 | + |
| 25 | +Liquidity (liquidity.py) |
| 26 | +Policies: Dictates how liquidity providers decide to interact with the system. |
| 27 | +Variables: Updates the system state concerning liquidity providers and the liquidity pools. |
| 28 | +Trading (trading.py) |
| 29 | +Policies: Defines the trading behavior of agents in the system. |
| 30 | +Variables: Updates the system state concerning traders, pools, and related metrics. |
| 31 | +Traction (traction.py) |
| 32 | +Policies: Defines how new agents are generated and added to the system. |
| 33 | +Variables: Updates the system state with more liquidity providers and traders. |
| 34 | + |
| 35 | +2.2 System Parameters (sys_params.py) |
| 36 | +Defines both protocol-specific parameters and simulation parameters. The protocol parameters include details like fees, liquidation thresholds, and maximum margins, while the simulation parameters dictate the behavior of agents in the system, such as the chance of trading and the traction of traders. |
| 37 | + |
| 38 | +Initial Conditions (initial_conditions): These dictate the starting state of the system. The parameters you can set include: |
| 39 | + |
| 40 | +genesis_traders: Initial number of traders in the system. |
| 41 | +genesis_providers: Initial number of liquidity providers. |
| 42 | +num_of_min: Number of minutes the simulation should run. |
| 43 | +pool_fees: Fees associated with the liquidity pool. |
| 44 | + |
| 45 | +System Parameters (sys_params): These represent both the protocol and the simulation behavior. |
| 46 | + |
| 47 | +Protocol Parameters: |
| 48 | +base_fee: Basic fee for certain operations. |
| 49 | +ratio_mult: Multiplier for ratios. |
| 50 | +max_margin: Maximum margin for assets. |
| 51 | +liquidation_threshold: Thresholds for asset liquidation. |
| 52 | +And others, like rate parameters, swap fees, and liquidity provider fees. |
| 53 | +Simulation Parameters: |
| 54 | +trader_traction: Percentage change in the number of traders. |
| 55 | +lp_traction: Percentage change in the number of liquidity providers. |
| 56 | +trade_chance: Probability values for long and short trades. |
| 57 | +swap_chance: Probability values for swapping in and out tokens. |
| 58 | +event: Specifies which event (scenario) the simulation should use. |
| 59 | + |
| 60 | +2.3 State Variables (state_variables.py) |
| 61 | +State variables are the backbone of the simulation. They represent the state of the system at any given timestep: |
| 62 | + |
| 63 | +Traders (generate_traders function): |
| 64 | + |
| 65 | +Each trader is initialized with a random amount of liquidity in different assets. |
| 66 | +They have no initial long or short positions. |
| 67 | +Other metrics like PnL, avg_position_hold, and risk_factor are also initialized. |
| 68 | + |
| 69 | +Liquidity Providers (generate_providers function): |
| 70 | + |
| 71 | +Each liquidity provider is given a random amount of funds in different assets. |
| 72 | +Thresholds for adding and removing liquidity are set. |
| 73 | +They have no initial share in the pool. |
| 74 | + |
| 75 | +Pools (generate_pools function): |
| 76 | + |
| 77 | +The pool starts with initial liquidity based on asset prices. |
| 78 | +Various metrics like open interest (oi_long, oi_short), volume, total fees collected, and others are initialized. |
| 79 | +The pool also has initial target ratios, minimum ratios, and maximum ratios for different assets. |
| 80 | + |
| 81 | +Genesis States (genesis_states): |
| 82 | + |
| 83 | +This is an array that collects the initial states for traders, liquidity providers, and pools for each event (scenario) in the simulation. |
| 84 | + |
| 85 | +3. Flow of Simulation |
| 86 | + |
| 87 | +Liquidity Phase: Liquidity providers might decide to add or remove liquidity based on their policies. The liquidity_policy in liquidity.py determines this behavior, and the system state is updated accordingly. |
| 88 | +Trading Phase: Traders might decide to open/close positions or swap tokens. The trading_policy in trading.py defines this behavior. Updates to traders, pools, and related metrics are made. |
| 89 | +Traction Phase: New agents (both traders and liquidity providers) might be added to the system. The behavior is defined in traction.py, and the state variables are updated. |
| 90 | +The simulation runs for a specified number of timesteps, with each timestep consisting of the above three phases. |
| 91 | + |
| 92 | +4. Behavior logic |
| 93 | + |
| 94 | +4.1 Trading logic |
| 95 | +The trading simulation mainly consists of three important functions: trading_decision, swap_decision, and trading_policy. |
| 96 | + |
| 97 | +4.1.1 Trading Decision (trading_decision function trad_mech.py) |
| 98 | +This function aims to simulate the decisions made by an individual trader within the system. It takes into account a multitude of variables like the trader's current financial state, the current timestep, inherent risk tolerance, and the asset being considered for trading. |
| 99 | + |
| 100 | +Key Steps and Logic: |
| 101 | +Asset Pricing: |
| 102 | +cs_price, cl_price, os_price, and ol_price represent different price points for the asset being traded. They are extracted from the asset_pricing dictionary which contains the upper and lower pricing provided by the platform with the upside leaning towards the platform. Initial prices are provided by Pyth oracle. |
| 103 | + |
| 104 | +Position Handling: |
| 105 | +Checks if the trader has any open long or short positions that need to be closed based on their average position hold time (avg_position_hold) or if they meet the liquidation threshold. |
| 106 | +PnL (Profit and Loss), interest, and payout are calculated to determine if liquidation is necessary. |
| 107 | + |
| 108 | +Random Trading Decisions: |
| 109 | +A random number (trade_action) is generated to decide if the trader will initiate a new trade. |
| 110 | +Different checks are in place to ensure that the trader has sufficient liquidity in either the asset or a stablecoin (USDC/USDT) to open a new position. |
| 111 | +The size of the new position (lot_size) is influenced by the trader's risk factor and the maximum available leverage (max_margin). |
| 112 | + |
| 113 | +Interest Handling: |
| 114 | +If the trader already has an open position in the same asset, interest is calculated based on the duration for which the position has been open. |
| 115 | + |
| 116 | +4.1.2 Swap Decision (swap_decision function swap_mech.py) |
| 117 | +This function determines if a trader will swap one asset for another. The function takes into account various factors such as the trader's existing liquidity in the asset, current asset prices, and the likelihood of a swap (swap_chance). |
| 118 | + |
| 119 | +Key Steps and Logic: |
| 120 | +Eligibility Check: |
| 121 | +The function first checks if the asset is tradable (asset_prices[asset][2] == True). If not, the function returns None. |
| 122 | + |
| 123 | +Random Swap Decision: |
| 124 | +A random number (swap_action) is generated to decide whether the trader will perform a swap operation. |
| 125 | +Buy Decision (swap_action < swap_chance[0]): |
| 126 | + |
| 127 | +If the random number falls below swap_chance[0], the trader decides to buy a new asset. |
| 128 | +A random amount of the existing asset (swap_in) is selected to be swapped. |
| 129 | +A random target asset (swap_out_asset) is chosen. |
| 130 | +The quantity of the target asset to be obtained (swap_out) is calculated based on current market prices. |
| 131 | +Sell Decision (swap_action > swap_chance[1]): |
| 132 | + |
| 133 | +If the random number is greater than swap_chance[1], the trader decides to sell the asset. |
| 134 | +A random amount of the existing asset (swap_out) is selected to be swapped out. |
| 135 | +A random target asset (swap_in_asset) is chosen. |
| 136 | +The quantity of the target asset to be obtained (swap_in) is calculated based on current market prices. |
| 137 | +Output: |
| 138 | + |
| 139 | +The function returns a dictionary containing the details of the swap (swap_in and swap_out), or None if no swap is to be performed. |
| 140 | +Example Output: |
| 141 | +Buy decision: {'swap_in': [5, 'ETH'], 'swap_out': [15, 'USDC']} |
| 142 | +Sell decision: {'swap_in': [20, 'USDC'], 'swap_out': [2, 'ETH']} |
| 143 | +No swap: None |
| 144 | + |
| 145 | +4.1.3 Trading Policy (trading_policy function trading.py) |
| 146 | +This function orchestrates the trading decisions across all traders and liquidity pools in the system. It does so by calling the trading_decision function for each trader and asset pair. |
| 147 | + |
| 148 | +Key Steps and Logic: |
| 149 | + |
| 150 | +Iterating Over Pools and Traders: |
| 151 | + |
| 152 | +For every pool and trader, the trading_decision function is called, and the decisions are stored. |
| 153 | + |
| 154 | +Trade Execution: |
| 155 | +The trade decisions returned by trading_decision are executed using helper functions like execute_long and execute_short. |
| 156 | +These functions also update relevant metrics like fees, liquidations, and swaps. |
| 157 | + |
| 158 | +Token Swapping: |
| 159 | +The swap_decision function guides the decision-making process for token swaps. If a trader decides to perform a swap, the swap_tokens function is invoked to carry out the swap operation. The fees associated with the swap are then calculated using the swap_fee_calc function. |
| 160 | + |
| 161 | +Metrics and State Updates: |
| 162 | +The state of the traders, liquidity providers, and pools is updated based on the executed trades and swaps. |
| 163 | +Metrics such as the number of longs, shorts, swaps, and liquidations are tallied and returned as part of the action dictionary. |
| 164 | + |
| 165 | +Helper Functions: |
| 166 | +execute_long: Responsible for opening or closing a long position based on the decisions made. It updates the trader's and pool's state accordingly. |
| 167 | +execute_short: Similar to execute_long but for short positions. |
| 168 | +swap_tokens: Executes the actual token swapping between different assets in the pool. |
| 169 | +swap_fee_calc: Determines the fees associated with a token swap. |
| 170 | +calculate_open_pnl: Updates the open PnL (Profit and Loss) for traders based on their open positions and the current asset prices. |
| 171 | + |
| 172 | +4.2 Liquidity provisioning logic |
| 173 | + |
| 174 | +4.2.1 Liquidity provisioning decision (liquidity_provider_decision Function liq_mech.py) |
| 175 | +This function determines the amount of liquidity to be added or removed from the pool by a liquidity provider. |
| 176 | + |
| 177 | +Key Steps and Logic: |
| 178 | +Initialize Decision Variables: |
| 179 | +A decision dictionary is initialized with asset names as keys and a default value of 0. |
| 180 | + |
| 181 | +Calculate Pool Yield and Volatility Multipliers: |
| 182 | +The function calculates an aggregate pool yield and volatility, weighted by the ratio of assets in the pool. |
| 183 | + |
| 184 | +Threshold Adjustment: |
| 185 | +The add and remove thresholds for each asset are dynamically adjusted based on the calculated volatility. |
| 186 | + |
| 187 | +Liquidity Addition: |
| 188 | +If the calculated yield exceeds the adjusted 'add_threshold', the liquidity provider considers adding liquidity. |
| 189 | +The amount of liquidity to be added is proportional to the excess yield and the available funds. |
| 190 | + |
| 191 | +Liquidity Removal: |
| 192 | +Conversely, if the yield is below the 'remove_threshold', the liquidity provider considers removing liquidity. |
| 193 | +The amount of liquidity to be removed is proportional to the shortfall in yield and the current liquidity. |
| 194 | +Output: |
| 195 | + |
| 196 | +The function returns a dictionary containing decisions about the amount of liquidity to add or remove for each asset. |
| 197 | + |
| 198 | +4.2.2 Liquidity policy (liquidity_policy function liquidity.py) |
| 199 | +This function updates the state variables to reflect the decisions made by each liquidity provider. |
| 200 | + |
| 201 | +Key Steps and Logic: |
| 202 | +Initialize State Variables: |
| 203 | +State variables are initialized from the previous state. |
| 204 | + |
| 205 | +Asset Volatility and Prices: |
| 206 | +Asset volatility and prices are fetched for each pool. |
| 207 | + |
| 208 | +Total Value Locked (TVL) and Pool Ratios: |
| 209 | +The TVL and asset ratios for each pool are updated. |
| 210 | + |
| 211 | +Liquidity Decisions: |
| 212 | +For each liquidity provider, the liquidity_provider_decision function is invoked to get the liquidity provision decisions. |
| 213 | + |
| 214 | +Constraints and Adjustments: |
| 215 | +Various checks and constraints are applied to ensure that the liquidity provision decisions are viable. |
| 216 | +Open positions and fees are also considered in the decision-making process. |
| 217 | + |
| 218 | +State Update: |
| 219 | +State variables are updated to reflect the new liquidity levels. |
| 220 | + |
| 221 | +Output: |
| 222 | +The function returns a dictionary containing the updated state variables. |
| 223 | + |
| 224 | +5. Monte Carlo Runs and Execution |
| 225 | + |
| 226 | +5.1 Execution (run.py) |
| 227 | +This script simulates a system and extracts relevant metrics from the simulation. |
| 228 | + |
| 229 | +Functions: |
| 230 | +run(event): |
| 231 | +Purpose: Executes the simulation for a specific event. |
| 232 | +Arguments: |
| 233 | +event: Index of the event/scenario to run the simulation for. |
| 234 | +Returns: DataFrame (df) containing raw system events. |
| 235 | + |
| 236 | +postprocessing(df, event): |
| 237 | +Purpose: Processes the raw system events from the simulation to extract relevant metrics. |
| 238 | +Arguments: |
| 239 | +df: DataFrame containing raw system events. |
| 240 | +event: Name or identifier of the event/scenario. |
| 241 | +Returns: DataFrame (data_df) with aggregated metrics. |
| 242 | + |
| 243 | +main(): |
| 244 | +Purpose: Main function to run the simulation and extract metrics for multiple scenarios and iterations. |
| 245 | +Behavior: For each scenario and iteration, it runs the simulation, saves the raw events to a JSON file, then processes the events to extract metrics and saves them to an Excel file. |
| 246 | + |
| 247 | +Execution: |
| 248 | +$ python run.py |
| 249 | + |
| 250 | +Control: |
| 251 | +To change the number of events or mc runs edit the variables on the following lines |
| 252 | +165 starting_event |
| 253 | +166 ending_event |
| 254 | +167 number_of_mc |
| 255 | + |
| 256 | +5.2 Merging (merger.py) |
| 257 | +This script merges multiple Excel files with simulation results into a single aggregated Excel file. |
| 258 | + |
| 259 | +Functions: |
| 260 | +main(): |
| 261 | +Purpose: Main function to merge multiple Excel files. |
| 262 | +Behavior: For each scenario, it reads the Excel files of multiple iterations, aggregates the data, and saves the aggregated data to a new Excel file. |
| 263 | + |
| 264 | +Execution: |
| 265 | +$ python merger.py |
| 266 | + |
| 267 | +All of the mergers will be saved into runs_merged directory |
| 268 | + |
| 269 | +Control: |
| 270 | +To change the number of events or mc runs edit the variables on the following lines |
| 271 | +7 starting_event |
| 272 | +8 ending_event |
| 273 | +9 number_of_mc |
| 274 | + |
| 275 | +5.3 Decoding jsons (json_decoder.py) |
| 276 | +This script reads JSON files with simulation results, processes the results, and saves them to Excel files. |
| 277 | + |
| 278 | +Functions: |
| 279 | + |
| 280 | +list_check(item): |
| 281 | +Purpose: Checks if an item is a list. If so, returns the first element; otherwise, returns the item itself. |
| 282 | +Arguments: |
| 283 | +item: The item to check. |
| 284 | +Returns: First element of the list (if item is a list) or the item itself. |
| 285 | + |
| 286 | +postprocessing(df, event): |
| 287 | +Purpose: Similar to the postprocessing function in run.py, but adapted to handle data from JSON files. |
| 288 | +Arguments: |
| 289 | +df: DataFrame containing raw system events. |
| 290 | +event: Name or identifier of the event/scenario. |
| 291 | +Returns: DataFrame (data_df) with aggregated metrics. |
| 292 | + |
| 293 | +main(): |
| 294 | +Purpose: Main function to read JSON files, process the data, and save it to Excel files. |
| 295 | +Behavior: For each iteration, it reads the JSON file, processes the data, and saves the metrics to an Excel file. |
10 | 296 |
|
11 | | -Use python run.py to execute simulation |
| 297 | +Execution: |
| 298 | +$ python json_decoder.py |
12 | 299 |
|
13 | | -If adjustments needed use sys_params.py and config.py to make necessary changes |
| 300 | +All of the decodings will be saved into runs directory |
0 commit comments