Skip to content
Merged

docs #33

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
16 changes: 16 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
dist/
build/

__marimo__
__pycache__
*.pyc
*.pyo
*.pyd
.data
.env
.venv
.mypy_cache
.ruff_cache
.pytest_cache
.codebuild
.gihub
35 changes: 27 additions & 8 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ jobs:
runs-on: ubuntu-latest
env:
PYTHON_ENV: ci
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }}
FMP_API_KEY: ${{ secrets.FMP_API_KEY }}
strategy:
Expand All @@ -20,6 +21,12 @@ jobs:

steps:
- uses: actions/checkout@v4
- name: install rops
run: |
curl -L https://raw.githubusercontent.com/quantmind/rops/main/dev/install-rops | bash
echo "$HOME/bin" >> $GITHUB_PATH
- name: install taplo
run: rops tools update taplo
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
Expand All @@ -37,17 +44,29 @@ jobs:
- name: run tests
run: make tests
- name: upload coverage reports to codecov
if: matrix.python-version == '3.13'
if: matrix.python-version == '3.14'
uses: codecov/codecov-action@v3
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: ./build/coverage.xml
- name: build book
if: ${{ matrix.python-version == '3.13' }}
run: make book
- name: publish book
if: ${{ matrix.python-version == '3.13' }}
run: make publish-book
- name: publish
if: ${{ matrix.python-version == '3.13' && github.event.head_commit.message == 'release' }}
if: ${{ matrix.python-version == '3.14' && github.event.head_commit.message == 'release' }}
run: make publish

deploy:
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
needs:
- build
env:
PYTHON_ENV: ci
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
FMP_API_KEY: ${{ secrets.FMP_API_KEY }}

steps:
- name: Checkout repo
uses: actions/checkout@v4
- name: install rops
run: |
curl -L https://raw.githubusercontent.com/quantmind/rops/main/dev/install-rops | bash
echo "$HOME/bin" >> $GITHUB_PATH
31 changes: 0 additions & 31 deletions .github/workflows/update.yml

This file was deleted.

4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ htmlcov
.eggs
*.egg-info
__pycache__
__marimo__
build
dist
.venv
Expand All @@ -33,3 +34,6 @@ dist
*.ipynb
.ipynb_checkpoints
_build

# builds
app/docs
6 changes: 5 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
{
"python.defaultInterpreterPath": ".venv/bin/python"
"python.defaultInterpreterPath": ".venv/bin/python",
"[python]": {
"editor.defaultFormatter": "ms-python.black-formatter",
"editor.formatOnSave": true
}
}
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Copyright (c) 2023-2025 Quantmind
Copyright (c) 2023-2026 Quantmind

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
Expand Down
17 changes: 16 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@ lint-check: ## Lint check only
install-dev: ## Install development dependencies
@./dev/install

.PHONY: marimo
marimo: ## Run marimo for editing notebooks
@./dev/marimo edit

.PHONY: marimo-export
marimo-export: ## Run marimo for editing notebooks
@./dev/marimo export html-wasm -f --show-code notebooks/supersmoother.py -o docs/applications/supersmoother

.PHONY: notebook
notebook: ## Run Jupyter notebook server
Expand All @@ -44,9 +51,17 @@ nbsync: ## Sync python myst notebooks to .ipynb files - needed for vs noteboo
sphinx-config: ## Build sphinx config
poetry run jupyter-book config sphinx notebooks

.PHONY: docs
docs: ## build documentation
@cp docs/index.md readme.md
@poetry run mkdocs build

.PHONY: docs-serve
docs-serve: ## serve documentation
@poetry run mkdocs serve --livereload --watch quantflow --watch docs

.PHONY: sphinx
sphinx:
sphinx: ## Build sphinx docs
poetry run sphinx-build notebooks path/to/book/_build/html -b html


Expand Down
26 changes: 26 additions & 0 deletions app/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import marimo
from fastapi.staticfiles import StaticFiles
from fastapi import FastAPI
from pathlib import Path

APP_PATH = Path(__file__).parent


def crate_app() -> FastAPI:
# Create a marimo asgi app
server = marimo.create_asgi_app()
for path in APP_PATH.glob("*.py"):
if path.name.startswith("_"):
continue
dashed = path.stem.replace("_", "-")
server = server.with_app(path=f"/{dashed}", root=f"./app/{path.name}")
# Create a FastAPI app
app = FastAPI()
app.mount("/examples", server.build())
app.mount("/", StaticFiles(directory=APP_PATH / "docs", html=True), name="static")
return app

# Run the server
if __name__ == "__main__":
import uvicorn
uvicorn.run(crate_app(), host="0.0.0.0", port=8001)
92 changes: 92 additions & 0 deletions app/gaussian_sampling.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import marimo

__generated_with = "0.19.7"
app = marimo.App(width="medium")


