Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
nagakingg committed Jan 12, 2024
1 parent be90785 commit 7299c82
Show file tree
Hide file tree
Showing 17 changed files with 315 additions and 232 deletions.
8 changes: 8 additions & 0 deletions curvesim/exceptions/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,3 +77,11 @@ class StateLogError(CurvesimException):

class UnregisteredPoolError(StateLogError):
"""Error raised when a pool type is not recognized by the metrics framework."""


class TimeSequenceError(CurvesimException):
"""Error using a TimeSequence object."""


class DataSourceError(CurvesimException):
"""Error using a DataSource object."""
4 changes: 2 additions & 2 deletions curvesim/iterators/price_samplers/price_volume.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from typing import Iterator

from curvesim.logging import get_logger
from curvesim.price_data import get
from curvesim.price_data import get_price_data
from curvesim.templates.price_samplers import PriceSample, PriceSampler
from curvesim.utils import dataclass, override

Expand Down Expand Up @@ -57,7 +57,7 @@ def __init__(
Identifies pricing source: coingecko or local.
"""
prices, volumes, _ = get(
prices, volumes, _ = get_price_data(
assets.addresses,
chain=assets.chain,
days=days,
Expand Down
101 changes: 5 additions & 96 deletions curvesim/network/coingecko.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,9 @@
"""
# pylint: disable=redefined-outer-name
import asyncio
from datetime import datetime, timedelta, timezone

import numpy as np
import pandas as pd

from curvesim.utils import get_pairs

from .http import HTTP
from .utils import sync

Expand Down Expand Up @@ -40,102 +36,14 @@ async def get_prices(coin_id, vs_currency, start, end):
r = await _get_prices(coin_id, vs_currency, start, end)

# Format data
data = pd.DataFrame(r["prices"], columns=["timestamp", "prices"])
data = data.merge(
pd.DataFrame(r["total_volumes"], columns=["timestamp", "volumes"])
)
data = pd.DataFrame(r["prices"], columns=["timestamp", "price"])
data = data.merge(pd.DataFrame(r["total_volumes"], columns=["timestamp", "volume"]))
data["timestamp"] = pd.to_datetime(data["timestamp"], unit="ms", utc="True")
data = data.set_index("timestamp")

return data


async def _pool_prices(coins, vs_currency, days, end=None):
if end is not None:
# Times to reindex to: daily intervals
# Coingecko only allows daily data when more than 90 days in the past
# for the free REST endpoint
t_end = datetime.fromtimestamp(end, tz=timezone.utc)
t_start = t_end - timedelta(days=days + 1)
t_samples = pd.date_range(start=t_start, end=t_end, freq="1D", tz=timezone.utc)
else:
# Times to reindex to: hourly intervals starting on half hour mark
t_end = datetime.now(timezone.utc) - timedelta(days=1)
t_end = t_end.replace(hour=23, minute=30, second=0, microsecond=0)
t_start = t_end - timedelta(days=days + 1)
t_samples = pd.date_range(start=t_start, end=t_end, freq="60T", tz=timezone.utc)
end = t_end.timestamp()

# Fetch data
tasks = []
for coin in coins:
start = t_start.timestamp() - 86400 * 3
tasks.append(get_prices(coin, vs_currency, start, end))

data = await asyncio.gather(*tasks)

# Format data
qprices = []
qvolumes = []
for d in data:
d.drop(d.tail(1).index, inplace=True) # remove last row
d = d.reindex(t_samples, method="ffill")
qprices.append(d["prices"])
qvolumes.append(d["volumes"])

qprices = pd.concat(qprices, axis=1)
qvolumes = pd.concat(qvolumes, axis=1)
qvolumes = qvolumes / np.array(qprices)

return qprices, qvolumes


def pool_prices(coins, vs_currency, days, chain="mainnet", end=None):
"""
Pull price and volume data for given coins, quoted in given
quote currency for given days.
Parameters
----------
coins: list of str
List of coin addresses.
vs_currency: str
Symbol for quote currency.
days: int
Number of days to pull data for.
Returns
-------
pair of pandas.Series
prices Series and volumes Series
"""
# Get data
coins = coin_ids_from_addresses_sync(coins, chain)
qprices, qvolumes = _pool_prices_sync(coins, vs_currency, days, end)

# Compute prices by coin pairs
combos = get_pairs(len(coins))
prices = []
volumes = []

for pair in combos:
base_price = qprices.iloc[:, pair[0]]
base_volume = qvolumes.iloc[:, pair[0]]

quote_price = qprices.iloc[:, pair[1]]
quote_volume = qvolumes.iloc[:, pair[1]]

# divide prices: (usd/base) / (usd/quote) = quote/base
prices.append(base_price / quote_price)
# sum volumes and convert to base: usd / (usd/base) = base
volumes.append((base_volume + quote_volume) / base_price)

prices = pd.concat(prices, axis=1)
volumes = pd.concat(volumes, axis=1)

return prices, volumes


async def _coin_id_from_address(address, chain):
address = address.lower()
chain = PLATFORMS[chain.lower()]
Expand Down Expand Up @@ -163,11 +71,12 @@ async def coin_ids_from_addresses(addresses, chain):


# Sync
_pool_prices_sync = sync(_pool_prices)
get_prices_sync = sync(get_prices)
coin_ids_from_addresses_sync = sync(coin_ids_from_addresses)


if __name__ == "__main__":
# TODO: update
coin_addresses = [
"0x6B175474E89094C44Da98b954EedeAC495271d0F",
"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
Expand All @@ -179,6 +88,6 @@ async def coin_ids_from_addresses(addresses, chain):

vs_ccy = "USD"
days = 1
prices, volumes = pool_prices(coin_addresses, vs_ccy, days, chain)
prices, volumes = get_prices_sync(coin_addresses, vs_ccy, days, chain)
print(prices.head())
print(volumes.head())
15 changes: 0 additions & 15 deletions curvesim/pool/sim_interface/cryptoswap.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
from math import prod

from curvesim.exceptions import SimPoolError
from curvesim.templates import SimAssets
from curvesim.templates.sim_pool import SimPool
from curvesim.utils import cache, override

Expand Down Expand Up @@ -213,17 +212,3 @@ def prepare_for_run(self, prices):
self.virtual_price = self.get_virtual_price()
self.xcp_profit = 10**18
self.xcp_profit_a = 10**18

@property
@override
@cache
def assets(self):
"""
Return :class:`.SimAssets` object with the properties of the pool's assets.
Returns
-------
SimAssets
SimAssets object that stores the properties of the pool's assets.
"""
return SimAssets(self.coin_names, self.coin_addresses, self.chain)
18 changes: 0 additions & 18 deletions curvesim/pool/sim_interface/metapool.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
from curvesim.exceptions import CurvesimValueError, SimPoolError
from curvesim.templates import SimAssets
from curvesim.templates.sim_pool import SimPool
from curvesim.utils import cache, override

Expand Down Expand Up @@ -214,20 +213,3 @@ def get_min_trade_size(self, coin_in):
The minimal trade size
"""
return 0

@property
@override
@cache
def assets(self):
"""
Return :class:`.SimAssets` object with the properties of the pool's assets.
Returns
-------
SimAssets
SimAssets object that stores the properties of the pool's assets.
"""
symbols = self.coin_names[:-1] + self.basepool.coin_names
addresses = self.coin_addresses[:-1] + self.basepool.coin_addresses

return SimAssets(symbols, addresses, self.chain)
15 changes: 0 additions & 15 deletions curvesim/pool/sim_interface/pool.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
from curvesim.exceptions import SimPoolError
from curvesim.templates import SimAssets
from curvesim.templates.sim_pool import SimPool
from curvesim.utils import cache, override

Expand Down Expand Up @@ -141,17 +140,3 @@ def get_min_trade_size(self, coin_in):
The minimal trade size
"""
return 0

@property
@override
@cache
def assets(self):
"""
Return :class:`.SimAssets` object with the properties of the pool's assets.
Returns
-------
SimAssets
SimAssets object that stores the properties of the pool's assets.
"""
return SimAssets(self.coin_names, self.coin_addresses, self.chain)
3 changes: 2 additions & 1 deletion curvesim/pool_data/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@
and 2-token cryptopools.
"""

__all__ = ["get_metadata", "get_pool_volume"]
__all__ = ["get_metadata", "get_pool_assets", "get_pool_volume"]


from .queries.metadata import get_metadata
from .queries.pool_assets import get_pool_assets
from .queries.pool_volume import get_pool_volume
27 changes: 27 additions & 0 deletions curvesim/pool_data/queries/pool_assets.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from typing import List, Union

from curvesim.constants import Chain
from curvesim.pool_data.metadata import PoolMetaDataInterface
from curvesim.pool_data.queries.metadata import get_metadata
from curvesim.templates.sim_asset import OnChainAssetPair
from curvesim.utils import get_pairs


def get_pool_assets(
metadata_or_address, chain: Union[str, Chain] = Chain.MAINNET
) -> List[OnChainAssetPair]:
if isinstance(metadata_or_address, str):
pool_metadata: PoolMetaDataInterface = get_metadata(metadata_or_address, chain)
else:
pool_metadata = metadata_or_address

symbol_pairs = get_pairs(pool_metadata.coin_names)
address_pairs = get_pairs(pool_metadata.coins)

sim_assets = []
for symbols, addresses in zip(symbol_pairs, address_pairs):
id = "-".join(symbols)
asset = OnChainAssetPair(id, *symbols, *addresses, pool_metadata.chain)
sim_assets.append(asset)

return sim_assets
57 changes: 28 additions & 29 deletions curvesim/price_data/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,38 +7,30 @@
Nomics data is deprecated.
"""


from typing import List

from curvesim.exceptions import NetworkError
from curvesim.templates.sim_asset import SimAsset
from curvesim.templates.time_sequence import TimeSequence

from .sources import coingecko
from .data_sources import CoinGeckoPriceVolumeDataSource


def get(
coins,
chain="mainnet",
*,
days=60,
data_dir="data",
src="coingecko",
end=None,
def get_price_data(
sim_assets: List[SimAsset],
time_sequence: TimeSequence,
data_source="coingecko",
):
"""
Pull price and volume data for given coins.
Pull price and volume data for each sim_asset.
Data is returned for all pairwise combinations of the input coins.
Parameters
----------
coins : list of str
List of coin addresses.
days : int, default=60
Number of days to pull data for.
data_dir : str, default="data"
Directory to load local data from.
sim_assets: List[SimAsset]
src : str, default="coingecko"
Data source ("coingecko", "nomics", or "local").
time_sequence: TimeSequence
Returns
Expand All @@ -49,17 +41,24 @@ def get(
volumes : pandas.DataFrame
Timestamped volumes for each pair of coins.
pzero : int or pandas.Series
Proportion of timestamps with zero volume.
"""
if src == "coingecko":
prices, volumes, pzero = coingecko(coins, chain=chain, days=days, end=end)

elif src == "nomics":
# Todo: replace this logic with SimAssetSeriesFactory

if data_source == "coingecko":
data_source = CoinGeckoPriceVolumeDataSource()

elif data_source == "nomics":
raise NetworkError("Nomics data is no longer supported.")

elif src == "local":
elif data_source == "local":
raise NetworkError("Local data currently not supported.")

return prices, volumes, pzero
prices = []
volumes = []
for sim_asset in sim_assets:
price, volume = data_source.query(sim_asset, time_sequence)
prices.append(price)
volumes.append(volume)

return prices, volumes
5 changes: 5 additions & 0 deletions curvesim/price_data/data_sources/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
__all__ = ["CoinGeckoPriceVolumeDataSource", "FileDataSource"]

from curvesim.templates.data_source import FileDataSource

from .coingecko import CoinGeckoPriceVolumeDataSource
Loading

0 comments on commit 7299c82

Please sign in to comment.