## 算術平均 vs. 幾何平均的陷阱 首先,我們要打破一個直覺。假設你有 100 元,投資一個資產。第一年漲了 50%(變成 150 元)。第二年跌了 50%(變成 75 元)。 直覺告訴你:漲 50% 跌 50%,平均起來應該是不賺不賠吧?錯!你虧了 25 元(-25%)。這就是金融學著名的「波動性拖累」(Volatility Drag)。 * 算術平均(Arithmetic Mean): $(+50\% - 50\%) / 2 = 0\%$。這是你以為的期望值。$$R_A = \frac{1}{N} \sum_{i=1}^{N} r_i$$ * 幾何平均(Geometric Mean): 這是你實際口袋裡的錢,它永遠小於或等於算術平均。 $$R_G = \left( \prod_{i=1}^{N} (1+r_i) \right)^{1/N} - 1$$ $$R_G \approx R_A - \frac{\sigma^2}{2}$$ 波動越大,這兩者之間的差距(拖累)就越大。傳統投資者視波動為「風險」,因為它會吃掉你的複利增長。但「蛛網收割」策略者卻視波動為「資源」,他們想辦法把這個被吃掉的收益「吐」出來。 ## 薛農的惡魔:如何利用「再平衡」逆天改命? 想像一支股票價格隨機亂跳,長期趨勢不明。如果你只是「買入持有」(Buy and Hold),你的資產也會跟著亂跳,甚至因為波動性拖累而縮水。 但如果你採用「50/50 再平衡策略」: 當你只有一半資金在股票裡,整個投資組合的波動率 $\sigma_p$ 會變成原來的一半:$$\sigma_p = 0.5 \times \sigma$$ 將資金分為兩半:50% 持有現金,50% 持有股票。 每天(或定期)檢查: * 若股票漲了,股票佔比會超過 50%,你就賣出多餘的股票變現(獲利了結)。 * 若股票跌了,股票佔比會低於 50%,你就動用現金買入股票(低價吸籌)。 代入到幾何收益公式 $$R_G(\text{Rebalanced}) = 0.5\mu - 0.125\sigma^2$$ 即便股票價格最終跌回原點,甚至微跌,只要過程中波動夠大,你通過不斷的「高拋低吸」,帳戶總資金竟然是增長的!你收割的不是股價的漲幅,而是波動本身 ## 槓桿 $f$ 的數學秘密 為什麼簡單的「維持固定比例」就能賺錢?這背後有一個精妙的數學公式控制著你的交易行為。 我們定義 $f$ 為你的目標槓桿率(Target Leverage)。 如果你全額買股,不留現金,$f=1$。如果你一半現金一半股,$f=0.5$。當價格變動率為 $r$ 時,為了維持槓桿 $f$ 不變,你需要進行的交易量 $\Delta V$ 是:$$\Delta V = \frac{(f-1)r}{1+r} \times \text{初始持倉}$$ 1. 當 $f=1$(買入持有):$(1-1) = 0$,交易量為 0。你不做任何操作,命運完全交給市場。 2. 當 $f > 1$(融資加槓桿):$(f-1)$ 是正數。價格漲($r>0$)$\rightarrow$ 你要買入(加倉)。價格跌($r<0$)$\rightarrow$ 你要賣出(停損)。這是典型的「追漲殺跌」,適合大牛市,但在震盪市中會被雙巴掌打死。 3. 當 $f < 1$(蛛網收割,例如 $f=0.5$):$(f-1)$ 是負數。價格漲($r>0$)$\rightarrow$ 分子為負 $\rightarrow$ 你自動賣出。價格跌($r<0$)$\rightarrow$ 分子為正 $\rightarrow$ 你自動買入。 ## 策略比較 策略 A:死多頭($f=1$)100 $\to$ 32 $\to$ 100。 * 結果:不賺不賠,浪費時間,還嚇出一身冷汗。 策略 B:固定金額投資(定投/馬丁格爾) * 越跌越買,而且堅持每次都要買固定金額。 * 風險:在跌到 32 元的過程中,你的現金可能早就燒光了,甚至面臨爆倉風險。這是在賭命。 策略 C:波動收割($f=0.5$ 固定槓桿) * 下跌時: 雖然總資產縮水,但因為你需要維持 50% 倉位,你會用手中的現金不斷買入變得便宜的籌碼。 * 觸底時: 在 32 元時,你手中的股數遠多於 100 元時。 * 上漲時: 隨著價格回升,你開始慢慢賣出股票,鎖定利潤。 * 結局: 當價格回到 100 元時,你的資產總值會超過初始本金(例如變成 106 元)。那多出來的 6% 是哪來的?它不來自資產增值(因為價格沒變),它來自於你在下跌途中低價囤積的籌碼,在上漲途中高價賣出的差價。這就是「再平衡溢價」(Rebalancing Premium) ## 注意事項 1. 選對標的: 這套策略最怕「跌下去不回來」(歸零)。所以不要用在單一垃圾股或高風險小幣上。指數型 ETF(如 SPY, 0050)或一籃子資產是最佳選擇,因為它們長期不會歸零且波動性適中。 1. 資金管理是關鍵: 堅持「固定槓桿」(Fixed Leverage)而非「固定金額」。固定槓桿能確保你永遠有現金抄底,且在暴跌時不會爆倉(因為你的虧損是按比例的)。 1. 注意摩擦成本: 頻繁的買賣會產生手續費和稅。不要股價跳動 0.1% 就去再平衡。設定一個閾值(Threshold),例如當倉位偏離目標 5% 時再動手,這樣能最大化「波動性紅利」與「交易成本」之間的效益。 ## 推導過程 ---- 槓桿公式 $$E_0 = \frac{P_0 V_0}{f}$$ * $E_0$ (Equity):你的本金(你實際掏出的錢)。 * $P_0 V_0$ (Position Value):房子/股票的總價值(單價 $\times$ 數量)。 * $f$ (Leverage):槓桿倍數(你把本金放大了幾倍)。 利用「槓桿倍數 $f$」來快速判斷負債比例 $$E_0 - P_0 V_0 = P_0 V_0 (\frac{1}{f} - 1)$$ * 若 $f=2$ (2倍槓桿):$(\frac{1}{2} - 1) = -0.5$。 $\rightarrow$ 負債是總倉位的一半。 什麼是 $E_{temp}$? Mark-to-Market(市值計價) 的意思是:雖然你還沒賣掉資產,但如果現在立刻結算,你的帳戶有多少錢? $$E_{temp} = E_0 + \text{Profit/Loss}$$ $$\text{Profit} = (P_1 - P_0) \times V_0$$ $P_1 = P_0(1+r)$ $$\text{Profit} = P_0 r V_0$$ $$E_{temp} = \underbrace{\frac{P_0 V_0}{f}}_{\text{本金}} + \underbrace{P_0 r V_0}_{\text{賺的錢}}$$ $$E_{temp} = P_0 V_0 \times \left( \frac{1}{f} + r \right)$$ 現在的總權益,等於 「原本資產總值」去乘以「(本金比率 + 漲跌幅)」。 ## 再平衡推導 $$P_1 V_1 = E_1 \times f$$ * $P_1 V_1$:新的總資產價值(新價格 $\times$ 新數量)。 * $E_1 \times f$:新的本金 $\times$ 設定的槓桿倍數。 $$P_0 (1+r) V_1 = \left[ P_0 V_0 (\frac{1}{f} + r) \right] \times f$$ * 左邊:把 $P_1$ 換成 $P_0(1+r)$。 * 右邊:把 $E_1$ 換成上一題算出來的公式 $P_0 V_0 (\frac{1}{f} + r)$。 $$V_0 (\frac{1}{f} + r) \times f$$ $$(1+r) V_1 = V_0 (1 + fr)$$ $$(1+r)(V_0 + \Delta V) = V_0 + frV_0$$ $$V_0 + \Delta V + rV_0 + r\Delta V = V_0 + frV_0$$ $$\Delta V + rV_0 + r\Delta V = frV_0$$ $$\Delta V + r\Delta V = frV_0 - rV_0$$ $$\Delta V(1+r) = (f-1)rV_0$$ $$\Delta V = \frac{(f-1)r}{1+r} V_0$$ ## 程式碼回測 ``` # @title 蛛網收割 (Fixed Leverage) 策略回測與最佳化引擎 # @markdown 請直接執行此區塊進行模擬。你可以修改參數來測試不同標的。 import numpy as np import pandas as pd import yfinance as yf import matplotlib.pyplot as plt import seaborn as sns from scipy.optimize import minimize_scalar # ========================================== # 1. 設定參數 (Configuration) # ========================================== TICKER = "LEU" # @param {type:"string"} START_DATE = "2025-08-01" # @param {type:"string"} END_DATE = "2025-12-26" # @param {type:"string"} INITIAL_CAPITAL = 100000 # @param {type:"number"} TARGET_LEVERAGE = 0.5 # @param {type:"number"} COST_RATE = 0.001 # @param {type:"number"} # ========================================== # 2. 核心策略邏輯 (Core Strategy Logic) # ========================================== def run_strategy(prices, target_leverage, threshold, cost_rate, initial_capital): """ 執行固定槓桿策略,並帶有再平衡閾值與交易成本。 """ n = len(prices) cash_arr = np.zeros(n) shares_arr = np.zeros(n) equity_arr = np.zeros(n) trade_count = 0 turnover = 0.0 # 初始化 cash = initial_capital shares = 0 # 第一筆交易:建立初始部位 target_position = initial_capital * target_leverage shares = target_position / prices[0] transaction_val = shares * prices[0] cost = transaction_val * cost_rate cash = initial_capital - transaction_val - cost trade_count += 1 cash_arr[0] = cash shares_arr[0] = shares equity_arr[0] = cash + shares * prices[0] for t in range(1, n): # Mark-to-Market current_price = prices[t] current_position_val = shares * current_price current_equity = cash + current_position_val # 計算目標部位與偏差 target_position_val = current_equity * target_leverage deviation = current_position_val - target_position_val deviation_pct = abs(deviation) / current_equity if current_equity > 0 else 0 # 檢查是否觸發閾值 if deviation_pct > threshold: # 執行再平衡 trade_val = -deviation trade_cost = abs(trade_val) * cost_rate # 更新狀態 shares = shares + (trade_val / current_price) cash = cash - trade_val - trade_cost trade_count += 1 turnover += abs(trade_val) # 記錄當前狀態 cash_arr[t] = cash shares_arr[t] = shares equity_arr[t] = cash + shares * current_price return equity_arr, trade_count, turnover # ========================================== # 3. 績效評估指標 (Performance Metrics) # ========================================== def calculate_metrics(equity_curve): """ 計算關鍵績效指標 """ returns = pd.Series(equity_curve).pct_change().dropna() # CAGR total_days = len(equity_curve) years = total_days / 252 cagr = (equity_curve[-1] / equity_curve[0]) ** (1 / years) - 1 if years > 0 else 0 # Volatility (Annualized) vol = returns.std() * np.sqrt(252) if len(returns) > 0 else 0 # Sharpe Ratio sharpe = (cagr / vol) if vol != 0 else 0 # Max Drawdown peak = pd.Series(equity_curve).expanding(min_periods=1).max() drawdown = (pd.Series(equity_curve) - peak) / peak max_dd = drawdown.min() # Calmar Ratio calmar = cagr / abs(max_dd) if max_dd != 0 else 0 # Sortino Ratio downside_returns = returns[returns < 0] downside_std = downside_returns.std() * np.sqrt(252) if len(downside_returns) > 0 else 0 sortino = (cagr / downside_std) if downside_std != 0 else 0 return { "CAGR": cagr, "Volatility": vol, "Sharpe": sharpe, "MaxDD": max_dd, "Calmar": calmar, "Sortino": sortino } # ========================================== # 4. 執行與數據獲取 # ========================================== print(f"下載數據: {TICKER}...") data = yf.download(TICKER, start=START_DATE, end=END_DATE, progress=False) if len(data) == 0: print("下載失敗,改用隨機幾何布朗運動 (GBM) 模擬...") np.random.seed(42) days = 1000 mu = 0.1 sigma = 0.5 dt = 1/252 prices = 100 * np.exp(np.cumsum((mu - 0.5 * sigma**2) * dt + sigma * np.sqrt(dt) * np.random.standard_normal(days))) dates = pd.date_range(start=START_DATE, periods=days) data = pd.DataFrame({"Close": prices}, index=dates) prices = data['Close'].values else: # 處理 yfinance 多層索引 if isinstance(data.columns, pd.MultiIndex): prices = data['Close'].iloc[:, 0].values else: prices = data['Close'].values # 基準:買入持有 bnh_equity = (prices / prices[0]) * INITIAL_CAPITAL # ========================================== # 5. 最佳化閾值 (Optimization Loop) # ========================================== print("開始最佳化閾值 (Grid Search)...") thresholds = np.linspace(0.0, 0.15, 30) results = [] for th in thresholds: eq, count, turn = run_strategy(prices, TARGET_LEVERAGE, th, COST_RATE, INITIAL_CAPITAL) mets = calculate_metrics(eq) mets['Threshold'] = th mets['Trade_Count'] = count mets['Final_Equity'] = eq[-1] results.append(mets) results_df = pd.DataFrame(results) # 找出最佳閾值 best_result = results_df.loc[results_df['Sharpe'].idxmax()] best_threshold = best_result['Threshold'] # 使用最佳閾值再跑一次 final_equity, final_count, final_turn = run_strategy(prices, TARGET_LEVERAGE, best_threshold, COST_RATE, INITIAL_CAPITAL) final_metrics = calculate_metrics(final_equity) # ========================================== # 6. 視覺化與報告 # ========================================== plt.figure(figsize=(14, 10)) plt.style.use('seaborn-v0_8-darkgrid') # 圖 1: 權益曲線比較 plt.subplot(2, 2, 1) plt.plot(data.index, bnh_equity, label='Buy & Hold (100%)', color='gray', alpha=0.6, linewidth=2) plt.plot(data.index, final_equity, label=f'Spiderweb (Lev={TARGET_LEVERAGE}, Th={best_threshold:.1%})', color='blue', linewidth=2) plt.title(f'Strategy Comparison: {TICKER}', fontsize=14, fontweight='bold') plt.ylabel('Equity ($)', fontsize=12) plt.legend(fontsize=10) plt.grid(True, alpha=0.3) # 圖 2: 閾值對績效的影響 plt.subplot(2, 2, 2) plt.plot(results_df['Threshold']*100, results_df['Sharpe'], marker='o', color='green', linewidth=2) plt.axvline(best_threshold*100, color='red', linestyle='--', alpha=0.7, label=f'Best: {best_threshold:.1%}') plt.title('Threshold vs Sharpe Ratio', fontsize=14, fontweight='bold') plt.xlabel('Rebalance Threshold (%)', fontsize=12) plt.ylabel('Sharpe Ratio', fontsize=12) plt.legend(fontsize=10) plt.grid(True) # 圖 3: 閾值對交易次數的影響 plt.subplot(2, 2, 3) plt.plot(results_df['Threshold']*100, results_df['Trade_Count'], marker='x', color='red', linewidth=2) plt.title('Threshold vs Trade Count', fontsize=14, fontweight='bold') plt.xlabel('Rebalance Threshold (%)', fontsize=12) plt.ylabel('Number of Trades', fontsize=12) plt.grid(True) # 圖 4: 回撤比較 plt.subplot(2, 2, 4) bnh_peak = pd.Series(bnh_equity).expanding(min_periods=1).max() bnh_dd = (pd.Series(bnh_equity) - bnh_peak) / bnh_peak strategy_peak = pd.Series(final_equity).expanding(min_periods=1).max() strategy_dd = (pd.Series(final_equity) - strategy_peak) / strategy_peak plt.plot(data.index, bnh_dd*100, label='B&H Drawdown', color='gray', alpha=0.6) plt.plot(data.index, strategy_dd*100, label='Strategy Drawdown', color='blue', linewidth=2) plt.title('Drawdown Comparison', fontsize=14, fontweight='bold') plt.ylabel('Drawdown (%)', fontsize=12) plt.legend(fontsize=10) plt.grid(True, alpha=0.3) plt.tight_layout() plt.show() # 文字報告 print("\n" + "="*50) print(f"最佳化結果分析 ({TICKER})") print("="*50) print(f"測試期間: {START_DATE} 至 {END_DATE}") print(f"初始資金: ${INITIAL_CAPITAL:,.0f}") print(f"目標槓桿: {TARGET_LEVERAGE:.1%}") print(f"交易成本: {COST_RATE:.2%}") print("-" * 50) print(f"最佳再平衡閾值: {best_threshold:.2%}") print(f"策略年化報酬 (CAGR): {final_metrics['CAGR']:.2%}") print(f"策略波動率 (Vol): {final_metrics['Volatility']:.2%}") print(f"策略夏普值 (Sharpe): {final_metrics['Sharpe']:.2f}") print(f"策略索提諾比率 (Sortino): {final_metrics['Sortino']:.2f}") print(f"策略最大回撤 (MaxDD): {final_metrics['MaxDD']:.2%}") print(f"策略卡瑪比率 (Calmar): {final_metrics['Calmar']:.2f}") print(f"總交易次數: {final_count}") print(f"期末資金: ${final_equity[-1]:,.0f}") print("-" * 50) bnh_metrics = calculate_metrics(bnh_equity) print(f"買入持有 CAGR: {bnh_metrics['CAGR']:.2%}") print(f"買入持有 Sharpe: {bnh_metrics['Sharpe']:.2f}") print(f"買入持有 MaxDD: {bnh_metrics['MaxDD']:.2%}") print(f"買入持有期末資金: ${bnh_equity[-1]:,.0f}") print("="*50) # 績效摘要表 print("\n閾值最佳化結果 (前5名):") print(results_df.nlargest(5, 'Sharpe')[['Threshold', 'CAGR', 'Sharpe', 'MaxDD', 'Trade_Count']].to_string(index=False)) ```