Skip to main content Skills Marketplace Descubra e explore skills de IA criadas pela comunidade.
Instalar com Codex ou Claude Copie este prompt, cole no Codex, Claude ou outro assistente e deixe que ele revise a página da skill e instale para você.
Copiar promptMostrar detalhes do prompt Um comando direto ignora o prompt de revisão. Verifique a origem antes de executá-lo.
npx skills add https://github.com/xjtulyc/awesome-rosetta-skills --skill financial-time-seriesO comando permanece em uma só linha. Role horizontalmente para revisá-lo antes de copiar.
Prefere uma cópia local? Baixe os arquivos disponíveis atualmente no SkillsMP.
Baixar Zip Baixando... Ocupações relacionadas SOC
Baseado na classificação ocupacional SOC
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