Giter Site home page Giter Site logo

curvesim's Introduction

image image image Code style: black CI Docs badge

Curvesim

Curvesim simulates Curve pools with optimal arbitrageurs trading against them to determine reasonable risk and reward parameters, such as amplitude (A) and fee, given historical price and volume feeds.

Users can re-use simulation components to simulate custom strategies and generate custom metrics. Pool objects enable simpler integration with Curve pools for both manual and automated analytics usage.

Dependencies:

Python versions 3.8 - 3.11 are supported.

Primary package dependencies: scipy, numpy, pandas, altair, matplotlib, requests, web3, gmpy2

When working on the codebase, to avoid dependency issues it is recommended to use the included requirements.txt file in a Python virtual environment (venv). Python 3.10 is required for this.

Documentation

Check out the full documentation at https://curvesim.readthedocs.io/. We recommend starting with the "Quickstart" guide.

Licensing

Portions of the codebase are authorized derivatives of code owned by Curve.fi (Swiss Stake GmbH). These are the vyper snippets used for testing (test/fixtures/curve) and the python code derived from them (curvesim/pool/stableswap and curvesim/pool/cryptoswap); there are copyright notices placed appropriately. The rest of the codebase has an MIT license.

Basic Use: Autosim

The autosim() function simulates existing Curve pools with a range of A and/or fee parameters. The function fetches pool properties (e.g., current pool size) and 2 months of price/volume data, runs multiple simulations in parallel, and returns a results object that can be introspected or generate charts.

Curve pools from any chain supported by the Convex Community Subgraphs can be simulated directly by inputting the pool's address.

Example:

To simulate 3pool with the default configuration:

import curvesim
res = curvesim.autosim("0xbEbc44782C7dB0a1A60Cb6fe97d0b483032FF1C7")

To simulate pools on chains other than Ethereum, use the "chain" argument:

import curvesim
# run sim on Arbitrum 2Crv
res = curvesim.autosim("0x7f90122BF0700F9E7e1F688fe926940E8839F353", chain="arbitrum")

Simulation Results

The simulation returns a SimResults object that can plot simulation metrics or return them as DataFrames.

Plotting results:

#Plot results using Altair
res.plot() 

#Save plot results as results.html
res.plot(save_as="results.html")

Example output:

Alt text

Alt text

Summary statistics:

>>> res.summary()
metric pool_value_virtual         pool_value  ...   pool_volume price_error
stat   annualized_returns annualized_returns  ...           sum      median
0                0.003580           0.005156  ...  2.286297e+09    0.000669
1                0.006158           0.007741  ...  1.966299e+09    0.000600
2                0.007760           0.009348  ...  1.652965e+09    0.000775
3                0.008611           0.010200  ...  1.377299e+09    0.000956
4                0.003760           0.005439  ...  2.400174e+09    0.000777
..                    ...                ...  ...           ...         ...
59               0.009523           0.012018  ...  1.521524e+09    0.001155
60               0.003742           0.006247  ...  2.388746e+09    0.001063
61               0.006533           0.009082  ...  2.084530e+09    0.000915
62               0.008344           0.010894  ...  1.775963e+09    0.000974
63               0.009402           0.011974  ...  1.502494e+09    0.001133

Timeseries data:

>>> res.data()
       run                 timestamp  ...      pool_volume  price_error
0        0 2023-03-21 23:30:00+00:00  ...  15206414.533633     0.005310
1        0 2023-03-22 00:30:00+00:00  ...    7278720.40969     0.002029
2        0 2023-03-22 01:30:00+00:00  ...   6125207.553072     0.000100
3        0 2023-03-22 02:30:00+00:00  ...    7066251.03295     0.000100
4        0 2023-03-22 03:30:00+00:00  ...   3512782.000945     0.000299
...    ...                       ...  ...              ...          ...
93755   63 2023-05-21 19:30:00+00:00  ...    879436.331564     0.000893
93756   63 2023-05-21 20:30:00+00:00  ...              0.0     0.001091
93757   63 2023-05-21 21:30:00+00:00  ...    720837.826971     0.000800
93758   63 2023-05-21 22:30:00+00:00  ...    445967.506177     0.001414
93759   63 2023-05-21 23:30:00+00:00  ...    391060.986022     0.000906

Customizing Simulation Parameters

