Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Jupyter free installation #180

Merged
merged 7 commits into from
Oct 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 5 additions & 18 deletions .github/workflows/no-optional-deps.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
name: Automated test suite wo/optional deps
# Make sure we can install Jupyter notebook free environments
name: Standalone install wo/optional deps test

on:
push:
Expand All @@ -18,22 +19,8 @@ jobs:
uses: actions/setup-python@v4
with:
python-version: "3.12"
- name: Install Poetry
uses: snok/install-poetry@v1
with:
virtualenvs-create: true
virtualenvs-in-project: true
installer-parallel: true
- name: Load cached venv
id: cached-poetry-dependencies
uses: actions/cache@v2
with:
path: .venv
key: venv-${{ runner.os }}-${{ steps.setup-python.outputs.python-version }}-${{ hashFiles('**/poetry.lock') }}
- name: Install dependencies
if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true'
run: |
poetry install --no-interaction
- name: Run test scripts
run: |
poetry run pytest -k test_no_opt_deps
bash scripts/standalone-test.sh
env:
TRADING_STRATEGY_API_KEY: ${{ secrets.TRADING_STRATEGY_API_KEY }}
5 changes: 3 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Current
# 0.24.3

- TODO
- Fix: Allow to run without Jupyter notebook/IPython installed. Make sure you use `Client.create_live_client()` instead of `Client.create_jupyter_client()`
- Fix: Python version pindown `<3.13` instead of `<=3.12` as the pip did not allow minor versions like `3.12.7`

# 0.24.2

Expand Down
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "trading-strategy"
version = "0.24.1"
version = "0.24.3"
description = "Algorithmic trading data for cryptocurrencies and DEXes like Uniswap, Aave and PancakeSwap"
authors = ["Mikko Ohtamaa <[email protected]>"]
homepage = "https://tradingstrategy.ai"
Expand Down Expand Up @@ -34,7 +34,7 @@ Sponsor = "https://tradingstrategy.ai"
[tool.poetry.dependencies]
# Web3.py dependency compatibility
# E.g. ethpm
python = ">=3.10,<=3.12"
python = ">=3.10,<3.13"
dataclasses-json = "^0.5.4"
pandas = "<3"
# numpy.dtype size changed, may indicate binary incompatibility. Expected 96 from C header, got 88 from PyObject
Expand Down
29 changes: 29 additions & 0 deletions scripts/standalone-test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#!/bin/bash
#
# See we can run without extra deps installed
#

if [[ -n "$VIRTUAL_ENV" ]]; then
echo "Error: A virtual environment is active. Cannot run."
exit 1
fi

set -e
set -u

CURRENT_PATH=`pwd`
TEST_SCRIPT=$CURRENT_PATH/tests/standalone-check.py
TEST_PATH=$(mktemp -d)

cd $TEST_PATH

python -m venv venv
source venv/bin/activate
pip install $CURRENT_PATH

python $TEST_SCRIPT

echo "All done"



51 changes: 51 additions & 0 deletions tests/standalone-check.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
"""A script to check that the package works with normal pip install without optional dependencies.

- Will catch any import errors

See scripts/standalone-test.sh
"""

print("Starting standalone check")

import os

import pandas as pd

from tradingstrategy.chain import ChainId
from tradingstrategy.client import Client
from tradingstrategy.pair import PandasPairUniverse
from tradingstrategy.timebucket import TimeBucket

try:
import IPython
raise RuntimeError("IPython must not be installed for this script to run")
except ImportError:
pass

# Disable the settings file.
# API key must be given in an environment variable.
client = Client.create_live_client(
settings_path=None,
api_key=os.environ["TRADING_STRATEGY_API_KEY"],
)
# Load pairs in all exchange
exchange_universe = client.fetch_exchange_universe()
pairs_df = client.fetch_pair_universe().to_pandas()

pair_universe = PandasPairUniverse(pairs_df, exchange_universe=exchange_universe)

pair_ids = [
pair_universe.get_pair_by_human_description([ChainId.ethereum, "uniswap-v3", "WETH", "USDC", 0.0005]).pair_id,
]

start = pd.Timestamp.utcnow() - pd.Timedelta("3d")
end = pd.Timestamp.utcnow()

