Skewness Kurtosis Analysis
Analyze the third and fourth statistical moments of return distributions - skewness measuring asymmetry and excess kurtosis measuring tail thickness relative to a normal distribution - as critical inputs for risk management models and option pricing frameworks that assume non-normal returns.
Skewness & Kurtosis Analysis — Statistical Analysis
Category: Statistical Analysis | Subcategory: Distributions
What This Notebook Does
Skewness and kurtosis are the third and fourth standardised moments of a return distribution:
Skewness = E[(X-μ)³] / σ³ — measures asymmetry
Kurtosis = E[(X-μ)⁴] / σ⁴ - 3 — measures tail fatness (excess kurtosis)
Why traders care:
- Positive skewness: more small losses, occasional large gains (preferred)
- Negative skewness: more small gains, occasional large losses (most options sellers)
- High kurtosis: fat tails — standard deviation underestimates actual risk
This notebook:
- Computes rolling skewness and kurtosis over time
- Identifies regime shifts in skewness (bearish: negative skew spikes)
- Compares skewness across asset classes
- Analyses the relationship between skewness and forward returns
- Builds a skewness-based timing indicator
- Exports the analysis
!pip install numpy pandas matplotlib seaborn scipy --quietimport numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from scipy import stats
import warnings
warnings.filterwarnings('ignore')
%matplotlib inline
plt.rcParams['figure.figsize'] = (14, 5)
plt.rcParams['axes.spines.top'] = False
plt.rcParams['axes.spines.right'] = False
print('Imports ready.')Imports ready.
Section 1 — Configuration
This section defines the configuration parameters for the analysis, such as the rolling window size, simulation days, and forward prediction days.
ROLLING_WINDOW = 30
SIMULATION_DAYS = 1460
FORWARD_DAYS = 10 # days to look ahead for predictive analysis
print('Config ready.')Config ready.
Section 2 — Data
This section generates synthetic price and return data with varying skewness regimes for the analysis.
def generate_skewed_returns(n=1460, seed=42):
rng = np.random.default_rng(seed)
t = np.arange(n)
# Cycle from positive to negative skew and back
skew_regime = np.sin(2 * np.pi * t / 365) * 0.8
bull = 0.5 * (skew_regime + 1)
bear = 1 - bull
# Mix of right-skewed (bull) and left-skewed (bear) distributions
right = rng.exponential(0.015, n) # positively skewed
left = -rng.exponential(0.018, n) # negatively skewed
base = rng.normal(0, 0.012, n)
rets = bull * right + bear * left + 0.3 * base
rets += rng.normal(0, 0.005, n) # small noise
price = 30_000 * np.cumprod(1 + rets)
idx = pd.date_range('2020-01-01', periods=n, freq='D')
return pd.DataFrame({'price': price, 'return': rets * 100,
'true_skew': skew_regime}, index=idx)
df = generate_skewed_returns(SIMULATION_DAYS)
print(f'Global skewness: {df["return"].skew():.3f}')
print(f'Global kurtosis: {df["return"].kurt():.3f}')Global skewness: -0.519 Global kurtosis: 6.357
Section 3 — Rolling Moments
This section calculates rolling skewness, kurtosis, and standard deviation of the returns, as well as forward returns for predictive analysis.
df['rolling_skew'] = df['return'].rolling(ROLLING_WINDOW).skew()
df['rolling_kurt'] = df['return'].rolling(ROLLING_WINDOW).kurt()
df['rolling_std'] = df['return'].rolling(ROLLING_WINDOW).std()
# Forward return (for predictive analysis)
df['fwd_return'] = df['price'].pct_change(FORWARD_DAYS).shift(-FORWARD_DAYS) * 100
# Correlation between rolling skew and forward return
skew_fwd_corr = df[['rolling_skew', 'fwd_return']].dropna().corr().iloc[0, 1]
print(f'Rolling skew vs {FORWARD_DAYS}d forward return correlation: {skew_fwd_corr:.3f}')Rolling skew vs 10d forward return correlation: 0.690
Section 4 — Skewness as Timing Signal
This section explores how rolling skewness can be used as a timing signal by comparing average forward returns during periods of positive and negative skew.
# Positive skewness: upside surprises dominate → bullish lean
# Negative skewness: downside surprises dominate → de-risk
df['skew_signal'] = np.sign(df['rolling_skew']) # 1=positive skew, -1=negative skew
pos_skew_fwd = df[df['rolling_skew'] > 0.5]['fwd_return'].dropna().mean()
neg_skew_fwd = df[df['rolling_skew'] < -0.5]['fwd_return'].dropna().mean()
print(f'Avg forward return when rolling skew > +0.5: {pos_skew_fwd:.2f}%')
print(f'Avg forward return when rolling skew < -0.5: {neg_skew_fwd:.2f}%')Avg forward return when rolling skew > +0.5: 8.26% Avg forward return when rolling skew < -0.5: -8.75%
Section 5 — Visualization
This section visualizes the price, rolling skewness, and rolling kurtosis over time to identify trends and patterns.
fig, axes = plt.subplots(3, 1, figsize=(14, 11), sharex=True)
fig.suptitle('Rolling Skewness & Kurtosis Analysis', fontsize=14, fontweight='bold')
ax1 = axes[0]
ax1.plot(df.index, df['price'], color='#1976d2', lw=1.2)
ax1.set_ylabel('Price'); ax1.set_title('BTC Price')
ax2 = axes[1]
ax2.plot(df.index, df['rolling_skew'], color='#e65100', lw=1.5)
ax2.fill_between(df.index, df['rolling_skew'], 0,
where=df['rolling_skew'] > 0, color='#43a047', alpha=0.25, label='Positive skew (bullish lean)')
ax2.fill_between(df.index, df['rolling_skew'], 0,
where=df['rolling_skew'] <= 0, color='#e53935', alpha=0.25, label='Negative skew (bearish lean)')
ax2.axhline(0, color='black', lw=0.8)
ax2.set_ylabel(f'{ROLLING_WINDOW}d Rolling Skewness')
ax2.legend(fontsize=8); ax2.set_title('Rolling Skewness')
ax3 = axes[2]
ax3.plot(df.index, df['rolling_kurt'], color='#7b1fa2', lw=1.5)
ax3.axhline(0, color='black', ls='--', lw=0.8, label='Normal kurtosis = 0')
ax3.axhline(3, color='red', ls=':', lw=0.8, label='Fat tail threshold')
ax3.set_ylabel(f'{ROLLING_WINDOW}d Rolling Kurtosis')
ax3.legend(fontsize=8); ax3.set_title('Rolling Excess Kurtosis')
plt.tight_layout()
plt.show()Section 6 — Export
This section exports the generated DataFrame containing all the analysis results to a CSV file.
df.to_csv('skewness_kurtosis_analysis.csv')
print('Saved: skewness_kurtosis_analysis.csv')Saved: skewness_kurtosis_analysis.csv
Conclusion
This notebook demonstrated how to generate synthetic price and return data with varying skewness regimes and then compute rolling skewness, kurtosis, and standard deviation. We explored the relationship between rolling skewness and forward returns, showing how periods of positive skew can indicate bullish sentiment, while negative skew can suggest bearish leanings. The visualizations provided a clear view of how these metrics evolve over time alongside price action. Finally, the analysis results were exported for further use. This methodology can be applied to real-world financial data to identify potential timing signals and manage risk based on the statistical properties of return distributions.