By default, pools are simulated using:

  • on-chain or Curve Subgraph data about the pool
  • a broad range of A (16 values, 64 to 11,585) and fee (5 values, 0.02 to 0.06%) values
  • CoinGecko price/volume data
  • all detected CPU cores

However, all of these can be altered using optional keyword arguments.

Custom A and/or Fee Ranges

Custom A and fee ranges can be specified using the "A" and "fee" arguments. Inputs must be lists or numpy arrays containing lists:

import curvesim
import numpy as np

#Specify A values:
res = curvesim.autosim('0xbEbc44782C7dB0a1A60Cb6fe97d0b483032FF1C7', A=range(1000,2001,100))

#Specify fees (0.03% and 0.04% with 10 decimal precision):
res = curvesim.autosim('0xbEbc44782C7dB0a1A60Cb6fe97d0b483032FF1C7', fee=[3000000, 4000000])

#Specify custom A range and 0.03% fee
res = curvesim.autosim('0xbEbc44782C7dB0a1A60Cb6fe97d0b483032FF1C7', A=range(1000,2001,100), fee=3000000)

Additionally, a small number of A/fee values (2 each) can be set for testing purposes:

res = curvesim.autosim('0xbEbc44782C7dB0a1A60Cb6fe97d0b483032FF1C7', test=True)

Overriding Simulation Parameters

The following parameters are automatically specified by autosim(), but can be overridden with keyword arguments:

  • D: total deposit size; default: fetched from on-chain data
  • vol_mult: multiplied by market volume to produce trade size limits; default: computed from Curve Subraph data (see Volume Limits for details)
  • feemul: fee multiplier used in dynamic fee pools
import curvesim

#Simulate 3pool assuming total deposit of $10B, fee = 0.03%
res = curvesim.autosim('0xbEbc44782C7dB0a1A60Cb6fe97d0b483032FF1C7', D=10000000000, fee=3000000)

#For metapools, specifying D effects the deposit in the metapool, but not the basepool
#Simulate USDN metapool assuming total deposit of $1B, fee = 0.03%
res = curvesim.autosim('0x0f9cb53Ebe405d49A0bbdBD291A65Ff571bC83e1', D=1000000000, fee=3000000)

#Simulate 3pool, limiting volume to 75% of market volume, fee = 0.03% 
#Note: it is not reccomended to adjust this parameter, try vol_mode instead (see below)
res = curvesim.autosim('0xbEbc44782C7dB0a1A60Cb6fe97d0b483032FF1C7', vol_mult=.75, fee=3000000)

#Simulate hypothetical 3pool with dynamic fee like AAVE pool, fee = 0.03% 
res = curvesim.autosim('0xbEbc44782C7dB0a1A60Cb6fe97d0b483032FF1C7', feemul=2*10**10, fee=3000000)

Volume Limits

To approximate realistic market conditions, the "vol_mult" argument is used to limit trade volume at each timepoint in the simulation. By default (vol_mode=1), vol_mult is simply the expected proportion of market volume that goes through the Curve pool (e.g., monthly pool volume / monthly price feed volume). At each timepoint, vol_mult is multiplied by the each coin-pair's market volume to produce a volume limit for each pair, thereby appropriately scaling trade volume while retaining natural volume dynamics. For metapools, vol_mult is computed seperately for the base pool and meta-pool.

In some cases, it may be helpful to limit trade volume differently. In particular, for new coins with little volume for some (but not all) pairs included in the pool, more robust simulations can be achieved by assuming equal volumes across trading pairs. We therefore provide three volume-limiting modes (vol_mode):

  • vol_mode = 1: limits trade volumes proportionally to market volume for each pair (default)
  • vol_mode = 2: limits trade volumes equally across pairs
  • vol_mode = 3: mode 2 for trades with meta-pool asset, mode 1 for basepool-only trades

We reccomend using the default vol_mode 1 in most cases. However, if that returns noisy/uninterpretable results, it may be worth trying mode 2 (for normal pools) or mode 3 (for meta-pools).

Data Sources

The "src" argument can be used to choose between two different data sources:

  • src = "coingecko": CoinGecko API (free); default
  • src = "local": local data stored in the "data" folder

Note that Nomics data is no longer supported since they went out of service.

Note on CoinGecko Data

Coingecko price/volume data is computed using all trading pairs for each coin, with volume summed across all pairs. Therefore, market volume taken from CoinGecko can be much higher than that of any specific trading pair used in a simulation. This issue is largely ameloriated by our volume limiting approach, with CoinGecko results typically mirroring results from pairwise data, but it should be noted that CoinGecko data may be less reliable than more granular data for certain simulations.