clmm_df = client.fetch_clmm_liquidity_provision_candles_by_pair_ids(
pair_ids,
TimeBucket.d1,
start_time=start,
end_time=end,
)

assert len(clmm_df) > 0
7 changes: 3 additions & 4 deletions tests/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,10 @@

import os
import logging
from pathlib import Path

import pytest

from tradingstrategy.environment.jupyter import JupyterEnvironment, SettingsDisabled
from tradingstrategy.environment.default_environment import DefaultClientEnvironment, SettingsDisabled
from tradingstrategy.timebucket import TimeBucket
from tradingstrategy.client import Client
from tradingstrategy.chain import ChainId
Expand Down Expand Up @@ -134,7 +133,7 @@ def test_client_convert_all_pairs_to_pandas(client: Client, cache_path: str):

def test_create_pyodide_client_detect():
"""Test the special client used in Pyodide which use HTTP referral authentication."""
env = JupyterEnvironment()
env = DefaultClientEnvironment()
env.clear_configuration()
client = Client.create_jupyter_client(pyodide=True)

Expand Down Expand Up @@ -162,6 +161,6 @@ def test_settings_disabled():

client = Client.create_live_client(api_key=api_key, settings_path=None)
env = client.env
assert isinstance(env, JupyterEnvironment)
assert isinstance(env, DefaultClientEnvironment)
with pytest.raises(SettingsDisabled):
env.setup_on_demand()
78 changes: 62 additions & 16 deletions tradingstrategy/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@
import pandas as pd

from tradingstrategy.candle import TradingPairDataAvailability
from tradingstrategy.environment.default_environment import DefaultClientEnvironment, DEFAULT_SETTINGS_PATH
from tradingstrategy.reader import BrokenData, read_parquet
from tradingstrategy.top import TopPairsReply
from tradingstrategy.transport.pyodide import PYODIDE_API_KEY
from tradingstrategy.types import PrimaryKey, AnyTimestamp
from tradingstrategy.utils.jupyter import is_pyodide
from tradingstrategy.lending import LendingReserveUniverse, LendingCandleType, LendingCandleResult

# TODO: Must be here because warnings are very inconveniently triggered import time
Expand All @@ -50,10 +50,7 @@
from tradingstrategy.chain import ChainId
from tradingstrategy.environment.base import Environment, download_with_progress_plain
from tradingstrategy.environment.config import Configuration
from tradingstrategy.environment.jupyter import (
JupyterEnvironment,
download_with_tqdm_progress_bar, DEFAULT_SETTINGS_PATH,
)

