Skip to main content ホーム クリエイター xjtulyc awesome-rosetta-skills financial-time-series
financial-time-series Use this Skill for financial time series analysis: GARCH volatility modeling, cointegration, pairs trading, factor models, and risk decomposition.
インストールへ移動 Skills Marketplace コミュニティが作成したAIスキルを発見・探索
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
直接コマンドでは確認用 Prompt が省略されます。実行前にソースを確認してください。
npx skills add https://github.com/xjtulyc/awesome-rosetta-skills --skill financial-time-seriesコマンドは1行のまま表示されます。コピー前に横へスクロールして全体を確認してください。
ローカルで確認しますか?SkillsMP が現在取得できるファイルをダウンロードできます。
Zipをダウンロード ダウンロード中... このリポジトリの他の Skills Astronomical data analysis with astropy and astroquery — FITS I/O, WCS transforms, catalog cross-matching, aperture photometry, and CMB power spectra.
Seismological data analysis with ObsPy — FDSN waveform download, response removal, phase picking, moment tensor inversion, and seismicity mapping.
Download and analyze oceanographic data from Copernicus Marine Service and Argo floats using copernicusmarine, gsw, and xarray.
name financial-time-series description Use this Skill for financial time series analysis: GARCH volatility modeling, cointegration, pairs trading, factor models, and risk decomposition.
tags ["finance","time-series","garch","volatility","quantitative-finance"] version 1.0.0 authors [{"name":"Rosetta Skills Contributors","github":"@xjtulyc"}] license MIT platforms ["claude-code","codex","gemini-cli","cursor"] dependencies {"python":["numpy>=1.24","pandas>=2.0","scipy>=1.11","statsmodels>=0.14","arch>=6.2","matplotlib>=3.7","yfinance>=0.2"]} last_updated 2026-03-17 status stable
Financial Time Series Analysis
One-line summary : Analyze financial time series with GARCH volatility models, cointegration tests, pairs trading signals, Fama-French factor regressions, and rolling risk decomposition.
When to Use This Skill
When modeling volatility clustering with GARCH/EGARCH models
When testing and exploiting cointegration for pairs trading
When running Fama-French 3/5 factor regressions for alpha/beta
When computing rolling VaR, expected shortfall, and drawdowns
When detecting structural breaks in financial time series
When building momentum and mean-reversion trading signals
Trigger keywords : GARCH, volatility, cointegration, pairs trading, Fama-French, factor model, VaR, expected shortfall, momentum, mean reversion, financial time series, ARCH, unit root, ADF test, Kalman filter
Background & Key Concepts
GARCH(p,q) Volatility
$$
r_t = \mu + \epsilon_t, \quad \epsilon_t = \sigma_t z_t, \quad z_t \sim \mathcal{N}(0,1)
$$
$$
\sigma_t^2 = \omega + \sum_{i=1}^q \alpha_i \epsilon_{t-i}^2 + \sum_{j=1}^p \beta_j \sigma_{t-j}^2
$$
Stationarity: $\sum \alpha_i + \sum \beta_j < 1$.
Cointegration (Engle-Granger)
Two I(1) series $X_t, Y_t$ are cointegrated if $\exists \beta: Y_t - \beta X_t = u_t$ where $u_t$ is I(0). The spread $u_t$ is mean-reverting and exploitable for pairs trading.
Fama-French 3-Factor Model
$$
R_i - R_f = \alpha_i + \beta_{MKT}(R_m - R_f) + \beta_{SMB} \cdot SMB + \beta_{HML} \cdot HML + \epsilon_i
$$
Environment Setup
Install Dependencies
pip install numpy>=1.24 pandas>=2.0 scipy>=1.11 statsmodels>=0.14 \
arch >=6.2 matplotlib>=3.7 yfinance>=0.2
Verify Installation
import numpy as np
import pandas as pd
from arch import arch_model
from statsmodels.tsa.stattools import adfuller
np.random.seed(42 )
returns = np.random.randn(500 ) * 0.01
am = arch_model(returns, vol= , p= , q= )
res = am.fit(disp= )
( )
'Garch'
1
1
'off'
print
f"arch version OK; GARCH omega={res.params['omega' ]:.6 f} "
Core Workflow
Step 1: GARCH Volatility Modeling import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from arch import arch_model
import yfinance as yf
print ("Downloading S&P 500 data..." )
try :
spy = yf.download("SPY" , start="2010-01-01" , end="2024-12-31" , progress=False )
prices = spy["Adj Close" ].dropna()
except Exception:
np.random.seed(0 )
dates = pd.date_range("2010-01-01" , periods=3780 , freq='B' )
omega, alpha, beta = 5e-6 , 0.09 , 0.88
n = len (dates)
sigma2 = np.zeros(n); eps = np.zeros(n)
sigma2[0 ] = omega / (1 - alpha - beta)
for t in range (1 , n):
sigma2[t] = omega + alpha * eps[t-1 ]**2 + beta * sigma2[t-1 ]
eps[t] = np.sqrt(sigma2[t]) * np.random.randn()
prices = pd.Series(100 * np.exp(np.cumsum(eps)), index=dates, name="SPY" )
returns = 100 * prices.pct_change().dropna()
am = arch_model(returns, vol='Garch' , p=1 , q=1 , dist='Normal' )
res = am.fit(disp='off' )
print (res.summary())
omega_hat = res.params['omega' ]
alpha_hat = res.params['alpha[1]' ]
beta_hat = res.params['beta[1]' ]
persistence = alpha_hat + beta_hat
half_life = np.log(0.5 ) / np.log(persistence)
print (f"\nVolatility persistence: {persistence:.4 f} " )
print (f"Shock half-life: {half_life:.1 f} trading days" )
print (f"Long-run annualized vol: {np.sqrt(omega_hat/(1 -persistence)*252 ):.2 f} %" )
cond_vol = res.conditional_volatility
annual_vol = cond_vol * np.sqrt(252 )
fig, axes = plt.subplots(3 , 1 , figsize=(14 , 10 ), sharex=True )
axes[0 ].plot(returns.index, returns, 'k-' , linewidth=0.4 , alpha=0.7 )
axes[0 ].set_title("Daily Returns (%)" )
axes[0 ].set_ylabel("Return (%)" )
axes[0 ].grid(True , alpha=0.3 )
axes[1 ].plot(returns.index, annual_vol, color='#e74c3c' , linewidth=1 )
axes[1 ].fill_between(returns.index, 0 , annual_vol, alpha=0.2 , color='#e74c3c' )
axes[1 ].set_ylabel("Annualized Vol (%)" )
axes[1 ].set_title("GARCH(1,1) Conditional Volatility" )
axes[1 ].grid(True , alpha=0.3 )
std_resid = res.std_resid
axes[2 ].plot(returns.index, std_resid, 'b-' , linewidth=0.4 , alpha=0.7 )
axes[2 ].axhline(0 , color='gray' , linewidth=0.5 )
axes[2 ].axhline(3 , color='red' , linestyle='--' , linewidth=1 )
axes[2 ].axhline(-3 , color='red' , linestyle='--' , linewidth=1 )
axes[2 ].set_ylabel("Std. Residuals" ); axes[2 ].set_xlabel("Date" )
axes[2 ].set_title("Standardized Residuals (should be ~N(0,1))" )
axes[2 ].grid(True , alpha=0.3 )
plt.suptitle("GARCH(1,1) Volatility Model — SPY" , y=1.01 )
plt.tight_layout()
plt.savefig("garch_volatility.png" , dpi=150 )
plt.show()
Step 2: Cointegration and Pairs Trading import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from statsmodels.tsa.stattools import adfuller, coint
from statsmodels.regression.linear_model import OLS
import statsmodels.api as sm
np.random.seed(7 )
n = 1000
dates = pd.date_range("2020-01-01" , periods=n, freq='B' )
trend = np.cumsum(np.random.randn(n) * 0.5 )
spread_true = np.random.randn(n) * 2.0
price_X = 100 + trend + np.random.randn(n) * 0.3
price_Y = 50 + 0.6 * trend + spread_true + np.random.randn(n) * 0.3
X = pd.Series(price_X, index=dates, name="Stock_X" )
Y = pd.Series(price_Y, index=dates, name="Stock_Y" )
for name, series in [("Stock X" , X), ("Stock Y" , Y)]:
adf_result = adfuller(series, autolag='AIC' )
print (f"ADF test — {name} : stat={adf_result[0 ]:.4 f} , p={adf_result[1 ]:.4 f} "
f"({'I(1)' if adf_result[1 ] > 0.05 else 'stationary' } )" )
score, pvalue, _ = coint(X, Y)
print (f"\nCointegration test: stat={score:.4 f} , p={pvalue:.4 f} " )
print ("Cointegrated!" if pvalue < 0.05 else "NOT cointegrated (p>0.05)" )
X_const = sm.add_constant(X)
ols_res = OLS(Y, X_const).fit()
beta_hedge = ols_res.params["Stock_X" ]
alpha_const = ols_res.params["const" ]
print (f"\nHedge ratio β = {beta_hedge:.4 f} (Y ≈ {alpha_const:.2 f} + {beta_hedge:.4 f} ·X)" )
spread = Y - beta_hedge * X - alpha_const
roll_mean = spread.rolling(window=60 ).mean()
roll_std = spread.rolling(window=60 ).std()
z_score = (spread - roll_mean) / roll_std
entry_long = z_score < -2.0
entry_short = z_score > +2.0
exit_signal = (z_score.abs () < 0.5 )
fig, axes = plt.subplots(3 , 1 , figsize=(14 , 10 ), sharex=True )
axes[0 ].plot(X.index, X, label="Stock X" , linewidth=0.8 )
axes[0 ].plot(Y.index, Y, label="Stock Y" , linewidth=0.8 )
axes[0 ].set_title("Cointegrated Price Pair" ); axes[0 ].legend(); axes[0 ].grid(alpha=0.3 )
axes[1 ].plot(spread.index, spread, 'purple' , linewidth=0.8 )
axes[1 ].plot(roll_mean.index, roll_mean, 'gray' , linestyle='--' , linewidth=1 , label='60d mean' )
axes[1 ].set_title("Spread (Y - βX - α)" ); axes[1 ].legend(); axes[1 ].grid(alpha=0.3 )
axes[2 ].plot(z_score.index, z_score, 'b-' , linewidth=0.7 )
axes[2 ].axhline( 2 , color='red' , linestyle='--' , linewidth=1.5 , label='Short (z>+2)' )
axes[2 ].axhline(-2 , color='green' , linestyle='--' , linewidth=1.5 , label='Long (z<-2)' )
axes[2 ].axhline( 0.5 , color='gray' , linestyle=':' , linewidth=1 )
axes[2 ].axhline(-0.5 , color='gray' , linestyle=':' , linewidth=1 )
axes[2 ].fill_between(z_score.index, -2 , z_score, where=z_score<-2 , alpha=0.3 , color='green' )
axes[2 ].fill_between(z_score.index, z_score, 2 , where=z_score>2 , alpha=0.3 , color='red' )
axes[2 ].set_title("Spread Z-Score — Trading Signals" )
axes[2 ].set_xlabel("Date" ); axes[2 ].legend(); axes[2 ].grid(alpha=0.3 )
plt.tight_layout()
plt.savefig("pairs_trading.png" , dpi=150 )
plt.show()
long_trades = entry_long.sum ()
short_trades = entry_short.sum ()
print (f"\nLong entries: {long_trades} , Short entries: {short_trades} " )
Step 3: Factor Model Regression import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import statsmodels.api as sm
np.random.seed(42 )
n = 252 * 5
dates = pd.date_range("2019-01-01" , periods=n, freq='B' )
mkt_premium = np.random.randn(n) * 0.01 + 0.0003
smb = np.random.randn(n) * 0.005
hml = np.random.randn(n) * 0.004
rf = np.full(n, 0.02 /252 )
true_alpha = 0.02 /252
true_beta_mkt = 1.15
true_beta_smb = 0.3
true_beta_hml = -0.2
asset_excess = (true_alpha
+ true_beta_mkt * mkt_premium
+ true_beta_smb * smb
+ true_beta_hml * hml
+ np.random.randn(n) * 0.008 )
factors = pd.DataFrame({
"Mkt-RF" : mkt_premium,
"SMB" : smb,
"HML" : hml,
"Rf" : rf,
}, index=dates)
y = pd.Series(asset_excess, index=dates, name="Asset excess return" )
X = sm.add_constant(factors[["Mkt-RF" , "SMB" , "HML" ]])
model = sm.OLS(y, X)
result = model.fit(cov_type='HAC' , cov_kwds={'maxlags' : 21 })
print (result.summary())
alpha_daily = result.params["const" ]
alpha_annual = alpha_daily * 252
t_alpha = result.tvalues["const" ]
print (f"\nAnnualized alpha: {alpha_annual*100 :.3 f} % (t={t_alpha:.2 f} )" )
print (f"Market beta: {result.params['Mkt-RF' ]:.4 f} (true: {true_beta_mkt} )" )
print (f"SMB beta: {result.params['SMB' ]:.4 f} (true: {true_beta_smb} )" )
print (f"HML beta: {result.params['HML' ]:.4 f} (true: {true_beta_hml} )" )
print (f"R²: {result.rsquared:.4 f} " )
roll_window = 63
def rolling_ols_beta (y_series, X_df, window ):
betas = {}
for col in X_df.columns:
betas[col] = [np.nan] * (window - 1 )
for end in range (window, len (y_series)+1 ):
y_w = y_series.iloc[end-window:end]
X_w = sm.add_constant(X_df.iloc[end-window:end])
try :
b = sm.OLS(y_w, X_w).fit().params[col]
except Exception:
b = np.nan
betas[col].append(b)
return pd.DataFrame(betas, index=y_series.index)
rolling_betas = rolling_ols_beta(y, factors[["Mkt-RF" , "SMB" , "HML" ]], roll_window)
fig, axes = plt.subplots(3 , 1 , figsize=(13 , 8 ), sharex=True )
colors = ['#e74c3c' , '#3498db' , '#2ecc71' ]
for ax, col, color, truth in zip (axes, ["Mkt-RF" ,"SMB" ,"HML" ], colors, [true_beta_mkt, true_beta_smb, true_beta_hml]):
ax.plot(rolling_betas.index, rolling_betas[col], color=color, linewidth=1.5 )
ax.axhline(truth, color='black' , linestyle='--' , linewidth=1 , alpha=0.7 , label=f'True β={truth} ' )
ax.set_ylabel(f"β ({col} )" ); ax.legend(loc='upper right' ); ax.grid(alpha=0.3 )
axes[-1 ].set_xlabel("Date" )
plt.suptitle("Rolling Fama-French Factor Exposures (63-day window)" )
plt.tight_layout()
plt.savefig("factor_model.png" , dpi=150 )
plt.show()
Advanced Usage
EGARCH and Fat-Tailed Distributions import numpy as np
from arch import arch_model
np.random.seed(0 )
returns = np.random.randn(1000 ) * 0.01
am = arch_model(returns * 100 , vol='EGARCH' , p=1 , q=1 , dist='StudentsT' )
res = am.fit(disp='off' )
print (res.summary())
nu = res.params.get('nu' , 'N/A' )
print (f"\nStudent's t d.o.f.: {nu} " )
print (f"EGARCH gamma (leverage): {res.params.get('gamma[1]' , 'N/A' ):.4 f} " )
Troubleshooting
Error: ConvergenceWarning in GARCH fit Cause : Poor starting values or very short series.
am = arch_model(returns, vol='Garch' , p=1 , q=1 )
res = am.fit(starting_values=np.array([0.01 , 0.05 , 0.90 ]), disp='off' , options={'maxiter' : 500 })
Error: ValueError: NaN values in ADF test Fix : Drop NaN before testing:
series = series.dropna()
result = adfuller(series, autolag='AIC' )
Spurious regression warning Always difference I(1) series before OLS, or use proper cointegration:
from statsmodels.tsa.stattools import adfuller
p_level = adfuller(X)[1 ]
p_diff = adfuller(X.diff().dropna())[1 ]
print (f"I(1) if p_level>0.05 and p_diff<0.05: {p_level:.3 f} , {p_diff:.3 f} " )
Version Compatibility Package Tested versions Notes arch 6.2, 6.3 arch_model API stablestatsmodels 0.14 coint requires statsmodels ≥ 0.11yfinance 0.2.x Adj Close column name may change
External Resources
Official Documentation
Key Papers
Bollerslev, T. (1986). Generalized autoregressive conditional heteroskedasticity . Journal of Econometrics.
Engle, R.F. & Granger, C.W.J. (1987). Co-integration and error correction . Econometrica.
Examples
Example 1: Value-at-Risk via Historical Simulation import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
np.random.seed(42 )
from scipy.stats import t as t_dist
returns = t_dist.rvs(df=5 , size=1000 ) * 0.01
confidence = 0.99
var_99 = np.percentile(returns, (1 -confidence)*100 )
es_99 = returns[returns <= var_99].mean()
print (f"1-day VaR (99%): {var_99*100 :.3 f} %" )
print (f"1-day ES (99%): {es_99*100 :.3 f} %" )
fig, ax = plt.subplots(figsize=(9 , 4 ))
ax.hist(returns*100 , bins=50 , color='steelblue' , edgecolor='white' , linewidth=0.5 , alpha=0.7 )
ax.axvline(var_99*100 , color='red' , linewidth=2 , linestyle='--' , label=f'VaR(99%)={var_99*100 :.2 f} %' )
ax.axvline(es_99*100 , color='orange' , linewidth=2 , linestyle='--' , label=f'ES(99%)={es_99*100 :.2 f} %' )
ax.set_xlabel("Daily Return (%)" ); ax.set_title("Portfolio Return Distribution — VaR / ES" )
ax.legend(); ax.grid(alpha=0.3 )
plt.tight_layout(); plt.savefig("var_es.png" , dpi=150 ); plt.show()
Example 2: Hurst Exponent (Long Memory Detection) import numpy as np
def hurst_exponent (ts, max_lag=100 ):
"""Estimate Hurst exponent via R/S analysis."""
lags = range (2 , max_lag)
tau = []
for lag in lags:
n_blocks = len (ts) // lag
if n_blocks < 2 :
break
rs_values = []
for i in range (n_blocks):
block = ts[i*lag:(i+1 )*lag]
mean_b = np.mean(block)
dev = np.cumsum(block - mean_b)
R = dev.max () - dev.min ()
S = np.std(block, ddof=1 )
if S > 0 :
rs_values.append(R/S)
if rs_values:
tau.append((lag, np.mean(rs_values)))
lags_arr = np.array([x[0 ] for x in tau])
rs_arr = np.array([x[1 ] for x in tau])
H, _ = np.polyfit(np.log(lags_arr), np.log(rs_arr), 1 )
return H
np.random.seed(1 )
rw = np.cumsum(np.random.randn(1000 ))
trending = np.cumsum(np.random.randn(1000 ) + 0.02 )
print (f"Hurst(random walk): H = {hurst_exponent(rw):.4 f} (expected ≈0.5)" )
print (f"Hurst(with trend): H = {hurst_exponent(trending):.4 f} (expected >0.5)" )
Last updated: 2026-03-17 | Maintainer: @xjtulyc
Issues: GitHub Issues