Technical Parameters

Additionally, one can specify:

  • ncpu: number of CPUs to use for parallel processing (default: all cores); for use with profilers, e.g. cProfile, use ncpu=1.
  • days: the number of days worth of data to use in the simulation (default: 60)

curvesim's People

Contributors

chanhosuh avatar nagakingg avatar allt0ld avatar github-actions[bot] avatar umerha avatar benber86 avatar tcintra avatar

Stargazers

Ben avatar Rar3 avatar  avatar Christian Eakin St. Louis avatar  avatar  avatar lyftium avatar juan_p2p avatar HOANG Bao Tin avatar  avatar  avatar  avatar Hank avatar WormholeOracle avatar Rafael Ugolini avatar Ian Moore avatar Shawn Harmsen avatar Humban avatar Harvey Specter avatar  avatar Erhan avatar niluke avatar Taka avatar abmis avatar William Eviso avatar eth_sign avatar Alpine avatar Zachary Carlin avatar Yusuke avatar Mark Guo avatar  avatar  avatar Karl Yu avatar emre avatar Ilya Lesik avatar lee.aki avatar longcpp avatar miguel avatar 49 avatar Eren Gökkaya avatar Camotelli avatar  avatar Jonny Dubowsky avatar  avatar Rodrigo Gaona avatar Artur Sepp avatar SimkoCarlos avatar Alvaro Serrano avatar bgbrthrs avatar Keshav Mundhra avatar wudancheng avatar Dan avatar  avatar  avatar  avatar null avatar Mikko Ohtamaa avatar  avatar  avatar Max Abouchar avatar Julian Carrier avatar  avatar  avatar  avatar  avatar didadi avatar Ahmed Fayed avatar mo avatar Paco avatar Frances He avatar  avatar marble avatar  avatar bobjonas avatar Shane Fontaine avatar Dist Bit avatar  avatar  avatar Shawn Anderson avatar Mehmetcan Budak avatar John avatar  avatar wk0 avatar  avatar Simon Leung avatar Matías Agustín Méndez avatar Beni Ben zikry avatar sudo rm -rf --no-preserve-root / avatar ljmanini avatar Dean Eigenmann avatar Marcelo Morgado avatar 0xmanny avatar Scott Simpson avatar dudesahn avatar 6 avatar Michael Egorov avatar Serhii Korniushov avatar  avatar gosuto.eth avatar William Gay avatar

Watchers

Michael Egorov avatar Camotelli avatar  avatar Peter Lai avatar Kostas Georgiou avatar

curvesim's Issues

Pull coin decimals to use proper precision in pool

Right now sims generate pool objects with 18 decimals for uniformity in calculated results, which is helpful for analysis. As a new feature of curvesim is to enable interactive usage of pool objects, we should pull the decimals/precision data and by default, create Pool instances with those precisions.

Create "Advanced Usage" section of the docs

Tasks

Create `CurveCryptoPool` class

TODO:

  • Create vyper text fixtures
  • Create class with necessary attributes, __init__, adapting as needed
  • Use __slots__
  • Implement external functions and core internal functions
    • newton_D
    • newton_y
    • halfpow
    • geometric_mean
    • sqrt_int
    • get_xcp
    • tweak_price
    • get_dy
    • _exchange
    • exchange
    • exchange_underlying
    • add_liquidity
    • remove_liquidity
    • remove_liquidity_one_coin
    • calc_token_amount
    • _calc_withdraw_one_coin / calc_withdraw_one_coin
    • get_virtual_price
    • price_oracle / internal_price_oracle
    • lp_price

Load historical pool volumes from file

Hypothetical pools won't have historic pool volumes available from the convex subgraph, so this will need to be loaded from file.

I'd like to add this functionality. From your perspective, is there a reason against that?

Update README

"Forthcoming" section is obsolete now. In particular, we need to explain the new functionality such as alternate chains and population from the registry.

A good once-over of the whole thing would be excellent also!

Sim Request: OUSD

Run sim with default A and fee params for OUSD-3CRV metapool

[SETTINGS]
address = 0x87650D7bbfC3A9F10587d7778206671719d9910D
chain = mainnet

Add crypto pools

So far only stable pools are supported. Crypto pools still need to be added.

