Skip to content
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
10 changes: 10 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,16 @@ on:
jobs:
build:
runs-on: ubuntu-latest
services:
redis:
image: redis:7
ports:
- 6379:6379
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
env:
PYTHON_ENV: ci
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Expand Down
16 changes: 10 additions & 6 deletions app/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,12 +64,16 @@ async def api_redoc() -> HTMLResponse:
api.include_router(volatility_router)
app.include_router(api)
app.include_router(status_router, include_in_schema=False)
app.mount(
"/examples",
HtmlFallbackStaticFiles(directory=APP_PATH / "examples", html=True),
name="examples",
)
app.mount("/", StaticFiles(directory=APP_PATH / "docs", html=True), name="static")
examples_dir = APP_PATH / "examples"
if examples_dir.is_dir():
app.mount(
"/examples",
HtmlFallbackStaticFiles(directory=examples_dir, html=True),
name="examples",
)
docs_dir = APP_PATH / "docs"
if docs_dir.is_dir():
app.mount("/", StaticFiles(directory=docs_dir, html=True), name="static")
return app


Expand Down
14 changes: 12 additions & 2 deletions app/api/hurst.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,18 @@

hurst_router = APIRouter()

DEFAULT_PERIODS = ("10s", "20s", "30s", "1m", "2m", "3m", "5m", "10m", "30m")
VASICEK_PERIODS = ("10m", "20m", "30m", "1h")
DEFAULT_PERIODS = (
"10s",
"20s",
"30s",
"1min",
"2min",
"3min",
"5min",
"10min",
"30min",
)
VASICEK_PERIODS = ("10min", "20min", "30min", "1h")


class HurstWienerResponse(BaseModel):
Expand Down
7 changes: 7 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ docs = [
"mkdocs-redirects>=1.2.1",
"mkdocstrings[python]==1.0.0",
"plotly>=6.2.0",
"pyarrow>=19.0.0",
]
ml = [
"torch>=2.10.0",
Expand Down Expand Up @@ -127,6 +128,12 @@ testpaths = [
"quantflow_tests",
]

[tool.coverage.run]
source = [
"quantflow",
"app",
]

[tool.coverage.report]
exclude_also = [
"@abstractmethod",
Expand Down
156 changes: 156 additions & 0 deletions quantflow_tests/test_app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
from datetime import date, timedelta
from unittest.mock import AsyncMock

import numpy as np
import pandas as pd
import pytest
from fastapi import FastAPI
from fastapi.testclient import TestClient

from app.__main__ import crate_app


@pytest.fixture
def mock_fmp() -> AsyncMock:
fmp = AsyncMock()
start = date.today() - timedelta(days=30)
dates = [start + timedelta(days=i) for i in range(30)]
prices_df = pd.DataFrame(
{
"date": dates,
"close": np.linspace(90.0, 110.0, 30).tolist(),
}
)
fmp.prices = AsyncMock(return_value=prices_df)
return fmp


@pytest.fixture
def app(mock_fmp: AsyncMock) -> FastAPI:
application = crate_app()
application.state.fmp = mock_fmp
return application


@pytest.fixture
def client(app: FastAPI) -> TestClient:
return TestClient(app)


def test_status(client: TestClient) -> None:
response = client.get("/status")
assert response.status_code == 200
assert response.json() == {"status": "ok"}


def test_ready(client: TestClient) -> None:
response = client.get("/ready")
assert response.status_code == 200
assert response.json() == {"status": "ok"}


def test_supersmoother(client: TestClient) -> None:
response = client.get("/.api/supersmoother?period=10&symbol=BTCUSD")
assert response.status_code == 200
data = response.json()
assert "data" in data
assert len(data["data"]) == 30
point = data["data"][0]
assert "date" in point
assert "close" in point
assert "supersmoother" in point
assert "ewma" in point


def test_supersmoother_custom_period(client: TestClient) -> None:
response = client.get("/.api/supersmoother?period=20&symbol=ETHUSD")
assert response.status_code == 200
data = response.json()
assert len(data["data"]) == 30


def test_supersmoother_invalid_period(client: TestClient) -> None:
response = client.get("/.api/supersmoother?period=1&symbol=BTCUSD")
assert response.status_code == 422


def test_gaussian_sampling(client: TestClient) -> None:
response = client.get("/.api/gaussian-sampling?kappa=1.0&samples=100")
assert response.status_code == 200
data = response.json()
assert "x" in data
assert "simulation" in data
assert "analytical" in data
assert len(data["x"]) == len(data["simulation"])


def test_poisson_sampling(client: TestClient) -> None:
response = client.get("/.api/poisson-sampling?intensity=2.0&samples=100")
assert response.status_code == 200
data = response.json()
assert "x" in data
assert "simulation" in data
assert "analytical" in data


def test_double_exponential_sampling(client: TestClient) -> None:
response = client.get("/.api/double-exponential-sampling?log_kappa=0.1&samples=100")
assert response.status_code == 200
data = response.json()
assert "x" in data
assert "simulation" in data
assert "analytical" in data
assert "char_x" in data
assert "char_y" in data


def test_heston_vol_surface_jd(client: TestClient) -> None:
response = client.get("/.api/heston-vol-surface?model=jd&vol=0.4&sigma=0.5")
assert response.status_code == 200
data = response.json()
assert "moneyness" in data
assert "ttm" in data
assert "iv" in data
assert len(data["ttm"]) == 10
assert len(data["moneyness"]) == 51


def test_heston_vol_surface_hj(client: TestClient) -> None:
response = client.get(
"/.api/heston-vol-surface?model=hj&vol=0.4&sigma=0.5&kappa=1.0&rho=-0.3"
)
assert response.status_code == 200
data = response.json()
assert "iv" in data


def test_hurst_wiener(client: TestClient) -> None:
response = client.get("/.api/hurst-wiener?sigma=2.0")
assert response.status_code == 200
data = response.json()
assert "dates" in data
assert "values" in data
assert "hurst_exponent" in data
assert "estimator_periods" in data


def test_hurst_vasicek(client: TestClient) -> None:
response = client.get("/.api/hurst-vasicek?kappa=10.0")
assert response.status_code == 200
data = response.json()
assert "dates" in data
assert "values" in data
assert "hurst_realized" in data
assert "hurst_pk" in data


def test_yield_curve(client: TestClient) -> None:
response = client.get(
"/.api/yield-curve?ttm=0.25&ttm=0.5&ttm=1.0&ttm=2.0"
"&rates=0.04&rates=0.042&rates=0.045&rates=0.048"
)
assert response.status_code == 200
data = response.json()
assert "curve" in data
assert "ttm" in data
assert "rates" in data
Loading
Loading