Skip to content

Commit d1278ac

Browse files
committed
Add Cointegration
1 parent 46544a5 commit d1278ac

10 files changed

Lines changed: 321 additions & 75 deletions

File tree

app/cointegration.py

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
import marimo
2+
3+
__generated_with = "0.19.7"
4+
app = marimo.App(width="medium")
5+
6+
7+
@app.cell
8+
def _():
9+
import marimo as mo
10+
from app.utils import nav_menu
11+
nav_menu()
12+
return (mo,)
13+
14+
15+
@app.cell
16+
def _(mo):
17+
mo.md(r"""
18+
# Cointegration Analysis of Cryptocurrencies
19+
""")
20+
return
21+
22+
23+
@app.cell
24+
def _(mo):
25+
from quantflow.data.fmp import FMP
26+
27+
frequency = mo.ui.dropdown(
28+
options={x.value: x.value for x in FMP.freq},
29+
value="1min",
30+
label="Frequency",
31+
)
32+
frequency
33+
return FMP, frequency
34+
35+
36+
@app.cell
37+
async def _(FMP, frequency):
38+
async with FMP() as cli:
39+
btc = await cli.prices("BTCUSD", convert_to_date=True, frequency=frequency.value)
40+
eth = await cli.prices("ETHUSD", convert_to_date=True, frequency=frequency.value)
41+
sol = await cli.prices("SOLUSD", convert_to_date=True, frequency=frequency.value)
42+
43+
btc = btc.set_index("date")
44+
eth = eth.set_index("date")
45+
sol = sol.set_index("date")
46+
return btc, eth, sol
47+
48+
49+
@app.cell
50+
def _(btc, eth, sol):
51+
# Merge the three price series on the date
52+
prices_3 = btc[['close']].join(eth[['close']], lsuffix='_btc', rsuffix='_eth').join(sol[['close']])
53+
prices_3.columns = ['btc_close', 'eth_close', 'sol_close']
54+
prices_3 = prices_3.dropna()
55+
prices_3
56+
return (prices_3,)
57+
58+
59+
@app.cell
60+
def _(prices_3):
61+
import numpy as np
62+
log_prices_3 = np.log(prices_3)
63+
return (log_prices_3,)
64+
65+
66+
@app.cell
67+
def _(log_prices_3):
68+
from statsmodels.tsa.vector_ar.vecm import coint_johansen
69+
70+
# Perform the Johansen cointegration test
71+
# We choose det_order=0 for a constant term in the cointegrating relation
72+
# and k_ar_diff=1 for the number of lags in the VAR model.
73+
johansen_result = coint_johansen(log_prices_3, det_order=0, k_ar_diff=1)
74+
deltas = johansen_result.evec[:, 0]
75+
deltas
76+
77+
return (deltas,)
78+
79+
80+
@app.cell
81+
def _(deltas, log_prices_3):
82+
residuals = log_prices_3.dot(deltas)
83+
residual_mean = residuals.mean()
84+
residuals = residuals - residual_mean
85+
return (residuals,)
86+
87+
88+
@app.cell
89+
def _(residuals):
90+
import pandas as pd
91+
import altair as alt
92+
93+
94+
# Create a DataFrame for plotting
95+
residuals_df = pd.DataFrame({
96+
"date": residuals.index,
97+
"residual": residuals.values
98+
})
99+
100+
# Create the base chart
101+
base = alt.Chart(residuals_df).encode(
102+
x=alt.X('date:T', title='Date')
103+
).properties(
104+
title='First Cointegration Residual of BTC, ETH, and SOL',
105+
width='container'
106+
)
107+
108+
# Create the line chart for the residuals
109+
line = base.mark_line(color='steelblue').encode(
110+
y=alt.Y('residual:Q', title='Residual (Spread)'),
111+
tooltip=[
112+
alt.Tooltip('date:T', title='Date'),
113+
alt.Tooltip('residual:Q', title='Residual', format='.4f')
114+
]
115+
)
116+
117+
line.interactive()
118+
return
119+
120+
121+
@app.cell
122+
def _():
123+
return
124+
125+
126+
@app.cell
127+
def _(mo):
128+
mo.md(r"""
129+
### Why Pick the Largest Eigenvalue?
130+
131+
In the Johansen cointegration test, the eigenvalues ($\lambda$) are sorted in descending order, and each one corresponds to a different potential cointegrating vector.
132+
133+
**The magnitude of the eigenvalue represents the strength and stability of the corresponding cointegrating relationship.**
134+
135+
1. **Strongest Relationship:** The largest eigenvalue corresponds to the linear combination of the time series that is "most stationary." This means the resulting spread (the residuals) has the strongest tendency to revert to its mean over time.
136+
137+
2. **Statistical Significance:** The test statistics used in the Johansen test (the Trace test and the Maximum Eigenvalue test) are functions of these eigenvalues. These tests help us determine how many statistically significant cointegrating relationships exist, starting from the one associated with the largest eigenvalue.
138+
139+
3. **Practical Application:** For applications like pairs trading, we want to find the most reliable and predictable long-run equilibrium relationship. By choosing the cointegrating vector associated with the largest eigenvalue, we are selecting the portfolio of assets whose value is most likely to be mean-reverting, making it the best candidate for a statistical arbitrage strategy.
140+
141+
In short, picking the largest eigenvalue is equivalent to picking the **most significant and stable cointegrating vector** found by the test.
142+
""")
143+
return
144+
145+
146+
@app.cell
147+
def _(mo):
148+
mo.md(r"""
149+
### Should You Use Log Prices?
150+
151+
**Yes, using log prices is generally recommended** for cointegration analysis in finance. Here is why:
152+
153+
1. **Percentage vs. Absolute Changes:** Log-prices allow the model to work with **relative percentage changes** rather than absolute dollar amounts. This is crucial when assets trade at vastly different scales (e.g., BTC at \$60k vs. SOL at \$150).
154+
2. **Variance Stabilization:** Financial time series often exhibit heteroscedasticity, meaning volatility increases as the price level rises. Log transformation helps stabilize this variance, making the data more suitable for linear statistical models.
155+
3. **Linearization:** Standard cointegration tests (like Johansen or Engle-Granger) look for linear combinations. Real-world economic relationships between assets are often multiplicative (based on ratios); taking logarithms converts these into linear additive relationships.
156+
""")
157+
return
158+
159+
160+
if __name__ == "__main__":
161+
app.run()

