-
Notifications
You must be signed in to change notification settings - Fork 10
Build initial prediction engine service #559
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
Changes from all commits
3b76b59
ff4ad48
8d83bf9
4ca456e
48fac02
e9f6982
e112e39
62f443b
1dc1c4e
dc025bc
502f22a
1c3ed7e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| .venv/ | ||
| **/__pycache__/ |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,9 +1,33 @@ | ||
| FROM python:3.13 | ||
|
|
||
| COPY --from=ghcr.io/astral-sh/uv:latest /uv /bin/uv | ||
|
|
||
| RUN apt-get update && apt-get install -y \ | ||
| build-essential \ | ||
| clang \ | ||
| libc6-dev \ | ||
| linux-libc-dev \ | ||
| && rm -rf /var/lib/apt/lists/* | ||
|
|
||
| ENV PYTEST_ADDOPTS="--rootdir=/tests" | ||
| ENV PYTHON=1 | ||
|
|
||
| WORKDIR /tests | ||
|
|
||
| COPY pyproject.toml uv.lock . | ||
| RUN uv sync --all-groups | ||
| COPY pyproject.toml uv.lock ./ | ||
|
|
||
| COPY application/datamanager/pyproject.toml ./application/datamanager/ | ||
|
|
||
| COPY application/positionmanager/pyproject.toml ./application/positionmanager/ | ||
|
|
||
| COPY application/predictionengine/pyproject.toml ./application/predictionengine/ | ||
|
|
||
| COPY infrastructure/pyproject.toml ./infrastructure/ | ||
|
|
||
| COPY workflows/pyproject.toml ./workflows/ | ||
|
|
||
| RUN uv sync --all-packages --dev | ||
|
|
||
| COPY application/ ./application/ | ||
|
|
||
| RUN mkdir -p /tests/coverage_output |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,14 +5,14 @@ | |
|
|
||
|
|
||
| class Polygon(BaseModel): | ||
| api_key: str | None = Field(default=os.getenv("POLYGON_API_KEY")) | ||
| api_key: str = Field(default=os.getenv("POLYGON_API_KEY", "")) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Consider validation for required configuration fields. While defaulting to empty strings provides type consistency, it may mask configuration errors. Consider adding validation to ensure critical fields like API keys and project identifiers are properly configured. Also note the inconsistency: class GCP(BaseModel):
bucket: Bucket = Bucket()
- credentials_path: str = os.getenv("GOOGLE_APPLICATION_CREDENTIALS", "")
+ credentials_path: str = Field(default=os.getenv("GOOGLE_APPLICATION_CREDENTIALS", ""))Consider adding validation to ensure required fields are non-empty: from pydantic import BaseModel, Field, field_validator
class Polygon(BaseModel):
api_key: str = Field(default=os.getenv("POLYGON_API_KEY", ""))
@field_validator('api_key')
@classmethod
def validate_api_key(cls, v):
if not v:
raise ValueError('POLYGON_API_KEY environment variable is required')
return vAlso applies to: 14-15 🤖 Prompt for AI Agents |
||
| base_url: str = "https://api.polygon.io" | ||
| daily_bars: str = "/v2/aggs/grouped/locale/us/market/stocks/" | ||
|
|
||
|
|
||
| class Bucket(BaseModel): | ||
| name: str | None = Field(default=os.getenv("DATA_BUCKET")) | ||
| project: str | None = Field(default=os.getenv("GCP_PROJECT")) | ||
| name: str = Field(default=os.getenv("DATA_BUCKET", "")) | ||
| project: str = Field(default=os.getenv("GCP_PROJECT", "")) | ||
|
|
||
| @computed_field | ||
| def daily_bars_path(self) -> str: | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,11 +3,9 @@ | |
| from datetime import datetime, timedelta | ||
| import polars as pl | ||
| from typing import Dict, Any | ||
|
|
||
| from .models import Money, DateRange, PredictionPayload | ||
| from .clients import AlpacaClient, DataClient | ||
| from .portfolio import PortfolioOptimizer | ||
|
|
||
| from prometheus_fastapi_instrumentator import Instrumentator | ||
|
|
||
|
|
||
|
|
@@ -25,12 +23,12 @@ def get_health() -> dict[str, str]: | |
| @application.post("/positions") | ||
| def create_position(payload: PredictionPayload) -> Dict[str, Any]: | ||
| alpaca_client = AlpacaClient( | ||
| api_key=os.getenv("ALPACA_API_KEY"), | ||
| api_secret=os.getenv("ALPACA_API_SECRET"), | ||
| api_key=os.getenv("ALPACA_API_KEY", ""), | ||
| api_secret=os.getenv("ALPACA_API_SECRET", ""), | ||
|
Comment on lines
+26
to
+27
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Avoid masking configuration errors with empty string defaults. Providing empty string defaults for critical configuration like API keys and service URLs masks configuration errors instead of failing fast. The Consider these better approaches: Option 1: Validate at startup +def validate_config():
+ required_vars = ["ALPACA_API_KEY", "ALPACA_API_SECRET", "DATAMANAGER_BASE_URL"]
+ missing = [var for var in required_vars if not os.getenv(var)]
+ if missing:
+ raise ValueError(f"Missing required environment variables: {missing}")
+
+@application.on_event("startup")
+async def startup_event():
+ validate_config()Option 2: Keep None and handle explicitly - api_key=os.getenv("ALPACA_API_KEY", ""),
- api_secret=os.getenv("ALPACA_API_SECRET", ""),
+ api_key=os.getenv("ALPACA_API_KEY") or raise ValueError("ALPACA_API_KEY not set"),
+ api_secret=os.getenv("ALPACA_API_SECRET") or raise ValueError("ALPACA_API_SECRET not set"),Also applies to: 31-31 🤖 Prompt for AI Agents |
||
| paper=os.getenv("ALPACA_PAPER", "true").lower() == "true", | ||
| ) | ||
|
|
||
| data_client = DataClient(datamanager_base_url=os.getenv("DATAMANAGER_BASE_URL")) | ||
| data_client = DataClient(datamanager_base_url=os.getenv("DATAMANAGER_BASE_URL", "")) | ||
|
|
||
| portfolio_optimizer = PortfolioOptimizer( | ||
| minimum_portfolio_tickers=int(os.getenv("MINIMUM_PORTFOLIO_TICKERS", "5")), | ||
|
|
@@ -134,8 +132,8 @@ def create_position(payload: PredictionPayload) -> Dict[str, Any]: | |
| @application.delete("/positions") | ||
| def delete_positions() -> Dict[str, Any]: | ||
| alpaca_client = AlpacaClient( | ||
| api_key=os.getenv("ALPACA_API_KEY"), | ||
| api_secret=os.getenv("ALPACA_API_SECRET"), | ||
| api_key=os.getenv("ALPACA_API_KEY", ""), | ||
| api_secret=os.getenv("ALPACA_API_SECRET", ""), | ||
| paper=os.getenv("ALPACA_PAPER", "true").lower() == "true", | ||
| ) | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| FROM python:3.12 | ||
|
|
||
| COPY --from=ghcr.io/astral-sh/uv:latest /uv /bin/uv | ||
|
|
||
| ENV PYTHONPATH=/app/src | ||
|
|
||
| WORKDIR /app | ||
|
|
||
| COPY pyproject.toml ./ | ||
|
|
||
| RUN uv sync --no-dev | ||
|
|
||
| COPY ./src ./src | ||
|
|
||
| COPY ./miniature_temporal_fusion_transformer.safetensor ./src/predictionengine/miniature_temporal_fusion_transformer.safetensor | ||
|
|
||
| EXPOSE 8080 | ||
|
|
||
| ENTRYPOINT ["uv", "run", "uvicorn", "predictionengine.main:application", "--host", "0.0.0.0", "--port", "8080", "--app-dir", "src"] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| [project] | ||
| name = "predictionengine" | ||
| version = "0.1.0" | ||
| description = "Prediction engine service" | ||
| requires-python = ">=3.12" # possibly 3.10 | ||
| dependencies = [ | ||
| "fastapi>=0.115.12", | ||
| "uvicorn>=0.34.2", | ||
| "tinygrad>=0.10.3", | ||
| "polars>=1.29.0", | ||
| "category-encoders>=2.8.1", | ||
| "requests>=2.31.0", | ||
| ] | ||
|
|
||
| [tool.hatch.build.targets.wheel] | ||
| packages = ["predictionengine"] | ||
|
|
||
| [build-system] | ||
| requires = ["hatchling"] | ||
| build-backend = "hatchling.build" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix the duplicated coverage.xml entry.
The line appears to have a concatenation error with
coverage.xmlcoverage.xml. This should be justcoverage.xml.Apply this diff to fix the typo:
📝 Committable suggestion
🤖 Prompt for AI Agents