主要是讀了99趴大大的動能致富一書簡單寫的筆記:
加速防禦雙動能策略是一個有效的風險管理方法,透過結合相對動能和絕對動能兩種方式,讓投資者在市場上漲時能夠抓住機會,而在市場下跌時則能降低風險,從而達到「進可攻,退可守」的效果。
雙動能策略的基礎的核心原理是「強者恆強」,即過去表現好的資產未來有可能繼續表現良好。雙動能策略運用了兩個概念:
整個運作方式如下:
這種策略之所以有效,主要有兩個原因:
防禦雙動能策略是一個簡單、有效的投資方法,特別適合希望在市場不穩定時保護資本的投資者,通過靈活調整股票與避險資產的配置,這種策略能夠在不同的市場環境中達到較好的風險管理效果,相較於單純的 Buy-and-Hold 策略,防禦雙動能策略雖然可能在牛市中略顯保守,但其在熊市中的防禦能力使得它成為一個值得考慮的長期投資策略。
基於查證精神用 Portfolio Visualizer 驗證看看策略的有效性,把時間拉到2024之後…表現比Buy and Hold VOO差,呃……
ANYWAY,分享一下加速防禦雙動能的判斷小程式(每個月初執行一次)
import yfinance as yf
import pandas as pd
import sqlite3
from datetime import datetime, timedelta
# 設定 ETF 代號
symbols = ['VOO', 'SCZ', 'TLT']
# 建立 SQLite 連接
conn = sqlite3.connect('etf_data.db')
c = conn.cursor()
# 建表格(如果尚未存在)儲存每月的收盤價
c.execute('''
CREATE TABLE IF NOT EXISTS etf_monthly_close (
symbol TEXT,
date TEXT,
close_price REAL
)
''')
# 抓取 ETF 的歷史資料(過去 7 個月)
def fetch_and_store_data(symbols, conn):
end_date = datetime.now().strftime('%Y-%m-%d')
start_date = (datetime.now() - timedelta(days=7*30)).strftime('%Y-%m-%d') # 抓取過去7個月的數據
for symbol in symbols:
#print(f"正在取得 {symbol} 的資料...")
# 抓取數據
data = yf.download(symbol, start=start_date, end=end_date)
if data.empty:
print(f"未能取得 {symbol} 的資料")
#else:
#print(f"成功取得 {symbol} 的資料:\n{data.tail()}")
data.reset_index(inplace=True)
data['Date'] = pd.to_datetime(data['Date'])
# 找到每個月最後一個交易日的資料
monthly_data = data.groupby(data['Date'].dt.to_period('M')).tail(1)
#print(f"{symbol} 每月最後一個交易日的資料:\n{monthly_data}")
# 檢查資料庫中是否已有該月份的資料,沒有的話才寫入
for _, row in monthly_data.iterrows():
c.execute('''
SELECT * FROM etf_monthly_close
WHERE symbol = ? AND date = ?
''', (symbol, row['Date'].strftime('%Y-%m-%d')))
result = c.fetchone()
if result is None:
# 插入新的數據
c.execute('''
INSERT INTO etf_monthly_close (symbol, date, close_price)
VALUES (?, ?, ?)
''', (symbol, row['Date'].strftime('%Y-%m-%d'), row['Close']))
#print(f"插入 {symbol} 的 {row['Date'].strftime('%Y-%m-%d')} 資料")
conn.commit()
# 計算報酬率的函數
def calculate_returns(df, symbol):
df_symbol = df[df['symbol'] == symbol].copy()
df_symbol.set_index('date', inplace=True)
# 計算 1 個月、3 個月、6 個月報酬率
df_symbol['month_1_return'] = df_symbol['close_price'].pct_change(1)
df_symbol['month_3_return'] = df_symbol['close_price'].pct_change(3)
df_symbol['month_6_return'] = df_symbol['close_price'].pct_change(6)
# 刪除 NaN 值
df_symbol.dropna(inplace=True)
return df_symbol
# 取得資料並存入資料庫
fetch_and_store_data(symbols, conn)
# 從資料庫中讀取所有 symbol 的資料
query = '''
SELECT symbol, date, close_price
FROM etf_monthly_close
ORDER BY symbol, date
'''
df = pd.read_sql(query, conn)
df['date'] = pd.to_datetime(df['date'])
# 確認已經讀取了 VOO、SCZ 和 TLT 的資料
#print(f"從資料庫讀取的資料:\n{df}")
# 檢查每個 symbol 的資料是否足夠計算報酬率
for symbol in symbols:
symbol_data = df[df['symbol'] == symbol]
#print(f"{symbol} 的資料量: {len(symbol_data)} 個資料點")
#print(symbol_data)
# 計算每個 ETF 的報酬率
voo_returns = calculate_returns(df, 'VOO')
scz_returns = calculate_returns(df, 'SCZ')
tlt_returns = calculate_returns(df, 'TLT')
# 計算 VOO、SCZ 和 TLT 的報酬率平均值(1 個月、3 個月、6 個月的平均)
voo_avg_return = voo_returns[['month_1_return', 'month_3_return', 'month_6_return']].mean(skipna=True).mean()
scz_avg_return = scz_returns[['month_1_return', 'month_3_return', 'month_6_return']].mean(skipna=True).mean()
tlt_avg_return = tlt_returns[['month_1_return', 'month_3_return', 'month_6_return']].mean(skipna=True).mean()
# 檢查報酬率是否有 NaN 值
print("VOO 的報酬率:")
print(voo_returns[['month_1_return', 'month_3_return', 'month_6_return']])
print("SCZ 的報酬率:")
print(scz_returns[['month_1_return', 'month_3_return', 'month_6_return']])
print("TLT 的報酬率:")
print(tlt_returns[['month_1_return', 'month_3_return', 'month_6_return']])
# 比較 VOO 和 SCZ 的平均報酬率
if not voo_returns.empty and voo_avg_return > scz_avg_return:
if voo_avg_return > 0:
result = 'VOO'
else:
if not tlt_returns.empty and 'month_1_return' in tlt_returns.columns and not tlt_returns['month_1_return'].isna().all():
if tlt_returns['month_1_return'].iloc[-1] > 0:
result = 'TLT'
else:
result = 'CASH'
else:
result = 'CASH'
else:
if scz_avg_return > 0:
result = 'SCZ'
else:
if not tlt_returns.empty and 'month_1_return' in tlt_returns.columns and not tlt_returns['month_1_return'].isna().all():
if tlt_returns['month_1_return'].iloc[-1] > 0:
result = 'TLT'
else:
result = 'CASH'
else:
result = 'CASH'
# 回傳結果及報酬率
print(f"VOO 平均報酬率: {voo_avg_return:.4f}")
print(f"SCZ 平均報酬率: {scz_avg_return:.4f}")
print(f"TLT 平均報酬率: {tlt_avg_return:.4f}")
print(f"最終選擇: {result}")
# 關閉資料庫連接
conn.close()
強烈懷疑 Buy-and-Hold VOO 是真王道,持續反覆打臉自己為什麼不加入大盤到底!?