From 5c94f2b15f146447b94e207c5932831e3a521ac5 Mon Sep 17 00:00:00 2001 From: Rudy Osuna Date: Wed, 3 Jun 2026 09:06:39 -0700 Subject: [PATCH 1/2] coingecko notebook --- .../research.ipynb | 179 ++++++++++++++++++ 1 file changed, 179 insertions(+) create mode 100644 project-templates/python/alternative-data-universe-coingeckouniverse/research.ipynb diff --git a/project-templates/python/alternative-data-universe-coingeckouniverse/research.ipynb b/project-templates/python/alternative-data-universe-coingeckouniverse/research.ipynb new file mode 100644 index 0000000000..e556e8994a --- /dev/null +++ b/project-templates/python/alternative-data-universe-coingeckouniverse/research.ipynb @@ -0,0 +1,179 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "a1b2c3d4", + "metadata": {}, + "source": [ + "![QuantConnect Logo](https://cdn.quantconnect.com/web/i/icon.png)\n", + "
" + ] + }, + { + "cell_type": "markdown", + "id": "e5f6a7b8", + "metadata": {}, + "source": [ + "## CoinGecko Market Cap Research\n", + "\n", + "This notebook studies whether cryptocurrency market cap helps explain future returns" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c9d0e1f2", + "metadata": {}, + "outputs": [], + "source": [ + "qb = QuantBook()\n", + "# Anchor the research clock to the start of 2026 for a reproducible history window.\n", + "qb.set_start_date(2026, 1, 1)\n", + "# Daily bars will have an end_time that matches the following midnight.\n", + "qb.settings.daily_precise_end_time = False\n", + "# Trade crypto in USD on Coinbase.\n", + "qb.set_account_currency(\"USD\")\n", + "market = Market.COINBASE\n", + "# Collect the Coinbase pairs quoted in the account currency.\n", + "market_pairs = [\n", + " x.key.symbol\n", + " for x in qb.symbol_properties_database.get_symbol_properties_list(market)\n", + " if x.value.quote_currency == qb.account_currency\n", + "]" + ] + }, + { + "cell_type": "markdown", + "id": "a3b4c5d6", + "metadata": {}, + "source": [ + "### Build a CoinGecko Universe\n", + "\n", + "Select the 10 largest Coinbase USD coins by market cap, then inspect the returned universe history." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e7f8a9b0", + "metadata": {}, + "outputs": [], + "source": [ + "def select_assets(data: List[CoinGecko]) -> List[Symbol]:\n", + " # Filter coins that are quoted in the account currency and available on the market.\n", + " tradable = [d for d in data if d.coin and (d.coin + qb.account_currency) in market_pairs]\n", + " # Sort by market cap and return the top 10 as tradable Symbols.\n", + " return [f.create_symbol(market, qb.account_currency)\n", + " for f in sorted(tradable, key=lambda f: f.market_cap)[-10:]]\n", + "\n", + "# Add the CoinGecko universe.\n", + "universe = qb.add_universe(CoinGeckoUniverse, \"CoinGeckoUniverse\", Resolution.DAILY, select_assets)\n", + "# Request universe history of the last 90 days.\n", + "universe_history = qb.universe_history(universe, qb.time - timedelta(90), qb.time - timedelta(1), flatten=True).rename_axis(['time', 'symbol']).drop(columns=['time'])\n", + "# Print the returned shape and columns.\n", + "print(f\"Shape: {universe_history.shape}\")\n", + "print(f\"Columns: {list(universe_history.columns)}\")\n", + "universe_history.head()" + ] + }, + { + "cell_type": "markdown", + "id": "c1d2e3f4", + "metadata": {}, + "source": [ + "### Universe Diagnostics\n", + "\n", + "Inspects the raw market-cap distribution and visualizes how the unique asset footprint expands chronologically." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a5b6c7d8", + "metadata": {}, + "outputs": [], + "source": [ + "# Count selected assets by day.\n", + "universe_size = universe_history.reset_index().groupby(['time', 'symbol']).size().groupby('time').size()\n", + "print(f\"Universe days: {len(universe_size)}\")\n", + "# Store the selected symbol list.\n", + "unique_assets = list(universe_history.index.levels[1].unique())\n", + "print(f\"Mean basket size per day: {universe_size.mean():.1f}\")\n", + "print('')\n", + "print(universe_history.marketcap.describe().map('{:0.3f}'.format))\n", + "universe_size.plot(title='Daily Universe Size', ylabel='Universe Size');" + ] + }, + { + "cell_type": "markdown", + "id": "e9f0a1b2", + "metadata": {}, + "source": [ + "### Daily Universe Prices\n", + "\n", + "Fetch daily price history for every symbol that appears in the universe." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c3d4e5f6", + "metadata": {}, + "outputs": [], + "source": [ + "# Extract unique assets\n", + "symbols = list(universe_history.index.get_level_values(1).unique())\n", + "# Fetch daily historical price metrics using the earliest timestamp available in the index.\n", + "history = qb.history(symbols, universe_history.index[0][0] - timedelta(1), qb.time, Resolution.DAILY)\n", + "history" + ] + }, + { + "cell_type": "markdown", + "id": "a7b8c9d0", + "metadata": {}, + "source": [ + "### Align Market Cap And Returns\n", + "\n", + "Build a joined table of market cap and future returns." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3d56c7cd", + "metadata": {}, + "outputs": [], + "source": [ + "# Align the factor with the return from the next open to the following open.\n", + "dataset = (\n", + " universe_history.reset_index().groupby(['time', 'symbol']).agg(marketcap=('marketcap', 'max'))\n", + " .join(history.open.unstack('symbol').sort_index().pct_change(2, fill_method=None).shift(-2).stack().rename('futurereturn'), how='inner')\n", + ")\n", + "\n", + "dataset.head()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Foundation-Py-Default", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.14" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} From 4149f5508bcb9d9aa8349baab677accd015f76d3 Mon Sep 17 00:00:00 2001 From: Rudy Osuna Date: Wed, 3 Jun 2026 13:27:29 -0700 Subject: [PATCH 2/2] Use universe wording and simplify diagnostics description --- .../research.ipynb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/project-templates/python/alternative-data-universe-coingeckouniverse/research.ipynb b/project-templates/python/alternative-data-universe-coingeckouniverse/research.ipynb index e556e8994a..c77984f27e 100644 --- a/project-templates/python/alternative-data-universe-coingeckouniverse/research.ipynb +++ b/project-templates/python/alternative-data-universe-coingeckouniverse/research.ipynb @@ -83,7 +83,7 @@ "source": [ "### Universe Diagnostics\n", "\n", - "Inspects the raw market-cap distribution and visualizes how the unique asset footprint expands chronologically." + "Check how many assets pass the filter each day and summarize the factors." ] }, { @@ -98,7 +98,7 @@ "print(f\"Universe days: {len(universe_size)}\")\n", "# Store the selected symbol list.\n", "unique_assets = list(universe_history.index.levels[1].unique())\n", - "print(f\"Mean basket size per day: {universe_size.mean():.1f}\")\n", + "print(f\"Mean universe size per day: {universe_size.mean():.1f}\")\n", "print('')\n", "print(universe_history.marketcap.describe().map('{:0.3f}'.format))\n", "universe_size.plot(title='Daily Universe Size', ylabel='Universe Size');"