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명령은 한 줄로 유지됩니다. 복사하기 전에 가로로 스크롤해 전체 내용을 확인하세요.
로컬 사본을 원하시나요? 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