from tradingstrategy.exchange import ExchangeUniverse
from tradingstrategy.timebucket import TimeBucket
from tradingstrategy.transport.cache import CachedHTTPTransport, DataNotAvailable
Expand Down Expand Up @@ -857,16 +854,21 @@ async def create_pyodide_client_async(cls,
return cls.create_jupyter_client(cache_path, api_key, pyodide=True)

@classmethod
def create_jupyter_client(cls,
cache_path: Optional[str] = None,
api_key: Optional[str] = None,
pyodide=None,
settings_path=DEFAULT_SETTINGS_PATH,
) -> "Client":
def create_jupyter_client(
cls,
cache_path: Optional[str] = None,
api_key: Optional[str] = None,
pyodide=None,
settings_path=DEFAULT_SETTINGS_PATH,
) -> "Client":
"""Create a new API client.

This function is intended to be used from Jupyter notebooks

.. note ::

Only use within Jupyter Notebook environments. Otherwise use :py:meth:`create_live_client`.

- Any local or server-side IPython session

- JupyterLite notebooks
Expand All @@ -890,11 +892,17 @@ def create_jupyter_client(cls,

"""

from tradingstrategy.transport.progress_enabled_download import download_with_tqdm_progress_bar

if pyodide is None:
pyodide = is_pyodide()
try:
from tradingstrategy.utils.jupyter import is_pyodide
pyodide = is_pyodide()
except ImportError:
pyodide = False

cls.preflight_check()
env = JupyterEnvironment(settings_path=settings_path)
env = DefaultClientEnvironment(settings_path=settings_path)

# Try Pyodide default key
if not api_key:
Expand All @@ -913,6 +921,7 @@ def create_jupyter_client(cls,
api_key = config.api_key

cache_path = cache_path or env.get_cache_path()

transport = CachedHTTPTransport(
download_with_tqdm_progress_bar,
cache_path=cache_path,
Expand All @@ -929,6 +938,7 @@ def create_test_client(cls, cache_path=None) -> "Client":
By default, the test client caches data under `/tmp` folder.
Tests do not clear this folder between test runs, to make tests faster.
"""

if cache_path:
os.makedirs(cache_path, exist_ok=True)
else:
Expand All @@ -937,7 +947,7 @@ def create_test_client(cls, cache_path=None) -> "Client":
api_key = os.environ.get("TRADING_STRATEGY_API_KEY")
assert api_key, "Unit test data client cannot be created without TRADING_STRATEGY_API_KEY env"

env = JupyterEnvironment(cache_path=cache_path, settings_path=None)
env = DefaultClientEnvironment(cache_path=cache_path, settings_path=None)
config = Configuration(api_key=api_key)
transport = CachedHTTPTransport(download_with_progress_plain, "https://tradingstrategy.ai/api", api_key=config.api_key, cache_path=env.get_cache_path(), timeout=15)
return Client(env, transport)
Expand All @@ -953,7 +963,43 @@ def create_live_client(

- The live client is non-interactive and logs using Python logger

- No interactive progress bars are set up
- If you want to run inside notebook, use :py:meth:`create_jupyter_client` instead

Example:

.. code-block:: python

from tradingstrategy.chain import ChainId
from tradingstrategy.client import Client
from tradingstrategy.pair import PandasPairUniverse
from tradingstrategy.timebucket import TimeBucket

# Disable the settings file.
# API key must be given in an environment variable.
client = Client.create_live_client(
settings_path=None,
api_key=os.environ["TRADING_STRATEGY_API_KEY"],
)
# Load pairs in all exchange
exchange_universe = client.fetch_exchange_universe()
pairs_df = client.fetch_pair_universe().to_pandas()

pair_universe = PandasPairUniverse(pairs_df, exchange_universe=exchange_universe)

pair_ids = [
pair_universe.get_pair_by_human_description([ChainId.ethereum, "uniswap-v3", "WETH", "USDC", 0.0005]).pair_id,
]

start = pd.Timestamp.utcnow() - pd.Timedelta("3d")
end = pd.Timestamp.utcnow()

# Download some data
clmm_df = client.fetch_clmm_liquidity_provision_candles_by_pair_ids(
pair_ids,
TimeBucket.d1,
start_time=start,
end_time=end,
)

:param api_key:
Trading Strategy oracle API key, starts with `secret-token:tradingstrategy-...`
Expand All @@ -972,7 +1018,7 @@ def create_live_client(
if settings_path is None:
assert api_key, "Either API key or settings file must be given"

env = JupyterEnvironment(settings_path=settings_path)
env = DefaultClientEnvironment(settings_path=settings_path)
if cache_path:
cache_path = cache_path.as_posix()
else:
Expand Down
4 changes: 2 additions & 2 deletions tradingstrategy/environment/colab.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
from tradingstrategy.environment.base import Environment
from tradingstrategy.environment.jupyter import JupyterEnvironment
from tradingstrategy.environment.jupyter import DefaultClientEnvironment


class ColabEnvironment(JupyterEnvironment):
class ColabEnvironment(DefaultClientEnvironment):

def start(self):
pass
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import functools
import logging
import os
import platform
Expand Down Expand Up @@ -30,20 +29,26 @@
DEFAULT_SETTINGS_PATH = Path(os.path.expanduser("~/.tradingstrategy"))



class SettingsDisabled(Exception):
"""Raised when the user tries to create/read settings file when it is purposefully disabled.

Docker environments.
"""


class JupyterEnvironment(Environment):
"""Define paths and setup processes when using Capitalgram from any local Jupyter Notebook installation"""
class DefaultClientEnvironment(Environment):
"""Sets up an application cache and settings.

- Use default `~/.cache` and `~/.tradingstrategy/settings.json` storage in your home folder.

- Locations can be overwritten e.g. in unit testes to temp paths
"""

def __init__(
self,
cache_path=None,
settings_path: Path | None = DEFAULT_SETTINGS_PATH,
settings_path=DEFAULT_SETTINGS_PATH,
):
"""CReate environment.

Expand Down
Loading