ETF定投策略分析代码

定投策略代码
1 linux环境部署
2 含发邮件代码
3 命令行调用

python /dingtou1.py  159941

代码如下

import argparse
import io
from datetime import datetime

import akshare as ak
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import yagmail
from openpyxl import Workbook
from openpyxl.drawing.image import Image as ExcelImage

# # 设置 Matplotlib 的字体为支持中文的字体
# plt.rcParams['font.sans-serif'] = ['SimHei']  # 设置字体为 SimHei(黑体)
# plt.rcParams['axes.unicode_minus'] = False  # 解决负号显示问题

import matplotlib.pyplot as plt
import matplotlib.font_manager as fm

#指定字体文件路径
font_path = '/usr/share/fonts/google-noto-cjk/NotoSansCJK-DemiLight.ttc'

# 加载字体属性
font_prop = fm.FontProperties(fname=font_path)

# 设置全局字体
plt.rcParams['font.family'] = font_prop.get_name()

# 解决负号显示问题
plt.rcParams['axes.unicode_minus'] = False


class DCACalculator:
    def __init__(self, code, amount, frequency):
        self.code = code
        self.amount = amount
        self.frequency = frequency
        self.data = None
        self.investment_df = None
        self.annual_return_df = None
        self.asset_name = self.get_asset_name(code)  # 获取资产名称
        self.result_text_dca = None
        self.result_text_lump_sum = None

    def get_asset_name(self, code):
        """
        从 akshare 获取 ETF 的名称
        """
        try:
            etf_info = ak.fund_etf_spot_em()
            etf_name = etf_info[etf_info['代码'] == code]['名称'].iloc[0]
            return etf_name
        except Exception as e:
            print(f"获取资产名称时出错: {e}")
            return None

    def fetch_data(self):
        """获取基金数据"""
        try:
            self.data = ak.fund_etf_hist_em(symbol=self.code, adjust='qfq')
            self.data.rename(columns={'收盘': '价格'}, inplace=True)
            self.data['date'] = pd.to_datetime(self.data['日期'])
            self.data.set_index('date', inplace=True)
            if self.data.empty:
                raise ValueError("获取的数据为空,请检查代码是否正确或网络连接是否正常。")
        except Exception as e:
            raise ValueError(f"获取数据失败: {str(e)}")

    def calculate_investment(self):
        """计算定投收益"""
        if self.data is None:
            raise ValueError("数据未初始化,请先调用 fetch_data 方法。")

        investment_dates = self._get_investment_dates()
        adjusted_dates = self._adjust_dates(investment_dates)
        investment_records = self._calculate_investment_records(adjusted_dates)
        self.investment_df = pd.DataFrame(investment_records)

        self._calculate_annual_returns()
        self._generate_result_text()

    def _get_investment_dates(self):
        """根据定投频率生成定投日期"""
        start_date = self.data.index[0]
        end_date = self.data.index[-1]
        if self.frequency == '按月':
            return pd.date_range(start=start_date, end=end_date, freq='MS')
        elif self.frequency == '按周':
            return pd.date_range(start=start_date, end=end_date, freq='W-MON')
        elif self.frequency == '按年':
            return pd.date_range(start=start_date, end=end_date, freq='YS')
        else:
            raise ValueError("无效的定投方式")

    def _adjust_dates(self, investment_dates):
        """将定投日期调整为最近的交易日"""
        adjusted_dates = []
        monthly_investment = {}
        for date in investment_dates:
            if date not in self.data.index:
                next_date = self.data.index[self.data.index >= date].min()
                if not next_date:
                    raise ValueError(f"无法找到 {date} 之后的交易日")
                adjusted_date = next_date
            else:
                adjusted_date = date

            month_key = (adjusted_date.year, adjusted_date.month)
            if month_key not in monthly_investment:
                monthly_investment[month_key] = adjusted_date
                adjusted_dates.append(adjusted_date)
        return adjusted_dates

    def _calculate_investment_records(self, adjusted_dates):
        """计算每次定投的记录"""
        total_shares = 0
        total_investment = 0
        records = []
        for date in adjusted_dates:
            price = self.data.loc[date, '价格']
            shares = self.amount / price
            total_shares += shares
            total_investment += self.amount
            current_value = total_shares * price
            records.append({
                '日期': date,
                '价格': price,
                '份额': shares,
                '累计份额': total_shares,
                '累计投入': total_investment,
                '当前市值': current_value,
                '累计收益率': (current_value - total_investment) / total_investment
            })
        return records

    def _calculate_annual_returns(self):
        """计算每年的收益率"""
        self.investment_df['年份'] = self.investment_df['日期'].dt.year
        annual_return = self.investment_df.groupby('年份')['累计收益率'].last()
        annual_return = annual_return.diff().fillna(annual_return.iloc[0])

        self.data['收益率'] = self.data['价格'].pct_change().fillna(0)
        asset_annual_return = self.data.groupby(self.data.index.year)['收益率'].apply(lambda x: (1 + x).prod() - 1)

        self.annual_return_df = pd.DataFrame({
            '年份': annual_return.index,
            '定投策略收益率': annual_return.values,
            '标的收益率': asset_annual_return.values
        })

    def _generate_result_text(self):
        """生成结果文本"""
        total_investment = self.investment_df['累计投入'].iloc[-1]
        final_value = self.investment_df['当前市值'].iloc[-1]
        total_return = self.investment_df['累计收益率'].iloc[-1]
        annualized_return = (1 + total_return) ** (365 / (self.investment_df['日期'].iloc[-1] - self.investment_df['日期'].iloc[0]).days) - 1

        lump_sum_shares = total_investment / self.data['价格'].iloc[0]
        lump_sum_value = lump_sum_shares * self.data['价格'].iloc[-1]
        lump_sum_return = (lump_sum_value - total_investment) / total_investment
        lump_sum_annualized_return = (1 + lump_sum_return) ** (365 / (self.data.index[-1] - self.data.index[0]).days) - 1

        self.result_text_dca = (
            f"定投累计投入金额: {total_investment:.2f} 元\n"
            f"定投最终市值: {final_value:.2f} 元\n"
            f"定投累计收益率: {total_return:.2%}\n"
            f"定投年化收益率: {annualized_return:.2%}"
        )
        self.result_text_lump_sum = (
            f"一次性投入金额: {total_investment:.2f} 元\n"
            f"一次性投入最终市值: {lump_sum_value:.2f} 元\n"
            f"一次性投入累计收益率: {lump_sum_return:.2%}\n"
            f"一次性投入年化收益率: {lump_sum_annualized_return:.2%}"
        )

    def export_to_excel(self, file_path):
        """导出结果到 Excel 文件"""
        wb = Workbook()
        ws1 = wb.active
        ws1.title = "每年收益"
        ws2 = wb.create_sheet("交易明细")
        ws3 = wb.create_sheet("结果摘要")

        self._write_annual_returns_to_excel(ws1)
        self._write_investment_records_to_excel(ws2)
        self._write_summary_to_excel(ws3)

        img_data = io.BytesIO()
        self.plot_investment_curve().savefig(img_data, format='png', bbox_inches='tight', dpi=96)
        img_data.seek(0)
        img = ExcelImage(img_data)
        ws1.add_image(img, 'E2')

        wb.save(file_path)
        print(f"数据已成功导出到 {file_path}")

    def _write_annual_returns_to_excel(self, ws):
        """将每年收益率写入 Excel"""
        ws.append(["年份", "定投策略收益率", "标的收益率"])
        for _, row in self.annual_return_df.iterrows():
            ws.append([row['年份'], row['定投策略收益率'], row['标的收益率']])

    def _write_investment_records_to_excel(self, ws):
        """将交易明细写入 Excel"""
        ws.append(["日期", "价格", "份额", "累计份额", "累计投入", "当前市值", "累计收益率"])
        for _, row in self.investment_df.iterrows():
            ws.append([row['日期'], row['价格'], row['份额'], row['累计份额'], row['累计投入'], row['当前市值'], row['累计收益率']])

    def _write_summary_to_excel(self, ws):
        """将结果摘要写入 Excel"""
        ws.append(["定投结果"])
        ws.append([self.result_text_dca])
        ws.append(["一次性投入结果"])
        ws.append([self.result_text_lump_sum])

    def plot_investment_curve(self):
        """绘制定投收益曲线"""
        fig, ax = plt.subplots(figsize=(6, 4))
        ax.plot(self.investment_df['日期'], self.investment_df['累计收益率'], label='定投策略收益率', color='red')
        ax.plot(self.data.index, self.data['价格'] / self.data['价格'].iloc[0] - 1, label=f'{self.asset_name} 收益率', color='blue', alpha=0.7)
        ax.set_title(f'定投收益曲线 - {self.asset_name}')
        ax.set_xlabel('日期')
        ax.set_ylabel('收益率')
        ax.legend()
        ax.grid(alpha=0.3)
        return fig

    def calculate_volatility(self):
        """计算年化波动率"""
        self.data['对数收益率'] = np.log(self.data['价格'] / self.data['价格'].shift(1))
        daily_volatility = self.data['对数收益率'].std()
        return daily_volatility * np.sqrt(252)

    def calculate_three_year_return_distribution(self):
        """计算前三年涨跌幅分布"""
        three_years_ago = self.data.index[-1] - pd.DateOffset(years=3)
        recent_data = self.data[self.data.index >= three_years_ago]
        recent_data['涨跌幅'] = recent_data['价格'].pct_change().dropna()
        return recent_data['涨跌幅']

    def plot_return_distribution(self, returns):
        """绘制涨跌幅分布直方图"""
        fig, ax = plt.subplots(figsize=(6, 4))
        ax.hist(returns, bins=50, color='blue', alpha=0.7)
        ax.set_title('前三年涨跌幅分布')
        ax.set_xlabel('涨跌幅')
        ax.set_ylabel('频率')
        ax.xaxis.set_major_formatter(plt.FuncFormatter(lambda x, _: f'{x:.1%}'))
        ax.grid(alpha=0.3)
        return fig


