|
| 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() |
0 commit comments