Integration tests for APIs

Should test against live APIs to check for breakages.

In order to do this, we will need to make the queries deterministic. Currently some queries use utc.now() and so the result will always change. Typical practice for such queries is to have an end date which can default to current date. This can be fed a fixed date for testing purposes.

Remove `test_trade` from `SimPool` implementations

Almost everything in test_trade can be done via trade with snapshotting and just computing the factor stuff outside. The only pool internal logic is handling the indices, which only means we can update trade to handle the base token.

Graph update for admin fees

The pool unit tests PR includes changes for incorporating admin fees into the sims. This will necessitate changes to the sim code and to the graphs to show the collected admin fees.

Blocked by:

  • #171
  • #185 (possibly -- to get the graphs just for stableswap, we could probably do some workaround)

Find Nomics alternative

Thank you for being a Nomics customer. Unfortunately, we will be sunsetting our paid API product on Mar 31, 2023 (i.e. in two months).

We need to start looking into alternatives for quality data (better than Coingecko) and find a replacement soon.

Feature: Curve V2 Sim

  • #91
  • Create SimCurveCryptoPool class (#180)
    • #178
    • #179
    • misc methods: prepare_for_trades, assets
  • Grid class should be refactored with mixins to extend current functionality to cryptopools (#158)
  • metrics framework needs updating: getters, PoolBalance needs to incorporate price, PoolValue needs to use xcp(D)
  • Update relevant network modules (we have the subgraph module updated and metadata is used to instantiate the cryptopool)
  • Update volume multiplier logic (#182)
  • Pipeline alterations to accommodate initial pool setup (#200)
  • Add v2 params into autosim argument parser
  • Tune/adjust crypto sims to exhibit useful/realistic behavior

Search for address from symbol

In [25]: await curvesim.network.subgraph.symbol_address("mim", "mainnet")

Now that we have removed symbol lookup for creating pools from on-chain, we could keep the above functionality as a search function. So instead of error-ing out, it should return search results. We might want to add options like "start_with" (currently what's done) and "contains" etc.

Simple arb pipeline

We want to have a simpler alternative to the current curvesim.pipelines.vol_limited_arb, let's say curvesim.pipeline.simple.

This simple pipeline will let us achieve several objectives:

  • removing the complex numeric solver for doing multi-pair arbitrages will make the end-to-end tests numerically stable, enabling replication of results between local dev and GH Actions environments
  • having a simpler alternative will create a more re-usable set of components for more complex strategies
  • documentation, particularly the "advanced usage" guide, will be more readable and understandable to users looking to create custom strategies

Add logger

Should use the standard python logger to capture various logging outputs. This is useful for general debugging and user error logs.

Standardize output folder names

Right now, different poolname inputs will create different result folders for the same pool. Should be standardized somehow, e.g. symbol + 6 char prefix from address, to balance readability with uniqueness.

Bug in volume limits?

The underlying trade volume data, which is used for limiting volume of arb trades, describes the last 24h; but that data is used with granularity 30mins.

IIUC, with the current setup, arbitrageurs can trade up to 24h trade volume every 30mins, which I think is wrong. The volume amount should be divided by 48.

Is my understanding correct?

Happy to submit a PR for that!

Pull and process cryptoswap params

  • Update curve volume subgraph to pull the following fields:
    • gamma
    • mid_fee
    • out_fee
    • allowed_extra_profit
    • fee_gamma
    • adjustment_step
    • ma_half_time
    • price_scale
    • price_oracle
    • last_prices
    • last_prices_timestamp
  • Update pool_snapshot in network.subgraph to pull and process cryptoswap params
  • Update PoolData and PoolMetaData to properly initialize a crypto pool

@view decorator

Marks pool methods as not altering state.

First simple thing it can do is append to the docstring(__doc__):

Note
----
This is a "view" function; it doesn't change the state of the pool.

Although it should probably look for an existing note section and add to that.

More powerfully, we could check after the method runs that the pre-state is the same as post-state.

Update CI test infra

We've been running the codebase through saved data we compare against pickled results. We should move toward running the codebase against the prior version, whether actual package or specified commit hash.

We can do this by pip install-ing in the CI (from version or hash), running the same data through current codebase and older version, and then comparing the results. For now, we can pull the data but probably in the future we should save the data to avoid issues with data providers.

Handle multiple coins (at least 3 because of tricrypto)

Factory pools are 2 coins but the code is mostly generic enough to handle more, e.g. tricrypto. A few places in the copied vyper factory contract highlights where the n-coin logic was changed to 2 coins. Those should likely be the only changes needed. For testing, we should use two vyper contracts based on n = 2 or 3: 1) existing fixture based on factory cryptopool 2) tricrypto2 fixture

Todo:

  • create tricrypto2 fixture
  • create test suite for n=3 checking against tricrypto fixture
  • update CryptoPool to handle n=3

Properly handle wrong or duplicate symbol

Right now, putting in the wrong pool address or lp token symbol will result in a mysterious index error. We should check the return data for missing pool data. There is also the issue of symbols not being unique, so we need to properly consider how to handle duplicates.

Sim logging does not indicate fixed pool parameters

In [8]: res = curvesim.autosim(mim, chain="mainnet", A=range(500, 1500, 250), fee=4000000)
[INFO][14:39:05][curvesim.price_data.sources]-29571: Fetching CoinGecko price data...
[INFO][14:39:06][curvesim.pool_data.cache]-29571: Fetching historical volume...
[INFO][14:39:06][curvesim.network.subgraph]-29571: Volume end date: 2023-05-18 00:00:00+00:00
[INFO][14:39:06][curvesim.network.subgraph]-29571: Volume end date: 2023-05-18 00:00:00+00:00
[WARNING][14:39:06][curvesim.network.subgraph]-29571: Only 53/60 days of pool volume returned.
[INFO][14:39:07][curvesim.pipelines.utils]-29571: Volume Multipliers: [8.21828766e-07 8.21828766e-07 8.21828766e-07 3.18995732e-05
 3.18995732e-05 3.18995732e-05]
[INFO][14:39:17][curvesim.pipelines.arbitrage]-29648: [MIM-3LP3CRV-f] Simulating with {'A': 500}
[INFO][14:39:17][curvesim.pipelines.arbitrage]-29646: [MIM-3LP3CRV-f] Simulating with {'A': 1000}
[INFO][14:39:18][curvesim.pipelines.arbitrage]-29649: [MIM-3LP3CRV-f] Simulating with {'A': 1250}
[INFO][14:39:18][curvesim.pipelines.arbitrage]-29647: [MIM-3LP3CRV-f] Simulating with {'A': 750}

Logging only shows the variable parameters (A values) not the fixed fee value.

Use Sphinx to generate documentation

As part of our cleanup/refactoring work, we should create good docstrings, including at module level. Sphinx will use this to auto-generate documentation creating cross-links and references to source code. Docstrings should also have simple examples of usage.

We should also have a "quickstart" section in the documentation.

Improve RAI3CRV metrics and tests

Currently, in the volume-limited arbitrage pipeline, pool value and annualized returns ("ar") are computed using the current redemption price at each timestamp. Previously, these were computed holding the redemption price constant at its first value. What is the preferred method?

Also, RAI3CRV does not pass the current tests when run on GH -- the usual "ar" tolerance is exceeded. Normal inter-machine differences in "ar" are likely exacerbated because the recorded pool value can vary "jerkily" with redemption price updates.

Dummy issue to test new sim request workflow params

Instructions:

In the issue description, put any explanation or context above the "Settings" heading, and the parameters for the simulation below it. This sample issue description follow the required format (hopefully this isn't too "meta"!).

Allowed parameters:

  • address (required): the pool address
  • chain (optional): Name of the chain or layer 2.
    Supported values are: "mainnet", "arbitrum", "optimism", "fantom", "avalanche", "matic", "xdai".
    Defaults to "mainnet".
  • A (optional): specified list of A params to simulate, given as a comma-separated list of values
    Default will use a large range of values.
  • fee (optional): specified list of fee values to simulate, given as a comma-separated list of values
  • vol_mult (optional): Modes for limiting trade volume.
    1: limits trade volumes proportionally to market volume for each pair
    2: limits trade volumes equally across pairs
    3: mode 2 for trades with meta-pool asset, mode 1 for basepool-only trades
    Defaults to 1.
  • test (optional): Uses small set of test parameters. Likely you don't want this. Defaults to "false".

Caution: #168 means we can't use vol_mult as described above

[SETTINGS]
address = 0xbebc44782c7db0a1a60cb6fe97d0b483032ff1c7
chain = mainnet
A = 500,600
fee = 500000

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.