选择指数池中依据过去20日收益率(即动量)排名前两位,同时其收盘价高于过去28日平均价格的指数进行投资。一旦某个持有的指数的动量排名下降到第三名之后,或者其收盘价低于28日均线时,就卖出该指数。然后,用相同的标准筛选并买入新的符合条件的指数进行替换。如果在任何时间点,指数池中没有指数满足上述买入条件,则保持现金仓位,即不持有任何指数。
这种策略旨在通过动态调整投资组合中的指数来追求更高的收益,同时利用动量和均线这两个指标试图捕捉市场趋势并避免潜在的下跌风险。动量参数设为20日,用于识别短期内表现强劲的指数;而均线参数设为28日,则是为了判断指数的价格趋势是否仍然向上。
代码如下
import pandas as pd
import akshare as ak
import yagmail
from datetime import datetime
# QQ 邮箱配置
QQ_EMAIL = "xx" # 发件人邮箱
QQ_PASSWORD ="xx" # 发件人邮箱密码或授权码
TO_EMAIL = "xxx" # 收件人邮箱
# ETF代码与名称的映射
etf_names = {
'510300': '300ETF',
'159915': '创业板',
'513050': '中概互联网ETF',
'159941': '纳指ETF',
'518880': '黄金ETF',
'511090': '30年国债ETF'
}
# 获取ETF数据的函数
def fetch_etf_data(symbol="510300"):
# 使用 akshare 的 fund_etf_hist_em 接口获取 ETF 数据
df = ak.fund_etf_hist_em(symbol=symbol, period="daily", adjust='qfq')
# 转换日期格式
df['日期'] = pd.to_datetime(df['日期']) # 假设日期列名为 '日期'
df.set_index('日期', inplace=True)
# 重命名列以符合标准格式
df.rename(columns={
'开盘': 'Open',
'最高': 'High',
'最低': 'Low',
'收盘': 'Close',
'成交量': 'Volume'
}, inplace=True)
return df
# 计算动量和均线
def calculate_momentum_and_ma(df, momentum_period=20, ma_period=28):
# 计算动量:当前收盘价与20天前收盘价的百分比变化
df['Momentum'] = df['Close'] / df['Close'].shift(momentum_period) - 1
# 计算28天均线
df['MA'] = df['Close'].rolling(window=ma_period).mean()
return df
# 筛选符合条件的ETF
def select_etfs(etf_list, momentum_period=20, ma_period=28):
etf_data = {}
for symbol in etf_list:
df = fetch_etf_data(symbol)
df = calculate_momentum_and_ma(df, momentum_period, ma_period)
etf_data[symbol] = df
# 获取最新的数据
latest_data = {symbol: df.iloc[-1] for symbol, df in etf_data.items()}
# 收集所有ETF的动量和是否大于均线的信息
all_etfs = []
for symbol, data in latest_data.items():
above_ma = data['Close'] > data['MA']
all_etfs.append((symbol, etf_names[symbol], data['Momentum'], above_ma))
# 按动量排序
all_etfs.sort(key=lambda x: x[2], reverse=True)
# 选择动量排名前两位且收盘价大于均线的ETF
selected_etfs = [(etf[0], etf[1], etf[2]) for etf in all_etfs if etf[3]][:2]
return selected_etfs, all_etfs
# 发送邮件
def send_email(selected_etfs, all_etfs, today):
try:
# 邮件主题
subject = f"ETF动量分析报告 ({today})"
# 邮件正文内容
text_content = f"""
日期: {today}
以下是今日推荐的ETF及其动量分析:
推荐持有:
"""
for symbol, name, momentum in selected_etfs:
text_content += f"{symbol} ({name}) - 动量: {momentum:.2%}\n"
text_content += "\n所有ETF的动量分析(按动量排序):\n"
for symbol, name, momentum, above_ma in all_etfs:
above_ma_str = "大于" if above_ma else "小于"
text_content += f"{symbol} ({name}) - 动量: {momentum:.2%},收盘价 {above_ma_str} 均线\n"
# 初始化 yagmail
yag = yagmail.SMTP(user=QQ_EMAIL, password=QQ_PASSWORD, host='smtp.qq.com', port=465, smtp_ssl=True)
# 发送邮件
yag.send(to=TO_EMAIL, subject=subject, contents=[text_content])
print("邮件发送成功")
except Exception as e:
print(f"邮件发送失败: {e}")
# 主函数
def main():
# 获取当前日期
today = datetime.now().strftime("%Y-%m-%d")
# ETF列表
etf_list = ['510300', '159915', '513050', '159941', '518880', '511090']
# 执行策略
selected_etfs, all_etfs = select_etfs(etf_list)
# 发送邮件
send_email(selected_etfs, all_etfs, today)
# 运行主函数
if __name__ == "__main__":
main()