@app.cell
def _():
import marimo as mo
from app.utils import nav_menu

nav_menu()
return (mo,)


@app.cell
def _(mo):
mo.md(r"""
# Gaussian Sampling

Here we sample the gaussian OU process for different mean reversion speed and number of paths.
""")
return


@app.cell
def _(mo):
import inspect
from quantflow.sp.ou import Vasicek
import pandas as pd

def simulate_vasicek(kappa: float, samples: int) -> pd.DataFrame:
pr = Vasicek(rate=0.5, kappa=kappa)
paths = pr.sample(samples, 1, 1000)
pdf = paths.pdf(num_bins=50)
pdf["simulation"] = pdf["pdf"]
pdf["analytical"] = pr.marginal(1).pdf(pdf.index)
return pdf

# 1. Get the source code of your function
# Note: The function must be defined in a previous cell or imported
try:
source_code = inspect.getsource(simulate_vasicek)
except OSError:
source_code = "# Code not available (source file not found)"

# 2. Display it inside an accordion so it doesn't clutter the view
mo.accordion({
"Show Simulation Code": mo.md(f"```python\n{source_code}\n```")
})
return (simulate_vasicek,)


@app.cell
def _(mo):
samples = mo.ui.slider(start=100, stop=10000, step=100, value=1000, debounce=True, full_width=True)
kappa = mo.ui.slider(start=0.1, stop=5, step=0.1, debounce=True, full_width=True)

def input_label(text):
return mo.Html(f"<span style='width: 250px; display: inline-block; font-weight: 500;'>{text}</span>")

controls = mo.vstack([
mo.hstack([input_label("Samples:"), samples], align="center"),
mo.hstack([input_label("Kappa (Mean reversion):"), kappa], align="center")
])
controls
return kappa, samples


@app.cell
def _(kappa, samples, simulate_vasicek):
df = simulate_vasicek(kappa=kappa.value, samples=samples.value)
return (df,)


@app.cell
def _(df):
import plotly.graph_objects as go
simulation = go.Bar(x=df.index, y=df["simulation"], name="simulation")
analytical = go.Scatter(x=df.index, y=df["analytical"], name="analytical")
fig = go.Figure(data=[simulation, analytical])
fig
return


@app.cell
def _():
return


if __name__ == "__main__":
app.run()
82 changes: 82 additions & 0 deletions app/hurst.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import marimo

__generated_with = "0.19.7"
app = marimo.App(width="medium")


@app.cell
def _():
import marimo as mo
from app.utils import nav_menu
nav_menu()
return (mo,)


@app.cell
def _(mo):
mo.md(r"""
# Hurst Exponent

The [Hurst exponent](https://en.wikipedia.org/wiki/Hurst_exponent) is a statistical measure used to uncover the long-term memory of a time series. It helps determine if a financial asset is purely random, trending, or mean-reverting.

The intuition is based on how the volatility of a time series scales with time. If a time series $x_t$ follows a standard Brownian motion (a Random Walk), the variance of the changes increases linearly with the time.

\begin{align}
\text{Var}(x_{t_2} - x_{t_1}) &\propto t_2 - t_1 \\
&\propto \Delta t^{2H}\\
H &= 0.5
\end{align}

where $H$ is the Hurst exponent.

Trending time-series have a Hurst exponent H > 0.5, while mean reverting time-series have H < 0.5. Understanding in which regime a time-series is can be useful for trading strategies.

These are some references to understand the Hurst exponent and its applications:

* [Hurst Exponent for Algorithmic Trading](https://robotwealth.com/demystifying-the-hurst-exponent-part-1/)
* [Basics of Statistical Mean Reversion Testing](https://www.quantstart.com/articles/Basics-of-Statistical-Mean-Reversion-Testing/)

## Estimate from OHLC data

We want to construct a mechanism to estimate the Hurst exponent via OHLC data because it is widely available from data providers and easily constructed as an online signal during trading.

In order to evaluate results against known solutions, we consider the Weiner process as generator of timeseries.

We use the **WeinerProcess** from the stochastic process library and sample one path over a time horizon of 1 (day) with a time step every second.
""")
return


@app.cell
def _():
from quantflow.sp.weiner import WeinerProcess
p = WeinerProcess(sigma=2.0)
paths = p.sample(n=1, time_horizon=1, time_steps=24*60*60)
paths.plot(title="A path of Weiner process with sigma=2.0")
return (paths,)


@app.cell
def _(mo):
mo.md(r"""
In order to down-sample the timeseries, we need to convert it into a dataframe with dates as indices.
""")
return


@app.cell
def _(paths):

from quantflow.utils.dates import start_of_day
df = paths.as_datetime_df(start=start_of_day(), unit="d").reset_index()

return


@app.cell
def _():
return


if __name__ == "__main__":
app.run()
Loading