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
39 changes: 38 additions & 1 deletion notebooks/applications/hurst.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ jupytext:
extension: .md
format_name: myst
format_version: 0.13
jupytext_version: 1.14.7
jupytext_version: 1.16.6
kernelspec:
display_name: Python 3 (ipykernel)
language: python
Expand All @@ -23,12 +23,49 @@ from quantflow.sp.cir import CIR
p = CIR(kappa=1, sigma=1)
```

## Study the Weiner process OHLC

```{code-cell} ipython3
from quantflow.sp.weiner import WeinerProcess
p = WeinerProcess(sigma=0.5)
paths = p.sample(1, 1, 1000)
df = paths.as_datetime_df().reset_index()
df
```

```{code-cell} ipython3
from quantflow.ta.ohlc import OHLC
from datetime import timedelta
ohlc = OHLC(serie="0", period="10m", rogers_satchell_variance=True, parkinson_variance=True, garman_klass_variance=True)
result = ohlc(df)
result
```

```{code-cell} ipython3

```

# Links

* [Wikipedia](https://en.wikipedia.org/wiki/Hurst_exponent)
* [Hurst Exponent for Algorithmic Trading
](https://robotwealth.com/demystifying-the-hurst-exponent-part-1/)

```{code-cell} ipython3
import pandas as pd
v = pd.to_timedelta(0.02, unit="d")
v
```

```{code-cell} ipython3
v.to_pytimedelta()
```

```{code-cell} ipython3
from quantflow.utils.dates import utcnow
pd.date_range(start=utcnow(), periods=10, freq="0.5S")
```

```{code-cell} ipython3

