Skip to content

Commit

Permalink
migrated towards uv
Browse files Browse the repository at this point in the history
  • Loading branch information
jbaron committed Dec 15, 2024
1 parent 4026be8 commit 08911a0
Show file tree
Hide file tree
Showing 16 changed files with 1,359 additions and 109 deletions.
7 changes: 0 additions & 7 deletions .flake8

This file was deleted.

2 changes: 1 addition & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
"python.testing.pytestEnabled": false,
"python.testing.unittestEnabled": true,
"[python]": {
"editor.defaultFormatter": "ms-python.black-formatter"
"editor.defaultFormatter": "charliermarsh.ruff"
},
"pylint.ignorePatterns": [
"*samples/*.py"
Expand Down
27 changes: 14 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,44 +40,45 @@ The core of roboquant limits the number of dependencies.
But you can install roboquant including one or more of the optional dependencies if you require certain functionality:

```shell
# market data from Yahoo Finance using the YahooFeed
python3 -m pip install --upgrade "roboquant[yahoo]"

# PyTorch based strategies using RNNStrategy
python3 -m pip install --upgrade "roboquant[torch]"

# Integration with Interactive Brokers using IBKRBroker
python3 -m pip install --upgrade "roboquant[ibkr]"

# Integration with Alpaca
python3 -m pip install --upgrade "roboquant[alpaca]"

# Install all dependencies
python3 -m pip install --upgrade "roboquant[all]"
```

## Building from source
Although this first step isn't required, it is recommended to create a virtual environment.
Go to the directory where you have downloaded the source code and run the following commands:
Roboquant.py uses `uv` as the main tool for handling package dependencies.


```shell
python3 -m venv .venv
source .venv/bin/activate
uv sync
```

You should now be in the virtual environment and ready to install the required packages and build/install roboquant:
You should now be in the virtual environment and ready to build/install roboquant:

```shell
pip install -r requirements.txt
python -m build
pip install .
uv build
uv pip install
```

Some other useful commands:

```shell
# run the unit tests
python -m unittest discover -s tests/unit
uv run python -m unittest discover -s tests/unit

# validate the code
flake8 roboquant tests
uvx ruff check

# publish, only works if UV_PUBLISH_TOKEN is set
uv publish
```

## License
Expand Down
14 changes: 7 additions & 7 deletions bin/local_install.sh
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@

[[ ! -f "LICENSE" ]] && echo "run the script from the project root directory like this: ./bin/local_install.sh" && exit 1

source .venv/bin/activate

rm -rf ./runs

uv sync --all-extras

# QA
flake8 roboquant tests || exit 1
pylint roboquant tests || exit 1
python -m unittest discover -s tests/unit || exit 1
uvx ruff check || exit 1
uv run python -m unittest discover -s tests/unit || exit 1

# Build
rm -rf dist
python -m build || exit 1
uv build || exit 1

# Install
pip install .
uv pip install .

13 changes: 6 additions & 7 deletions bin/publish.sh
Original file line number Diff line number Diff line change
@@ -1,23 +1,22 @@

[[ ! -f "LICENSE" ]] && echo "run the script from the project root directory like this: ./bin/publish.sh" && exit 1

source .venv/bin/activate

rm -rf ./runs

uv sync --all-extras

# QA
flake8 || exit 1
pylint roboquant tests || exit 1
python -m unittest discover -s tests/unit || exit 1
uvx ruff check
uv run python -m unittest discover -s tests/unit || exit 1

# Build
rm -rf dist
python -m build || exit 1
uv build || exit 1

# Publish
read -p "Publish (y/n)? " ANSWER
if [ "$ANSWER" = "y" ]; then
twine upload dist/*; exit 0
uv publish; exit 0
else
echo "Not published"; exit 1
fi
10 changes: 5 additions & 5 deletions bin/verify.sh
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@

[[ ! -f "LICENSE" ]] && echo "run the script from the project root directory like this: ./bin/verify.sh" && exit 1

source .venv/bin/activate
uv sync --all-extras

# QA
flake8 || exit 1
pylint roboquant tests || exit 1
python -m unittest discover -s tests/unit || exit 1
echo "Running ruff"
uvx ruff check || exit 1
echo "Running unittest"
uv run python -m unittest discover -s tests/unit || exit 1

echo "All tests passed"
41 changes: 16 additions & 25 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
[tool.black]
[tool.ruff]
line-length = 127

[tool.pytest.ini_options]
Expand All @@ -8,25 +8,18 @@ testpaths = [
"tests/unit"
]

[tool.pyright]
reportOptionalOperand = "none"

[tool.pylint.'MESSAGES CONTROL']
max-line-length = 127
disable = "too-few-public-methods,missing-module-docstring,missing-class-docstring,missing-function-docstring,unnecessary-ellipsis,invalid-name"
max-args = 15
max-locals = 20
max-attributes = 10

[build-system]
requires = ["setuptools>=61.0"]
build-backend = "setuptools.build_meta"
requires = ["hatchling"]
build-backend = "hatchling.build"

[tool.hatch.version]
path = "roboquant/__init__.py"

[tool.setuptools.packages.find]
exclude = ["docs*", "tests*", "samples*", "scratch*"]
[tool.ruff.lint]
ignore = ["F401"]

[tool.setuptools.package-data]
"*" = ["*.json"]
[tool.pyright]
reportOptionalOperand = "none"

[project]
name = "roboquant"
Expand All @@ -53,21 +46,19 @@ dependencies = [
"numpy>=1.26.4",
"websocket-client~=1.8.0",
"requests>=2.32.0",
"fastavro>=1.9.7",
"pyarrow>=16.1.0",
"matplotlib>=3.10.0",
"yfinance~=0.2.50"
]

[tool.setuptools.dynamic]
version = {attr = "roboquant.__version__"}

[project.optional-dependencies]
torch = ["torch>=2.5.0", "tensorboard>=2.15.2", "stable-baselines3[extra_no_roms]>=2.3.2", "sb3-contrib>=2.3.0"]
yahoo = ["yfinance~=0.2.50"]
torch = ["torch>=2.5.0", "tensorboard>=2.15.2", "stable-baselines3>=2.4.0", "sb3-contrib>=2.4.0"]
ibkr = ["nautilus-ibapi~=10.19.2"]
alpaca = ["alpaca-py"]
all = ["roboquant[torch,yahoo,ibkr,alpaca]"]

all = ["roboquant[torch,ibkr,alpaca]"]

[project.urls]
Homepage = "https://roboquant.org"
Repository = "https://github.com/neurallayer/roboquant.py.git"
Issues = "https://github.com/neurallayer/roboquant.py/issues"

24 changes: 0 additions & 24 deletions requirements.txt

This file was deleted.

23 changes: 17 additions & 6 deletions roboquant/account.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,7 @@ def base_currency(self) -> Currency:
return self.buying_power.currency

def mkt_value(self) -> Wallet:
"""Return the sum of the market values of the open positions in the account.
"""
"""Return the sum of the market values of the open positions in the account."""
result = Wallet()
for asset, position in self.positions.items():
result += asset.contract_amount(position.size, position.mkt_price)
Expand All @@ -78,15 +77,25 @@ def position_value(self, asset: Asset) -> float:

def short_positions(self) -> dict[Asset, Position]:
"""Return al the short positions in the account"""
return {asset: position for (asset, position) in self.positions.items() if position.is_short}
return {
asset: position
for (asset, position) in self.positions.items()
if position.is_short
}

def long_positions(self) -> dict[Asset, Position]:
"""Return al the long positions in the account"""
return {asset: position for (asset, position) in self.positions.items() if position.is_long}
return {
asset: position
for (asset, position) in self.positions.items()
if position.is_long
}

def contract_value(self, asset: Asset, size: Decimal, price: float) -> float:
"""Contract value denoted in the base currency of the account"""
return asset.contract_amount(size, price).convert_to(self.base_currency, self.last_update)
return asset.contract_amount(size, price).convert_to(
self.base_currency, self.last_update
)

def equity(self) -> Wallet:
"""Return the equity of the account.
Expand All @@ -107,7 +116,9 @@ def unrealized_pnl(self) -> Wallet:
"""
result = Wallet()
for asset, position in self.positions.items():
result += asset.contract_amount(position.size, position.mkt_price - position.avg_price)
result += asset.contract_amount(
position.size, position.mkt_price - position.avg_price
)
return result

def required_buying_power(self, order: Order) -> Amount:
Expand Down
5 changes: 4 additions & 1 deletion roboquant/asset.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,13 +58,15 @@ def deserialize(value: str) -> "Asset":

@dataclass(frozen=True, slots=True)
class Stock(Asset):
"""Stock (or equity) asset"""

def serialize(self):
return "Stock" + ":" + self.symbol + ":" + self.currency


@dataclass(frozen=True, slots=True)
class Crypto(Asset):
"""Crypto-currency asset"""

symbol: str # type: ignore
currency: Currency # type: ignore
Expand All @@ -77,14 +79,15 @@ def from_symbol(symbol: str, sep="/"):

@dataclass(frozen=True, slots=True)
class Option(Asset):
"""Option Contract asset"""

multiplier = 100

def contract_value(self, size: Decimal, price: float) -> float:
return float(size) * price * self.multiplier


def __default_deserializer(clazz: Type[Asset]):

__cache: dict[str, Asset] = {}

def _deserialize(value: str) -> Asset:
Expand Down
7 changes: 7 additions & 0 deletions roboquant/feeds/historic.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,13 @@ def assets(self) -> list[Asset]:
"""Return the list of unique symbols available in this feed"""
self._update()
return list(self.__assets)

def get_asset(self, symbol: str) -> Asset | None:
"""Return the first asset that matches the provided symbol name, or None if not found"""
try:
return next(asset for asset in self.assets() if asset.symbol == symbol)
except StopIteration:
return None

def timeline(self) -> list[datetime]:
"""Return the timeline of this feed as a list of datatime objects"""
Expand Down
12 changes: 8 additions & 4 deletions roboquant/feeds/randomwalk.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,14 @@ def __init__(
timeline = [start_date + frequency * i for i in range(n_prices)]

match price_type:
case "bar": item_gen = self.__get_bar
case "trade": item_gen = self.__get_trade
case "quote": item_gen = self.__get_quote
case _: raise ValueError("unsupported item_type", price_type)
case "bar":
item_gen = self.__get_bar
case "trade":
item_gen = self.__get_trade
case "quote":
item_gen = self.__get_quote
case _:
raise ValueError("unsupported item_type", price_type)

for asset in assets:
prices = self.__price_path(rnd, n_prices, price_dev, start_price_min, start_price_max)
Expand Down
2 changes: 1 addition & 1 deletion roboquant/strategies/tastrategy.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ class TaStrategy(Strategy):
based on technical indicators using a history of bars/candlesticks.
Subclasses should implement the `process_asset` method. This method is only invoked once
there is at least `size` history for an individual asset.
there is at least `size` history for an individual asset available.
"""

def __init__(self, size: int) -> None:
Expand Down
1 change: 0 additions & 1 deletion tests/unit/test_buffer.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@


class TestBuffer(unittest.TestCase):

def test_buffer(self):
b = NumpyBuffer(10, 5)
x = np.arange(100).reshape(20, 5)
Expand Down
Loading

0 comments on commit 08911a0

Please sign in to comment.