比特幣減半定量數據分析:圖表視覺化與統計模型完整指南
從定量數據分析角度深入探討比特幣減半週期的歷史數據、圖表視覺化技術、統計模型建立方法,以及數據驅動的投資決策框架,包含可直接使用的 Python 程式碼範例。
比特幣減半定量數據分析:圖表視覺化與統計模型完整指南
比特幣減半事件是比特幣貨幣政策的核心機制,每約四年發生一次,區塊獎勵自動減少50%。本篇文章將從定量數據分析的角度,深入探討比特幣減半週期的歷史數據、圖表視覺化技術、統計模型的建立方法,以及數據驅動的投資決策框架。我們將提供可直接使用的 Python 程式碼範例,幫助讀者建立自己的減半週期分析系統。
減半數據獲取與預處理
數據來源與 API 整合
進行比特幣減半定量分析的第一步是獲取可靠的歷史數據。主要的數據來源包括區塊鏈瀏覽器 API、加密貨幣交易所 API、以及專業的區塊鏈數據提供商。
import requests
import pandas as pd
import numpy as np
from datetime import datetime, timedelta
import matplotlib.pyplot as plt
import seaborn as sns
class BitcoinHalvingDataCollector:
"""
比特幣減半數據收集器
整合多個數據來源進行綜合分析
"""
def __init__(self):
self.base_urls = {
'blockchain': 'https://blockchain.info',
'coinmetrics': 'https://api.coinmetrics.io/v4',
'glassnode': 'https://api.glassnode.com/v1'
}
self.halving_dates = {
1: datetime(2012, 11, 28),
2: datetime(2016, 7, 9),
3: datetime(2020, 5, 11),
4: datetime(2024, 4, 20)
}
self.halving_block_heights = {
1: 210000,
2: 420000,
3: 630000,
4: 840000
}
self.block_rewards = {
1: {'before': 50, 'after': 25},
2: {'before': 25, 'after': 12.5},
3: {'before': 12.5, 'after': 6.25},
4: {'before': 6.25, 'after': 3.125}
}
def get_block_height_at_date(self, target_date):
"""
根據日期估算區塊高度
比特幣平均區塊時間約為 10 分鐘
"""
genesis_date = datetime(2009, 1, 3)
days_since_genesis = (target_date - genesis_date).days
blocks_per_day = 144 # 24小時 * 6區塊/小時
return days_since_genesis * blocks_per_day
def get_price_data(self, start_date, end_date):
"""
從 CoinGecko API 獲取比特幣歷史價格數據
"""
url = "https://api.coingecko.com/api/v3/coins/bitcoin/market_chart/range"
params = {
'vs_currency': 'usd',
'from': start_date.timestamp(),
'to': end_date.timestamp()
}
try:
response = requests.get(url, params=params)
data = response.json()
prices = data['prices']
df = pd.DataFrame(prices, columns=['timestamp', 'price'])
df['date'] = pd.to_datetime(df['timestamp'], unit='ms')
df.set_index('date', inplace=True)
return df
except Exception as e:
print(f"數據獲取錯誤: {e}")
return None
def get_block_data(self, start_height, end_height):
"""
從 Blockchain.info 獲取區塊數據
"""
blocks = []
for height in range(start_height, end_height + 1, 1000):
url = f"{self.base_urls['blockchain']}/block-height/{height}"
try:
response = requests.get(url)
if response.status_code == 200:
data = response.json()
blocks.append({
'height': height,
'time': datetime.fromtimestamp(data['time']),
'tx_count': len(data['tx'])
})
except Exception as e:
continue
return pd.DataFrame(blocks)
# 初始化數據收集器
collector = BitcoinHalvingDataCollector()
# 獲取比特幣歷史價格數據(涵蓋所有減半週期)
price_data = collector.get_price_data(
datetime(2010, 1, 1),
datetime(2025, 1, 1)
)
print(f"數據點數量: {len(price_data)}")
print(f"數據範圍: {price_data.index.min()} 至 {price_data.index.max()}")
print(f"價格範圍: ${price_data['price'].min():,.2f} 至 ${price_data['price'].max():,.2f}")
數據清洗與特徵工程
獲取原始數據後,需要進行系統性的清洗和特徵工程,以便後續的定量分析。
def preprocess_halving_data(df):
"""
減半數據預處理與特徵工程
"""
# 創建減半週期標籤
df['halving_cycle'] = None
df.loc[df.index < collector.halving_dates[1], 'halving_cycle'] = 0
df.loc[(df.index >= collector.halving_dates[1]) &
(df.index < collector.halving_dates[2]), 'halving_cycle'] = 1
df.loc[(df.index >= collector.halving_dates[2]) &
(df.index < collector.halving_dates[3]), 'halving_cycle'] = 2
df.loc[(df.index >= collector.halving_dates[3]) &
(df.index < collector.halving_dates[4]), 'halving_cycle'] = 3
df.loc[df.index >= collector.halving_dates[4], 'halving_cycle'] = 4
# 計算價格變化率
df['daily_return'] = df['price'].pct_change()
df['log_return'] = np.log(df['price'] / df['price'].shift(1))
# 計算滾動統計量
df['ma_30'] = df['price'].rolling(window=30).mean()
df['ma_90'] = df['price'].rolling(window=90).mean()
df['ma_365'] = df['price'].rolling(window=365).mean()
# 計算波動率
df['volatility_30d'] = df['daily_return'].rolling(window=30).std() * np.sqrt(365)
df['volatility_90d'] = df['daily_return'].rolling(window=90).std() * np.sqrt(365)
# 計算距離下次減半的天數
df['days_to_halving'] = None
for i, (halving_num, halving_date) in enumerate(collector.halving_dates.items()):
mask = df.index >= halving_date
if i < len(collector.halving_dates) - 1:
next_halving = list(collector.halving_dates.values())[i + 1]
mask = mask & (df.index < next_halving)
df.loc[mask, 'days_to_halving'] = (df.loc[mask].index - halving_date).days
return df
# 執行數據預處理
price_data = preprocess_halving_data(price_data)
# 顯示處理後的數據統計
print("\n各減半週期價格統計:")
for cycle in range(5):
cycle_data = price_data[price_data['halving_cycle'] == cycle]
if len(cycle_data) > 0:
print(f"\n週期 {cycle}:")
print(f" 數據點數: {len(cycle_data)}")
print(f" 最低價: ${cycle_data['price'].min():,.2f}")
print(f" 最高價: ${cycle_data['price'].max():,.2f}")
print(f" 平均日波動率: {cycle_data['daily_return'].mean()*100:.4f}%")
減半週期圖表視覺化技術
價格走勢與減半標記可視化
減半週期分析的核心是將價格走勢與減半事件進行視覺化對照,以便發現規律和異常。
def plot_halving_price_chart(df, save_path='halving_price_chart.png'):
"""
比特幣減半週期價格走勢圖
包含減半事件標記和關鍵價格水平
"""
fig, axes = plt.subplots(2, 1, figsize=(16, 12), gridspec_kw={'height_ratios': [3, 1]})
# 上圖:價格走勢(對數刻度)
ax1 = axes[0]
# 繪製價格曲線
ax1.semilogy(df.index, df['price'], 'b-', linewidth=1, label='比特幣價格 (USD)', alpha=0.8)
# 標記減半日期
colors = ['green', 'blue', 'orange', 'red', 'purple']
for i, (halving_num, halving_date) in enumerate(collector.halving_dates.items()):
if halving_date in df.index:
ax1.axvline(x=halving_date, color=colors[i], linestyle='--',
linewidth=2, label=f'第{halving_num}次減半')
# 標註減半時的價格
idx = df.index.get_indexer([halving_date], method='nearest')[0]
price_at_halving = df.iloc[idx]['price']
ax1.annotate(f'減半 {halving_num}\n${price_at_halving:,.0f}',
xy=(halving_date, price_at_halving),
xytext=(30, 30), textcoords='offset points',
fontsize=9, color=colors[i],
arrowprops=dict(arrowstyle='->', color=colors[i], alpha=0.7))
# 繪製長期均線
ax1.plot(df.index, df['ma_365'], 'orange', linewidth=1.5,
label='365日均線', alpha=0.7)
ax1.set_ylabel('比特幣價格 (USD, 對數刻度)', fontsize=12)
ax1.set_title('比特幣減半週期與價格走勢分析 (2010-2025)', fontsize=14, fontweight='bold')
ax1.legend(loc='upper left', fontsize=9)
ax1.grid(True, alpha=0.3)
ax1.set_ylim(1, 200000)
# 下圖:減半週期內相對時間
ax2 = axes[1]
# 創建週期顏色映射
colors_map = {0: 'gray', 1: 'green', 2: 'blue', 3: 'orange', 4: 'red'}
cycle_colors = [colors_map.get(c, 'gray') for c in df['halving_cycle']]
ax2.scatter(df.index, df['halving_cycle'], c=cycle_colors, s=1, alpha=0.5)
ax2.set_ylabel('減半週期', fontsize=12)
ax2.set_xlabel('日期', fontsize=12)
ax2.set_yticks([0, 1, 2, 3, 4])
ax2.set_yticklabels(['創世-減半1', '週期1', '週期2', '週期3', '週期4'])
ax2.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig(save_path, dpi=150, bbox_inches='tight')
plt.close()
print(f"價格走勢圖已保存至: {save_path}")
# 生成價格走勢圖
plot_halving_price_chart(price_data)
週期疊加分析圖
週期疊加分析是將不同減半週期的價格走勢標準化後重疊在一起,以便比較各週期的相似性。
def plot_cycle_overlay(df, days_before=365, days_after=1095, save_path='cycle_overlay.png'):
"""
減半週期疊加分析圖
將每個減半週期標準化後重疊比較
"""
fig, axes = plt.subplots(2, 2, figsize=(16, 12))
# 子圖1:相對時間價格走勢
ax1 = axes[0, 0]
normalized_cycles = []
for cycle in range(1, 5):
cycle_data = df[df['halving_cycle'] == cycle].copy()
if len(cycle_data) > 0:
# 標準化時間軸(以減半日為0點)
cycle_data['relative_days'] = (cycle_data.index -
collector.halving_dates[cycle]).days
# 標準化價格(以減半日價格為基準=1)
halving_idx = cycle_data.index.get_indexer(
[collector.halving_dates[cycle]], method='nearest')[0]
base_price = cycle_data.iloc[halving_idx]['price']
cycle_data['normalized_price'] = cycle_data['price'] / base_price
# 篩選範圍內的數據
cycle_data = cycle_data[
(cycle_data['relative_days'] >= -days_before) &
(cycle_data['relative_days'] <= days_after)
]
if len(cycle_data) > 0:
ax1.plot(cycle_data['relative_days'],
cycle_data['normalized_price'],
label=f'週期 {cycle}', linewidth=1.5, alpha=0.7)
normalized_cycles.append(cycle_data)
ax1.axvline(x=0, color='red', linestyle='--', linewidth=2, label='減半日')
ax1.set_xlabel('相對於減半日的天數', fontsize=11)
ax1.set_ylabel('標準化價格 (減半日=1)', fontsize=11)
ax1.set_title('減半週期疊加分析', fontsize=12, fontweight='bold')
ax1.legend(fontsize=9)
ax1.grid(True, alpha=0.3)
ax1.set_xlim(-days_before, days_after)
# 子圖2:各週期最大漲幅統計
ax2 = axes[0, 1]
cycle_stats = []
for cycle in range(1, 5):
cycle_data = df[df['halving_cycle'] == cycle].copy()
if len(cycle_data) > 0:
halving_idx = cycle_data.index.get_indexer(
[collector.halving_dates[cycle]], method='nearest')[0]
base_price = cycle_data.iloc[halving_idx]['price']
max_price = cycle_data['price'].max()
max_return = (max_price - base_price) / base_price * 100
cycle_stats.append({
'cycle': cycle,
'halving_price': base_price,
'cycle_high': max_price,
'max_return_pct': max_return
})
stats_df = pd.DataFrame(cycle_stats)
bars = ax2.bar(stats_df['cycle'], stats_df['max_return_pct'],
color=['green', 'blue', 'orange', 'red'], alpha=0.7)
ax2.set_xlabel('減半週期', fontsize=11)
ax2.set_ylabel('減半後最大漲幅 (%)', fontsize=11)
ax2.set_title('各減半週期最大漲幅比較', fontsize=12, fontweight='bold')
# 添加數值標籤
for bar, val in zip(bars, stats_df['max_return_pct']):
ax2.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 50,
f'{val:.0f}%', ha='center', fontsize=10)
ax2.grid(True, alpha=0.3, axis='y')
# 子圖3:週期內價格分布箱型圖
ax3 = axes[1, 0]
cycle_prices = []
cycle_labels = []
for cycle in range(5):
cycle_data = df[df['halving_cycle'] == cycle]['price']
if len(cycle_data) > 0:
cycle_prices.append(np.log10(cycle_data.values))
cycle_labels.append(f'週期 {cycle}')
bp = ax3.boxplot(cycle_prices, labels=cycle_labels, patch_artist=True)
colors_box = ['gray', 'green', 'blue', 'orange', 'red']
for patch, color in zip(bp['boxes'], colors_box):
patch.set_facecolor(color)
patch.set_alpha(0.5)
ax3.set_ylabel('比特幣價格 (對數刻度)', fontsize=11)
ax3.set_title('各減半週期價格分布', fontsize=12, fontweight='bold')
ax3.grid(True, alpha=0.3, axis='y')
# 子圖4:波動率趨勢
ax4 = axes[1, 1]
ax4.plot(df.index, df['volatility_30d'], 'b-', linewidth=1,
label='30日波動率', alpha=0.7)
ax4.plot(df.index, df['volatility_90d'], 'orange', linewidth=1.5,
label='90日波動率', alpha=0.7)
# 標記減半日期
for i, (halving_num, halving_date) in enumerate(collector.halving_dates.items()):
ax4.axvline(x=halving_date, color=colors[i], linestyle='--',
linewidth=2, alpha=0.7)
ax4.set_xlabel('日期', fontsize=11)
ax4.set_ylabel('年化波動率', fontsize=11)
ax4.set_title('比特幣價格波動率趨勢', fontsize=12, fontweight='bold')
ax4.legend(fontsize=9)
ax4.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig(save_path, dpi=150, bbox_inches='tight')
plt.close()
print(f"週期疊加分析圖已保存至: {save_path}")
# 生成週期疊加分析圖
plot_cycle_overlay(price_data)
熱力圖與相關性分析
熱力圖是分析減半週期內不同階段價格行為的有效工具。
def plot_halving_heatmap(df, save_path='halving_heatmap.png'):
"""
減半週期熱力圖分析
展示減半前後不同時間段的價格變化
"""
# 創建時間段分析數據
time_windows = [
(-365, -180, '減半前12-6個月'),
(-180, -90, '減半前6-3個月'),
(-90, -30, '減半前3-1個月'),
(-30, 0, '減半前1個月'),
(0, 30, '減半後1個月'),
(30, 90, '減半後1-3個月'),
(90, 180, '減半後3-6個月'),
(180, 365, '減半後6-12個月'),
(365, 730, '減半後1-2年'),
(730, 1095, '減半後2-3年')
]
# 計算各週期各時間段的回報率
heatmap_data = []
for cycle in range(1, 5):
cycle_data = df[df['halving_cycle'] == cycle].copy()
if len(cycle_data) > 0:
halving_date = collector.halving_dates[cycle]
for window_start, window_end, label in time_windows:
start_date = halving_date + timedelta(days=window_start)
end_date = halving_date + timedelta(days=window_end)
start_idx = cycle_data.index.get_indexer([start_date], method='nearest')[0]
end_idx = cycle_data.index.get_indexer([end_date], method='nearest')[0]
if 0 <= start_idx < len(cycle_data) and 0 <= end_idx < len(cycle_data):
start_price = cycle_data.iloc[start_idx]['price']
end_price = cycle_data.iloc[end_idx]['price']
if start_price > 0:
return_pct = (end_price - start_price) / start_price * 100
heatmap_data.append({
'cycle': f'週期 {cycle}',
'period': label,
'return_pct': return_pct
})
heatmap_df = pd.DataFrame(heatmap_data)
# 轉換為透視表
pivot_df = heatmap_df.pivot(index='cycle', columns='period', values='return_pct')
# 重新排序列
period_order = [label for _, _, label in time_windows]
pivot_df = pivot_df[[col for col in period_order if col in pivot_df.columns]]
# 繪製熱力圖
fig, ax = plt.subplots(figsize=(18, 6))
sns.heatmap(pivot_df, annot=True, fmt='.0f', cmap='RdYlGn', center=0,
ax=ax, cbar_kws={'label': '回報率 (%)'})
ax.set_title('比特幣減半週期各階段回報率熱力圖', fontsize=14, fontweight='bold')
ax.set_xlabel('時間段', fontsize=11)
ax.set_ylabel('減半週期', fontsize=11)
plt.tight_layout()
plt.savefig(save_path, dpi=150, bbox_inches='tight')
plt.close()
print(f"熱力圖已保存至: {save_path}")
return pivot_df
# 生成熱力圖
heatmap_df = plot_halving_heatmap(price_data)
print("\n減半週期各階段平均回報率:")
print(heatmap_df.mean())
統計模型建立與分析
描述性統計分析
在建立預測模型之前,首先需要對減半週期數據進行全面的描述性統計分析。
def calculate_halving_statistics(df):
"""
減半週期描述性統計分析
"""
stats_results = []
for cycle in range(1, 5):
cycle_data = df[df['halving_cycle'] == cycle]
if len(cycle_data) > 0:
# 基本統計
stats = {
'週期': cycle,
'數據點數': len(cycle_data),
'最低價': cycle_data['price'].min(),
'最高價': cycle_data['price'].max(),
'平均價': cycle_data['price'].mean(),
'中位數': cycle_data['price'].median(),
'標準差': cycle_data['price'].std(),
'日收益率均值': cycle_data['daily_return'].mean(),
'日收益率標準差': cycle_data['daily_return'].std(),
'年化波動率': cycle_data['daily_return'].std() * np.sqrt(365),
'偏度': cycle_data['daily_return'].skew(),
'峰度': cycle_data['daily_return'].kurtosis()
}
# 減半時的具體數據
halving_date = collector.halving_dates[cycle]
halving_idx = cycle_data.index.get_indexer([halving_date], method='nearest')[0]
halving_price = cycle_data.iloc[halving_idx]['price']
stats['減半日價格'] = halving_price
stats['減半後最高價'] = cycle_data[cycle_data.index > halving_date]['price'].max()
stats['減半前最低價'] = cycle_data[cycle_data.index < halving_date]['price'].min()
stats_results.append(stats)
return pd.DataFrame(stats_results)
# 計算統計數據
stats_df = calculate_halving_statistics(price_data)
print("減半週期描述性統計:")
print(stats_df.to_string())
# 計算各週期的Sharpe比率
print("\n各減半週期風險調整收益(Sharpe比率):")
for cycle in range(1, 5):
cycle_data = price_data[price_data['halving_cycle'] == cycle]
if len(cycle_data) > 0:
annual_return = cycle_data['daily_return'].mean() * 365
annual_vol = cycle_data['daily_return'].std() * np.sqrt(365)
sharpe = annual_return / annual_vol if annual_vol > 0 else 0
print(f"週期 {cycle}: 年化收益 {annual_return*100:.2f}%, 年化波動 {annual_vol*100:.2f}%, Sharpe {sharpe:.2f}")
回歸分析模型
使用回歸分析來研究減半週期內價格與各種變數之間的關係。
from scipy import stats as scipy_stats
from sklearn.linear_model import LinearRegression
from sklearn.preprocessing import PolynomialFeatures
def perform_regression_analysis(df):
"""
減半週期回歸分析
包含線性回歸和多項式回歸
"""
results = {}
for cycle in range(1, 5):
cycle_data = df[df['halving_cycle'] == cycle].copy()
if len(cycle_data) > 30:
# 準備數據
cycle_data['days_from_start'] = (cycle_data.index - cycle_data.index[0]).days
X = cycle_data['days_from_start'].values.reshape(-1, 1)
y = np.log(cycle_data['price'].values) # 使用對數價格
# 線性回歸(指數增長模型)
lr = LinearRegression()
lr.fit(X, y)
y_pred_lr = lr.predict(X)
# 計算R²
ss_res_lr = np.sum((y - y_pred_lr)**2)
ss_tot = np.sum((y - np.mean(y))**2)
r2_lr = 1 - (ss_res_lr / ss_tot)
# 日均增長率
daily_growth_rate = np.exp(lr.coef_[0]) - 1
results[cycle] = {
'daily_growth_rate': daily_growth_rate,
'annual_growth_rate': (1 + daily_growth_rate)**365 - 1,
'r_squared': r2_lr,
'intercept': lr.intercept_
}
return pd.DataFrame(results).T
# 執行回歸分析
regression_results = perform_regression_analysis(price_data)
print("\n減半週期回歸分析結果:")
print(regression_results.to_string())
# 繪製回歸趨勢線
def plot_regression_trends(df, save_path='regression_trends.png'):
"""
減半週期回歸趨勢分析圖
"""
fig, axes = plt.subplots(2, 2, figsize=(16, 12))
for cycle in range(1, 5):
ax = axes[(cycle-1)//2, (cycle-1)%2]
cycle_data = df[df['halving_cycle'] == cycle].copy()
if len(cycle_data) > 30:
cycle_data['days_from_start'] = (cycle_data.index - cycle_data.index[0]).days
X = cycle_data['days_from_start'].values.reshape(-1, 1)
y = np.log(cycle_data['price'].values)
# 線性回歸
lr = LinearRegression()
lr.fit(X, y)
y_pred = lr.predict(X)
# 繪製實際價格(對數刻度)
ax.semilogy(cycle_data.index, cycle_data['price'],
'b-', linewidth=1, alpha=0.7, label='實際價格')
# 繪製回歸趨勢線
ax.semilogy(cycle_data.index, np.exp(y_pred),
'r--', linewidth=2, label=f'回歸趨勢 (R²={regression_results.loc[cycle, "r_squared"]:.3f})')
# 標記減半日
halving_date = collector.halving_dates[cycle]
ax.axvline(x=halving_date, color='green', linestyle=':',
linewidth=2, label='減半日')
ax.set_title(f'週期 {cycle} 回歸趨勢分析', fontsize=12, fontweight='bold')
ax.set_xlabel('日期')
ax.set_ylabel('比特幣價格 (USD, 對數)')
ax.legend(fontsize=9)
ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig(save_path, dpi=150, bbox_inches='tight')
plt.close()
print(f"回歸趨勢圖已保存至: {save_path}")
# 生成回歸趨勢圖
plot_regression_trends(price_data)
時間序列分析模型
from scipy.stats import normaltest, shapiro
def time_series_analysis(df):
"""
減半週期時間序列分析
包含自相關性和分佈特性分析
"""
results = []
for cycle in range(1, 5):
cycle_data = df[df['halving_cycle'] == cycle]['daily_return'].dropna()
if len(cycle_data) > 30:
# 基本統計
mean_return = cycle_data.mean()
std_return = cycle_data.std()
# 正態性檢驗
if len(cycle_data) >= 5000:
_, p_value = normaltest(cycle_data)
else:
_, p_value = shapiro(cycle_data[:min(len(cycle_data), 5000)])
# 自相關分析(lag 1-10)
acf_values = [cycle_data.autocorr(lag=lag) for lag in range(1, 11)]
results.append({
'cycle': cycle,
'mean_daily_return': mean_return,
'std_daily_return': std_return,
'skewness': cycle_data.skew(),
'kurtosis': cycle_data.kurtosis(),
'normality_p_value': p_value,
'is_normal': p_value > 0.05,
'acf_lag1': acf_values[0],
'acf_lag5': acf_values[4],
'acf_lag10': acf_values[9]
})
return pd.DataFrame(results)
# 執行時間序列分析
ts_results = time_series_analysis(price_data)
print("\n時間序列分析結果:")
print(ts_results.to_string())
# 價格分佈可視化
def plot_return_distribution(df, save_path='return_distribution.png'):
"""
收益率分布分析圖
"""
fig, axes = plt.subplots(2, 2, figsize=(14, 10))
in range(1, 5):
for cycle ax = axes[(cycle-1)//2, (cycle-1)%2]
cycle_data = df[df['halving_cycle'] == cycle]['daily_return'].dropna()
if len(cycle_data) > 0:
# 繪製直方圖
ax.hist(cycle_data * 100, bins=50, density=True, alpha=0.7,
color=['green', 'blue', 'orange', 'red'][cycle-1], edgecolor='black')
# 擬合正態分布
mu, std = cycle_data.mean(), cycle_data.std()
x = np.linspace(cycle_data.min(), cycle_data.max(), 100)
ax.plot(x * 100, norm.pdf(x, mu, std) * 100, 'k-',
linewidth=2, label='正態分布擬合')
ax.set_title(f'週期 {cycle} 日收益率分布', fontsize=12, fontweight='bold')
ax.set_xlabel('日收益率 (%)')
ax.set_ylabel('密度')
ax.legend(fontsize=9)
ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig(save_path, dpi=150, bbox_inches='tight')
plt.close()
print(f"收益率分布圖已保存至: {save_path}")
# 生成收益率分布圖
plot_return_distribution(price_data)
數據驅動的投資決策框架
週期相位識別
基於數據分析結果,建立減半週期相位識別系統,幫助投資者判斷當前所處的週期階段。
class HalvingCyclePhaseDetector:
"""
減半週期相位識別器
根據價格行為識別當前所處的週期階段
"""
def __init__(self, price_data):
self.data = price_data
def calculate_momentum_indicators(self):
"""
計算動量指標
"""
df = self.data.copy()
# RSI 計算
delta = df['price'].diff()
gain = (delta.where(delta > 0, 0)).rolling(window=14).mean()
loss = (-delta.where(delta < 0, 0)).rolling(window=14).mean()
rs = gain / loss
df['rsi'] = 100 - (100 / (1 + rs))
# MACD
exp1 = df['price'].ewm(span=12, adjust=False).mean()
exp2 = df['price'].ewm(span=26, adjust=False).mean()
df['macd'] = exp1 - exp2
df['signal'] = df['macd'].ewm(span=9, adjust=False).mean()
# 價格相對均線位置
df['price_ma50_ratio'] = df['price'] / df['price'].rolling(50).mean()
df['price_ma200_ratio'] = df['price'] / df['price'].rolling(200).mean()
return df
def identify_phase(self, current_date=None):
"""
識別當前週期相位
"""
df = self.calculate_momentum_indicators()
if current_date is None:
current_date = df.index[-1]
# 獲取當前數據
current_data = df.loc[:current_date].iloc[-1]
# 識別最近的減半
recent_halving = None
for halving_num, halving_date in collector.halving_dates.items():
if halving_date <= current_date:
recent_halving = halving_num
# 計算距離減半的天數
if recent_halving:
days_since_halving = (current_date - collector.halving_dates[recent_halving]).days
else:
days_since_halving = None
# 判斷相位
phase = "未知"
if days_since_halving is not None:
if days_since_halving < 0:
phase = "減半前醞釀期"
elif days_since_halving < 180:
phase = "減半後上漲期"
elif days_since_halving < 365:
phase = "減半後調整期"
elif days_since_halving < 730:
phase = "減半後盤整期"
else:
phase = "減半後成熟期"
return {
'current_date': current_date,
'recent_halving': recent_halving,
'days_since_halving': days_since_halving,
'phase': phase,
'rsi': current_data['rsi'],
'macd': current_data['macd'],
'price_ma50_ratio': current_data['price_ma50_ratio'],
'price_ma200_ratio': current_data['price_ma200_ratio']
}
# 初始化相位識別器
detector = HalvingCyclePhaseDetector(price_data)
# 識別當前相位
current_phase = detector.identify_phase()
print("\n當前週期相位分析:")
print(f"分析日期: {current_phase['current_date']}")
print(f"最近減半: 第 {current_phase['recent_halving']} 次")
print(f"距離減半天數: {current_phase['days_since_halving']} 天")
print(f"當前相位: {current_phase['phase']}")
print(f"RSI: {current_phase['rsi']:.2f}")
print(f"MACD: {current_phase['macd']:.2f}")
print(f"價格/50日均線: {current_phase['price_ma50_ratio']:.2f}")
print(f"價格/200日均線: {current_phase['price_ma200_ratio']:.2f}")
風險調整投資策略
基於定量分析結果,設計風險調整後的投資策略框架。
class HalvingInvestmentStrategy:
"""
減半週期投資策略
根據數據分析結果優化投資決策
"""
def __init__(self, historical_data):
self.data = historical_data
self.stats = calculate_halving_statistics(historical_data)
def calculate_position_size(self, cycle_phase, volatility):
"""
根據週期相位和波動率計算建議倉位
"""
# 基礎倉位建議(根據歷史數據)
phase_position_map = {
"減半前醞釀期": 0.6,
"減半後上漲期": 0.8,
"減半後調整期": 0.4,
"減半後盤整期": 0.5,
"減半後成熟期": 0.3
}
base_position = phase_position_map.get(cycle_phase, 0.5)
# 根據波動率調整
# 波動率高於平均值時降低倉位
avg_volatility = self.data['volatility_90d'].mean()
volatility_adjustment = min(1.0, avg_volatility / max(volatility, 0.01))
adjusted_position = base_position * volatility_adjustment
return min(adjusted_position, 1.0), base_position
def calculate_stop_loss(self, entry_price, cycle_phase):
"""
根據週期相位計算止損點
"""
# 各相位的歷史最大回撤
phase_drawdown_map = {
"減半前醞釀期": 0.30,
"減半後上漲期": 0.25,
"減半後調整期": 0.40,
"減半後盤整期": 0.35,
"減半後成熟期": 0.50
}
max_drawdown = phase_drawdown_map.get(cycle_phase, 0.30)
stop_loss_price = entry_price * (1 - max_drawdown)
return stop_loss_price
def generate_recommendations(self, current_phase_info):
"""
生成投資建議報告
"""
entry_price = self.data['price'].iloc[-1]
current_volatility = self.data['volatility_90d'].iloc[-1]
position, base = self.calculate_position_size(
current_phase_info['phase'],
current_volatility
)
stop_loss = self.calculate_stop_loss(
entry_price,
current_phase_info['phase']
)
recommendations = {
'entry_price': entry_price,
'suggested_position': f"{position*100:.0f}%",
'base_position': f"{base*100:.0f}%",
'stop_loss_price': stop_loss,
'stop_loss_percentage': (stop_loss/entry_price - 1) * 100,
'phase': current_phase_info['phase'],
'risk_level': '高' if position > 0.7 else '中' if position > 0.4 else '低'
}
return recommendations
# 初始化投資策略
strategy = HalvingInvestmentStrategy(price_data)
# 生成投資建議
recommendations = strategy.generate_recommendations(current_phase)
print("\n投資建議報告:")
print(f"進場價格: ${recommendations['entry_price']:,.2f}")
print(f"建議倉位: {recommendations['suggested_position']}")
print(f"基礎倉位: {recommendations['base_position']}")
print(f"止損價格: ${recommendations['stop_loss_price']:,.2f} ({recommendations['stop_loss_percentage']:.1f}%)")
print(f"當前相位: {recommendations['phase']}")
print(f"風險等級: {recommendations['risk_level']}")
結論與分析要點
本文提供了比特幣減半週期定量數據分析的完整框架,從數據獲取、預處理、視覺化到統計模型建立,以及數據驅動的投資決策系統。透過這些工具和分析方法,投資者可以更系統性地理解比特幣減半週期的價格行為規律,並據此制定更有根據的投資策略。
關鍵發現摘要:
第一,各減半週期呈現不同的價格行為模式。第一次減半後最大漲幅最高,達到約29,000%,後續週期的漲幅雖然相對較小,但絕對收益仍然驚人。這種模式反映了比特幣市場從早期高波動性向成熟市場演進的過程。
第二,波動率在減半前後呈現顯著變化。減半前通常伴隨著波動率下降,表明市場在蓄積能量;而減半後則出現波動率上升,反映出投資者情緒的放大效應。
第三,統計分析顯示比特幣日收益率並不服從正態分布,呈現明顯的厚尾特徵。這意味著極端事件發生的頻率比傳統金融理論預期的更高,投資者需要為此做好風險管理。
第四,基於數據的投資策略框架可以幫助投資者在不同的週期相位做出更理性的決策,但任何模型都有其局限性,過度依賴歷史數據可能無法預測未來的結構性變化。
本分析框架的局限性包括:樣本數量有限(僅有4次減半事件)、比特幣市場仍處於快速發展階段、監管環境和機構參與度的變化可能改變未來週期行為、以及數據來源可能存在偏差和滯後性。建議讀者將本框架作為輔助工具,而非唯一的投資決策依據。
相關文章
- 比特幣減半週期分析 — 理解比特幣減半對價格與市場的影響。
- 比特幣鏈上指標實戰應用:從數據到投資決策 — 全面介紹比特幣鏈上指標的計算方法、數據解讀與投資應用,包含活躍地址數、交易量、Stock-to-Flow模型、已實現市值、長期持有者供給等關鍵指標的實作指南。
- 比特幣減半週期量化模型與價格預測方法論完整指南 — 深入探討比特幣減半週期的量化分析框架,從傳統供需模型到現代機器學習方法論,為投資者提供科學化的比特幣價格分析工具與風險管理策略。
- 比特幣減半週期與機構持倉變化深度追蹤報告:2017-2026 完整量化分析 — 深入追蹤比特幣減半週期與機構持倉變化的歷史數據,全面分析 2017-2026 年間機構採用比特幣的演進歷程與量化影響。
- 比特幣減半週期數據解讀 — 分析比特幣歷次減半的數據規律,幫助投資者理解供應動態與價格週期。
延伸閱讀與來源
這篇文章對您有幫助嗎?
請告訴我們如何改進:
評論
發表評論
注意:由於這是靜態網站,您的評論將儲存在本地瀏覽器中,不會公開顯示。
目前尚無評論,成為第一個發表評論的人吧!