```
Empty file added quantflow/ta/__init__.py
Empty file.
12 changes: 12 additions & 0 deletions quantflow/ta/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from typing import TypeAlias

import pandas as pd
import polars as pl

DataFrame: TypeAlias = pl.DataFrame | pd.DataFrame


def to_polars(df: DataFrame) -> pl.DataFrame:
if isinstance(df, pd.DataFrame):
return pl.DataFrame(df)
return df
110 changes: 110 additions & 0 deletions quantflow/ta/ohlc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
from dataclasses import dataclass
from datetime import timedelta

import numpy as np
import polars as pl

from .base import DataFrame, to_polars


@dataclass
class OHLC:
"""Aggregates OHLC data over a given period and serie

Optionally calculates the range-based variance estimators for the serie.
Range-based estimator are called like that because they are calculated from the
difference between the period high and low.
"""

serie: str
"""serie to aggregate"""
period: str | timedelta
"""down-sampling period, e.g. 1h, 1d, 1w"""
index_column: str = "index"
"""column to group by"""
parkinson_variance: bool = False
"""add Parkinson variance column"""
garman_klass_variance: bool = False
"""add Garman Klass variance column"""
rogers_satchell_variance: bool = False
"""add Rogers Satchell variance column"""
percent_variance: bool = False
"""log-transform the variance columns"""

@property
def open_col(self) -> pl.Expr:
return self.var_column("open")

@property
def high_col(self) -> pl.Expr:
return self.var_column("high")

@property
def low_col(self) -> pl.Expr:
return self.var_column("low")

@property
def close_col(self) -> pl.Expr:
return self.var_column("close")

def __call__(self, df: DataFrame) -> pl.DataFrame:
"""Returns a dataframe with OHLC data sampled over the given period"""
result = (
to_polars(df)
.group_by_dynamic(self.index_column, every=self.period)
.agg(
pl.col(self.serie).first().alias(f"{self.serie}_open"),
pl.col(self.serie).max().alias(f"{self.serie}_high"),
pl.col(self.serie).min().alias(f"{self.serie}_low"),
pl.col(self.serie).last().alias(f"{self.serie}_close"),
pl.col(self.serie).mean().alias(f"{self.serie}_mean"),
)
)
if self.parkinson_variance:
result = self.parkinson(result)
if self.garman_klass_variance:
result = self.garman_klass(result)
if self.rogers_satchell_variance:
result = self.rogers_satchell(result)
return result

def parkinson(self, df: DataFrame) -> pl.DataFrame:
"""Adds parkinson variance column to the dataframe

This requires the serie high and low columns to be present
"""
c = (self.high_col - self.low_col) ** 2 / np.sqrt(4 * np.log(2))
return to_polars(df).with_columns(c.alias(f"{self.serie}_pk"))

def garman_klass(self, df: DataFrame) -> pl.DataFrame:
"""Adds Garman Klass variance estimator column to the dataframe

This requires the serie high and low columns to be present.
"""
open = self.open_col
hh = self.high_col - open
ll = self.low_col - open
cc = self.close_col - open
c = (
0.522 * (hh - ll) ** 2
- 0.019 * (cc * (hh + ll) + 2.0 * ll * hh)
- 0.383 * cc**2
)
return to_polars(df).with_columns(c.alias(f"{self.serie}_gk"))

def rogers_satchell(self, df: DataFrame) -> pl.DataFrame:
"""Adds Rogers Satchell variance estimator column to the dataframe

This requires the serie high and low columns to be present.
"""
open = self.open_col
hh = self.high_col - open
ll = self.low_col - open
cc = self.close_col - open
c = hh * (hh - cc) + ll * (ll - cc)
return to_polars(df).with_columns(c.alias(f"{self.serie}_rs"))

def var_column(self, suffix: str) -> pl.Expr:
"""Returns a polars expression for the OHLC column"""
col = pl.col(f"{self.serie}_{suffix}")
return col.log() if self.percent_variance else col
71 changes: 0 additions & 71 deletions quantflow/utils/df.py

This file was deleted.

16 changes: 16 additions & 0 deletions quantflow/utils/paths.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import annotations

from datetime import datetime
from typing import Any, cast

import numpy as np
Expand All @@ -10,6 +11,7 @@

from . import plot
from .bins import pdf as bins_pdf
from .dates import utcnow
from .types import FloatArray


Expand Down Expand Up @@ -53,6 +55,14 @@ def ys(self) -> list[list[float]]:
"""Paths as list of list (for visualization tools)"""
return self.data.transpose().tolist() # type: ignore

def dates(
self, *, start: datetime | None = None, unit: str = "d"
) -> pd.DatetimeIndex:
"""Dates of paths as a pandas DatetimeIndex"""
start = start or utcnow()
end = start + pd.to_timedelta(self.t, unit=unit)
return pd.date_range(start=start, end=end, periods=self.time_steps + 1)

def mean(self) -> FloatArray:
"""Mean of paths"""
return np.mean(self.data, axis=1)
Expand All @@ -65,6 +75,12 @@ def var(self) -> FloatArray:
"""Variance of paths"""
return np.var(self.data, axis=1)

def as_datetime_df(
self, *, start: datetime | None = None, unit: str = "d"
) -> pd.DataFrame:
"""Paths as pandas DataFrame with datetime index"""
return pd.DataFrame(self.data, index=self.dates(start=start, unit=unit))

def integrate(self) -> Paths:
"""Integrate paths"""
return self.__class__(
Expand Down
24 changes: 24 additions & 0 deletions quantflow_tests/test_ohlc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from quantflow.sp.weiner import WeinerProcess
from quantflow.ta.ohlc import OHLC


def test_ohlc() -> None:
ohlc = OHLC(
serie="0",
period="10m",
parkinson_variance=True,
garman_klass_variance=True,
rogers_satchell_variance=True,
)
assert ohlc.serie == "0"
assert ohlc.period == "10m"
assert ohlc.index_column == "index"
assert ohlc.parkinson_variance is True
assert ohlc.garman_klass_variance is True
assert ohlc.rogers_satchell_variance is True
assert ohlc.percent_variance is False
# create a dataframe
path = WeinerProcess(sigma=0.5).sample(1, 1, 1000)
df = path.as_datetime_df().reset_index()
result = ohlc(df)
assert result.shape == (145, 9)
2 changes: 1 addition & 1 deletion readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ pip install quantflow

## Modules

* [quantflow.cli](https://github.com/quantmind/quantflow/tree/main/quantflow/cli) aommand line client (requires `quantflow[cli,data]`)
* [quantflow.cli](https://github.com/quantmind/quantflow/tree/main/quantflow/cli) command line client (requires `quantflow[cli,data]`)
* [quantflow.data](https://github.com/quantmind/quantflow/tree/main/quantflow/data) data APIs (requires `quantflow[data]`)
* [quantflow.options](https://github.com/quantmind/quantflow/tree/main/quantflow/options) option pricing and calibration
* [quantflow.sp](https://github.com/quantmind/quantflow/tree/main/quantflow/sp) stochastic process primitives
Expand Down
Loading