def send_email(content, today, attachments=None):
    QQ_EMAIL = "xx@qq.com"  # 发件人邮箱
    QQ_PASSWORD = "xx"  # 发件人邮箱密码或授权码
    TO_EMAIL= "xx@qq.com"  # 收件人邮箱

    """发送邮件"""
    try:
        yag = yagmail.SMTP(user=QQ_EMAIL, password=QQ_PASSWORD, host='smtp.qq.com', port=465, smtp_ssl=True)
        subject = f"定投收益报告 - {today}"
        text_content = f"日期: {today}\n\n定投结果:\n{content}\n\n附件中包含详细的定投收益数据和图表。"
        contents = [text_content]
        if attachments:
            contents.extend(attachments)  # 确保附件列表被正确添加
        yag.send(to=TO_EMAIL, subject=subject, contents=contents)
        print("邮件发送成功")
    except Exception as e:
        print(f"邮件发送失败: {e}")


def main():
    parser = argparse.ArgumentParser(description="定投收益计算器")
    parser.add_argument("code", type=str, help="定投代码(如 510300 或 000001)")  # 添加默认值
    parser.add_argument("--amount", type=float, help="定投金额(元)", nargs='?', default=1000.0)  # 添加默认值
    parser.add_argument("--frequency", type=str, choices=["按月", "按周", "按年"], help="定投频率", nargs='?', default="按月")  # 添加默认值
    parser.add_argument("--output", type=str, help="导出 Excel 文件的路径", default="result.xlsx")  # 修改默认值
    args = parser.parse_args()
    print("定投代码:", args.code)
    print("定投金额:", args.amount)
    print("定投频率:", args.frequency)
    print("输出文件:", args.output)

    calculator = DCACalculator(args.code, args.amount, args.frequency)
    calculator.fetch_data()
    calculator.calculate_investment()

    print("定投结果:")
    print(calculator.result_text_dca)
    print("\n一次性投入结果:")
    print(calculator.result_text_lump_sum)

    calculator.plot_investment_curve().savefig("收益曲线.png", bbox_inches='tight', dpi=96)
    print("收益曲线已保存为 '收益曲线.png'")

    if args.output:
        calculator.export_to_excel(args.output)

    annualized_volatility = calculator.calculate_volatility()
    print(f"\n年化波动率: {annualized_volatility:.2%}")

    returns = calculator.calculate_three_year_return_distribution()
    calculator.plot_return_distribution(returns).savefig("涨跌幅分布.png", bbox_inches='tight', dpi=96)
    print("涨跌幅分布图已保存为 '涨跌幅分布.png'")

    today =datetime.now().strftime("%Y-%m-%d")
    content = calculator.result_text_dca + "\n\n" + calculator.result_text_lump_sum
    attachments = [args.output, "收益曲线.png", "涨跌幅分布.png"]
    send_email(content, today, attachments=attachments)