app/supersmoother.py

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,14 @@
44
app = marimo.App(width="medium")
55

66

7+
@app.cell
8+
def _():
9+
import marimo as mo
10+
from app.utils import nav_menu
11+
nav_menu()
12+
return (mo,)
13+
14+
715
@app.cell(hide_code=True)
816
def _(mo):
917
mo.md(r"""
@@ -12,12 +20,6 @@ def _(mo):
1220
return
1321

1422

15-
@app.cell
16-
def _():
17-
import marimo as mo
18-
return (mo,)
19-
20-
2123
@app.cell
2224
def _():
2325
from quantflow.data.fmp import FMP
@@ -54,21 +56,19 @@ def _(data, period):
5456
# create the filters
5557
smoother = SuperSmoother(period=period.value)
5658
ewma = EWMA(period=period.value)
57-
ewma_min = EWMA(period=period.value, tau=0.5)
5859
# sort dates ascending
5960
sm = data[["date", "close"]].copy().sort_values("date", ascending=True).reset_index(drop=True)
6061
sm["supersmoother"] = sm["close"].apply(smoother.update)
6162
sm["ewma"] = sm["close"].apply(ewma.update)
62-
sm["ewma_min"] = sm["close"].apply(ewma_min.update)
6363
return (sm,)
6464

6565

66-
@app.cell(hide_code=True)
66+
@app.cell
6767
def _(alt, sm):
6868
# Melt the dataframe to a long format suitable for Altair
6969
sm_long = sm.melt(
7070
id_vars=['date'],
71-
value_vars=['close', 'supersmoother', "ewma", "ewma_min"],
71+
value_vars=['close', 'supersmoother', "ewma"],
7272
var_name='Signal',
7373
value_name='Price'
7474
)
@@ -79,8 +79,8 @@ def _(alt, sm):
7979
y=alt.Y('Price:Q', title='Price (USD)', scale=alt.Scale(zero=False)),
8080
color=alt.Color('Signal:N', title='Signal',
8181
scale=alt.Scale(
82-
domain=['close', 'supersmoother', 'ewma', "ewma_min"],
83-
range=['#4c78a8', '#f58518', '#e45756', '#e45756']) # Vega-Lite default palette
82+
domain=['close', 'supersmoother', 'ewma'],
83+
range=['#4c78a8', '#f58518', '#e45756']) # Vega-Lite default palette
8484
),
8585
tooltip=[
8686
alt.Tooltip('date:T', title='Date'),

docs/api/sp/index.md

Lines changed: 3 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,32 +2,8 @@
22

33
This page gives an overview of all Stochastic Processes available in the library.
44

5-
.. _sp:
5+
::: quantflow.sp.base.StochasticProcess
66

7-
.. currentmodule:: quantflow.sp.base
7+
::: quantflow.sp.base.StochasticProcess1D
88

9-
.. autoclass:: StochasticProcess
10-
:members:
11-
:noindex:
12-
:autosummary:
13-
:autosummary-nosignatures:
14-
15-
.. autoclass:: StochasticProcess1D
16-
:members:
17-
:noindex:
18-
19-
20-
.. autoclass:: IntensityProcess
21-
:members:
22-
:noindex:
23-
24-
.. toctree::
25-
:maxdepth: 1
26-
27-
weiner
28-
poisson
29-
compound_poisson
30-
ou
31-
cir
32-
jump_diffusion
33-
heston
9+
::: quantflow.sp.base.IntensityProcess

docs/bibliography.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
# Bibliography
22

3+
---
34

45
### molnar
56

6-
Peter Molnar. Volatility modeling and forecasting: utilization of realized volatility, implied volatility and the highest and lowest price of the day. Master's thesis, University of Economics in Prague, 2020. URL: https://drive.google.com/file/d/1zCU1OZyrKQLpxaypPv9U5UPbReBDXcMf/view.
7+
Peter Molnar
8+
9+
[Volatility modeling and forecasting: utilization of realized volatility, implied volatility and the highest and lowest price of the day](https://drive.google.com/file/d/1zCU1OZyrKQLpxaypPv9U5UPbReBDXcMf/view)
10+
11+
Master's thesis, University of Economics in Prague, 2020
12+
13+
---

docs/index.md

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,18 @@ pip install quantflow
2525
* [quantflow.ta](https://github.com/quantmind/quantflow/tree/main/quantflow/ta) timeseries analysis tools
2626
* [quantflow.utils](https://github.com/quantmind/quantflow/tree/main/quantflow/utils) utilities and helpers
2727

28-
28+
## Optional dependencies
29+
30+
Quantflow comes with two optional dependencies:
31+
32+
* `data` for data retrieval, to install it use
33+
```
34+
pip install quantflow[data]
35+
```
36+
* `cli` for command line interface, to install it use
37+
```
38+
pip install quantflow[data,cli]
39+
```
2940

3041
## Command line tools
3142

0 commit comments

Comments
 (0)