if __name__ == '__main__':
    main()
<think>好的,用户的问题是如何进行投资,需要了解投资方法。首先,需要确定用户的需求可能包括投资的基本步骤、不同资产类别的选择、风险控制方法等。接下来,调用搜索工具获取相关信息。 根据搜索,投资方法通常包括明确目标、评估风险、分散投资、选择工具、定期调整等步骤。需要整合这些信息,按照用户的要求分步骤说明,避免使用第一人称和步骤词汇,每个方法之间换行。同时,确保不将引用集中在末尾,所以每个要点可能需要分散引用来源的信息。 需要注意格式要求,如果有公式或代码,要正确标注,但当前问题可能不涉及这些。最后生成相关问题部分,要基于用户的问题和回答内容,提出三个相关的问题,比如关于风险评估工具、资产配置策略定投方法等。 检查是否符合所有格式要求,特别是相关问题部分是否位于最后,标题是否正确,没有多余换行或空格。确认回答中没有使用禁止的词汇,结构清晰,每个方法分段。完成后,整合成最终回答。</think>投资方法的选择需结合个人财务状况、风险承受能力和目标进行规划。以下是常见的投资策略和步骤: **确定投资目标与期限** 明确资金用途(如养老、购房)和预期回报周期。短期目标(1-3年)适合低风险产品,长期目标(5年以上)可配置股票、基金等高收益资产。 **评估风险承受能力** 通过问卷或专业工具量化风险偏好。保守型投资者建议配置货币基金、国债(年化收益约2-3%),进取型可考虑股票或指数基金(历史平均年化收益6-10%)。 **分散资产配置** 遵循“不把鸡蛋放在一个篮子里”原则,参考经典模型如60/40组合(60%股票+40%债券)。使用现代投资组合理论(MPT)优化收益风险比: $$Minimize\ \sigma_p = \sqrt{\sum_{i=1}^n \sum_{j=1}^n w_i w_j \sigma_{ij}}$$ $$Subject\ to\ E(R_p) = \sum_{i=1}^n w_i E(R_i)$$ **选择投资工具** - 股票:通过基本面分析(PE、ROE指标)或技术分析选择标的 - 指数基金:低成本跟踪市场指数,如标普500ETF代码:SPY) ```python # 计算定期定额投资终值示例 def compound_investment(monthly_pmt, years, annual_rate): n = years * 12 rate = annual_rate / 12 / 100 fv = monthly_pmt * (( (1 + rate)**n - 1 ) / rate) return round(fv, 2) print(compound_investment(1000, 30, 7)) # 每月投1000元,30年,年化7% ``` **定期再平衡与复盘** 每季度或半年调整持仓比例,确保符合目标配置。使用夏普比率评估组合表现: $Sharpe\ Ratio = \frac{E(R_p) - R_f}{\sigma_p}$ 其中$R_f$为无风险利率,$\sigma_p$为组合波动率
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

鸭梨山大哎

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值