使用Spyder进行财务数据分析

在本研讨会结束时,学员将能够有效地使用Spyder,应用一些规范的金融理论和模型,以最大化给定风险水平的预期回报的方式组合资产组合。通过这种方式,基于统计的工具被用来构建投资组合。

必备条件

要学习本研讨会,我们建议您具备Python的一般知识。你可以去参观 The Python Tutorial 学习这种编程语言的基础知识或更新您的Python知识。

您还需要拥有 Anaconda (或 Miniconda )并安装了Spyder。有关Spyder安装的详细信息,请参阅 installation guide

重要

Spyder现在提供 独立安装程序 对于Windows和MacOS,无需下载 Python 或在现有环境中手动安装,即可更轻松地启动和运行应用程序。虽然我们仍然支持 Python ,但我们建议在这些平台上使用这种安装方法,以避免大多数软件包冲突问题和其他问题。

还需要具备以下先验知识:

  • Python的中级级别

  • 统计学基础知识

  • 具备一定的金融计量经济学知识者优先。

学习目标

完成本研讨会后,您应该能够:

  • 将基本统计分析应用于股票和加密货币投资组合,以衡量其表现

  • 了解使用IDE编程的优点,例如使用变量资源管理器检查变量,以及利用绘图窗格与绘图进行交互。

学员配置文件

本研讨会面向对金融感兴趣的人,他们希望使用Python和Spyder迈出金融分析的第一步。

简介

在本研讨会中,我们将从以下位置实时获取财务数据 Yahoo! Finance API,并使用计量经济学和计算工具探索金融投资组合。

为什么要使用Python进行财务分析?

对于那些投资金融工具的人来说,实时分析历史和当前的金融数据是必不可少的。Python具有许多功能,使其成为金融任务的理想之选:

  • 它对任何人来说都很容易学习,无论他们是否有以前的编程经验

  • 它有各种专门的数学和统计类库 (SciPyNumPyPandas ),它们通常用于财务分析

  • 可接入实时加载财务数据的API

  • 它是当今计量经济学中最有用的工具之一,也是机器学习(监督学习、无监督学习、强化学习)资源最多的编程语言

  • 它有很好的绘图库

  • 您可以使用资源,例如 Google ColabBinder 在云中进行分析。

我为什么要使用IDE?

虽然您可以在没有IDE(集成开发环境)的情况下使用Python,但是使用IDE(集成开发环境)会更好。Spyder是一个用Python编写的科学集成开发环境,由科学家、工程师和数据分析师设计。Spyder的功能及其与Python的集成使其成为财务分析的完美工具。

使用Spyder进行财务分析简介

如果您不熟悉Spyder,我们建议您从我们的 Quickstart 。但是如果你想要一个总结,这里有一个快速的概述。

注解

如果您已经有使用Spyder的经验,您可以跳过本部分。

编辑

这个 Editor 是编写代码并将其另存为文件(脚本)的位置。它使您可以轻松地持久化您的工作。您可以在这里编写您希望在IPython控制台中进行的数据分析中保留的代码。 在这里,您还将能够阅读、编辑和运行本研讨会中的代码

IPython控制台

这个 IPython Console 是Spyder的组件,您可以在其中编写要试验的代码块。 在本研讨会中,我们将向您提供一些代码片段,您可以在此控制台中复制并运行这些代码

本质上,IPython控制台允许您使用Python执行命令和与数据交互。

变量资源管理器

这个 Variable Explorer 是Spyder最好的功能之一。它允许您以交互方式浏览和管理在当前所选对象的代码中生成的对象 IPython控制台 会议。

Variable Explorer是本研讨会中最常用的组件之一。 这是我们将在其中观察数据和大多数分析结果(曲线图除外)的窗格

打印窗格

这个 Plots pane 显示在您的IPython控制台会话中创建的所有静电图形和图像。 代码生成的所有绘图都将显示在此组件中 。此窗格还允许您将每个图形保存在本地文件中,或将其复制到剪贴板以与其他人共享。

准备工作

在开始之前,您必须已经安装了运行代码所需的一些包和库。我们建议您在虚拟环境中安装这些要求。在这里,我们将一步一步地解释如何做到这一点。

设置CONDA环境

如果您想让Spyder在专用环境中独立于您的其他软件包进行更新,并避免任何冲突,您可以做到。

您可以通过两种不同的方式设置您的环境。

重要

我们建议使用Anaconda(或Miniconda)创建虚拟环境,因为它与Spyder无缝集成。您可以在以下位置找到安装说明 Anaconda documentation

使用命令

只需在您的Anaconda提示符(Windows)或终端(其他平台)中运行以下命令,即可创建一个名为 financial-analysis

$ conda create -n financial-analysis

要安装Spyder的可选依赖项以实现全部功能,请使用以下命令:

$ conda activate financial-analysis
$ conda install -c conda-forge numpy scipy pandas matplotlib sympy cython spyder-kernels requests multitasking lxml tqdm
$ pip install -i https://pypi.anaconda.org/ranaroussi/simple yfinance
$ pip install Historic-Crypto

重要

Spyder现在提供 独立安装程序 对于Windows和MacOS,无需下载 Python 或在现有环境中手动安装,即可更轻松地启动和运行应用程序。

下载数据集

虽然在研讨会期间我们将解释如何使用一些API下载最新数据,但您也可以从以下地址下载CSV格式的数据集 this link

要完成本研讨会,您不需要创建新目录。但是,如果您已经下载了数据,并且想要使用它而不是API,则必须将包含下载数据的目录设置为工作目录。为此,请检查工作目录是否正确。您应该在右上角看到下载数据所在目录的路径。大概是这样的:

Spyder's Working Directory plugin showing the working directory downloaded-data-dir

在Spyder中设置虚拟环境

让我们检查一下我们创建的虚拟环境是否在Spyder中启用。去 Preferences > Python interpreter ,并使用下面的下拉列表 Use the following Python interpreter 选择您的虚拟环境。您应该会看到类似这样的内容:

Screenshot on how to set up environment in Spyder's Preferences

现在,您已经为继续研讨会做好了一切准备。

下载代码

尽管该研讨会是为您在IPython控制台中编写代码而设计的,但我们已经创建了一个您可以下载的文件 here 。此脚本提供了您将在本研讨会中编写的所有代码,如果您迷路了,您可以使用它作为指南。

获取财务数据

谈到金融,与时俱进是非常重要的。因此,我们将使用一个Python库,它允许我们从以下位置获取更新的历史股票市场记录 Yahoo! Finance 接口。通过这种方式,我们将能够下载我们感兴趣分析的时间段内的数据。

请记住在Spyder右下角的“IPython控制台”中键入并运行此研讨会的所有代码。

Screenshot IPython Console location in Spyder IDE

您也可以在编辑器(占据Spyder整个左侧的窗格)中编写代码。如果使用编辑器,则可以通过选择代码并按下 Run selection or current line 按钮中的 Run toolbar 或通过按下 F9 钥匙。

Editor location in Spyder IDE

要开始,请导入库。

import numpy as np
import pandas as pd
import matplotlib as mpl
from scipy.optimize import minimize
import matplotlib.pyplot as plt
import pprint

from Historic_Crypto import Cryptocurrencies
from Historic_Crypto import HistoricalData
import yfinance as yf

第一组导入用于基本操作。第二个 (Historic_Cryptoyfinance )导入我们将用于下载财务数据的库。

让我们从一个示例开始探索库。我们将通过一行代码获取有关Netflix的财务信息:

netflix = yf.Ticker("NFLX")

我们已经使用了 Ticker 类中的 yfinance 库以创建 netflix 对象。此对象包含我们可以查询以获得各种类型信息的属性和方法。

一般股票信息

如果您想知道可以查询哪些方法和属性,可以使用内置的 help() 功能。

您还可以键入对象的名称 (netflix ),然后键入句点并按Tab键一次。然后将显示IPython建议,帮助您导航对象:

IPython Console auto-suggestions

例如,我们可以使用 info 对象的属性。

netflix_info = netflix.info

我们可以在变量资源管理器的 netflix_info 变量。

First variable in Variable Explorer

如果我们双击它,将显示一个包含详细信息的新窗口。

Python dict example in Variable Explorer

在该窗口中,我们可以看到一个Python字典,其中每个键都有一个赋值。每种类型的值都用不同的颜色表示。例如,我们看到Netflix是一个娱乐业,汇总了业务类型等指标。变量资源管理器是查看这些类型的结果的一种非常方便的方式。我们可以将其与在IPython控制台中使用 pprint 功能:

pprint.pprint(netflix_info)
netflix_info output in IPython Console

虽然 pprint 显示所有信息,则在变量资源管理器中查看它会更容易。

历史股票数据

我们可以使用以下代码行在数据集中下载股票的历史记录:

hist = netflix.history(period="max")

我们可以在变量资源管理器中看到此数据集的摘要和更多详细信息。它显示为DataFrame对象,有7列和数千行。如果我们双击它,我们将看到按升序日期组织的Netflix股票的历史记录。

Netflix historical operations

在整个研讨会中,我们将使用这些历史信息与各种规范的金融理论比较不同类型的金融投资组合。

第一个投资组合

让我们建立我们的第一个股票投资组合!比方说我们对投资技术感兴趣,我们想知道把钱投入这个行业的一些“重量级公司”能获得什么样的业绩。

为了衡量我们第一个投资组合的表现,我们将使用金融界的一个经典理论: mean-variance portfolio (MVP) theory 。该模型假设投资者只关心预期收益和这种收益的方差。分析完全基于基于股价时间序列的统计指标,例如周期性的平均收益以及这些收益的方差具有相同的周期性。

准备投资组合数据

在开始之前,让我们运行几行代码来样式化绘图。这些行是可选的,但我们建议您运行它们,使图形看起来像我们在本研讨会中展示的屏幕截图。

plt.style.use("fivethirtyeight")
mpl.rcParams["savefig.dpi"] = 300
mpl.rcParams["font.family"] = "serif"
np.set_printoptions(precision=5, suppress=True, formatter={"float": lambda x: f"{x:6.3f}"})

假设我们要衡量一个由谷歌、苹果、微软、Netflix和亚马逊股票组成的投资组合的表现。让我们称这套为一套 SYMBOLS_1

SYMBOLS_1 = ["GOOG", "AAPL", "MSFT", "NFLX", "AMZN"]

注解

如果您搜索 SYMBOLS_1 在变量资源管理器中找不到它:Python不将该元素解释为变量,而是将其解释为 常量 。这是因为名称是用大写字母书写的(没有字母是小写的)。默认情况下,变量资源管理器不会显示此内容,但实际上您可以更改 Preferences 才能看到这些常量。

我们将下载此投资组合的历史数据。为此,我们将使用 yfinance download() 函数,该函数将带有符号的字符串作为其第一个参数 (SYMBOLS_1 )。参数的睡觉是开始日期 (start="2012-01-01" ),结束日期 (end="2021-01-01" )以及数据将如何分组 (group_by="Ticker" )。

data_1 = yf.download(" ".join(SYMBOLS_1), start="2012-01-01", end="2021-01-01", group_by="Ticker")

最后一个论点 (group_by="Ticker" )主要按库存对信息进行分组。否则,通过交易的信息类型(例如,开盘价、收盘价、交易量)进行主要分组。在下图中,您可以看到不同之处(上图是默认组织,下图是按“Ticker”分组)。

Data grouped by Ticker

历史数据已作为Pandas DataFrame下载。我们可以在变量中查看这些数据 data_1 在变量资源管理器中。

Portfolio 1 historical data

让我们稍微更改一下数据的格式,使符号显示为列名,并且Ticker从列移动到行。我们将使用 stack() DataFrame的操作。

data_1 = data_1.stack(level=1).rename_axis(["Date", "Ticker"]).reset_index(level=1)
Portfolio 1 historical data stacked by Ticker

从股票行情来看,我们只对每日收盘价感兴趣。因此,我们将只保留以下值 "Close" 然后去掉Ticker专栏。

close_data_1 = data_1[data_1.Ticker == "Close"].drop("Ticker", inplace=False, axis=1)

您会注意到,在变量Explorer中有一个新的DataFrame,名为 close_data_1 具有2,265行和5列。

第一眼看一眼这个公文包

我们想看看,如果我们在2012年至2021年初进行投资,我们的投资组合会有怎样的表现。我们怎样才能得到这个测量值呢?我们来看看每只股票的月度收盘价。为此,我们将对数据进行自动重采样。然后我们将计算相对频率(百分比)的变化。

重采样将使用 resample("M") 方法和使用 pct_change() 方法。结果将存储在 monthly_data_1 变量。

monthly_data_1 = close_data_1.resample("M").ffill().pct_change()

因为每只股票都是一列,所以我们可以使用 mean() 方法以查看结果。

monthly_data_1.mean()

# AMZN    0.029931
# GOOG    0.018784
# MSFT    0.020698
# AAPL    0.023033
# NFLX    0.042060
# dtype: float64

重要

请记住,此收盘信息代表每只股票的相对增长,而不是以任何货币表示的收盘价。

正如我们看到的,所有的结果都是正面的,所以这个投资组合多年来一直是有利可图的。相对而言,涨幅最大的将来自Netflix(0.4%)。这些百分比显示了股票价格每月变动的平均值。但是这个价格有多稳定呢?要找出这一点,我们可以计算股价增长的标准差:

monthly_data_1.std()

# AMZN    0.082308
# GOOG    0.061057
# MSFT    0.058395
# AAPL    0.081239
# NFLX    0.145416
# dtype: float64

波动最大的是Netflix(0.1454),这表明尽管它的价格增长最快,但在这些年里也有很大的变化。另一方面,最稳定的价格是微软的(0.0583)。

同时观察增长和变异性的一个好方法是画一张时间序列图。为了缩放值,我们将把动作收盘价的相对频率除以它在数据框中的初始值 close_data_1 (这些初始值可以通过以下命令找到 close_data_1.iloc[0]) )。该图是用 plot() Pandas DataFrame方法(我们将绘图大小和绘图标题作为参数传递给它)。

(close_data_1/close_data_1.iloc[0]).plot(figsize=(16, 10), title="Portfolio 1 daily stock price")
Portfolio 1 daily stock price

重要

您将能够在Plots窗格中看到该图形。在左边,你会看到详细的情节。在右侧,将创建一个堆栈,其中包含在控制台中生成的所有绘图以及通过直接在编辑器中运行代码生成的所有绘图。您可以用鼠标右键或触控板单击绘图,将其复制、删除或保存到磁盘。

上述时间序列曲线图清楚地显示了所有股票的增长情况,尤其是Netflix。上面讨论的高波动性也可以看到。例如,从情节中可以看出,由于波动性很高,在2014年和2019年投资Netflix几乎没有利润(如果股票在年初买入,在年底卖出)。

我们还可以使用 plot() 方法我们已有的每月数据的DataFrame。为此,我们将向所有值加1,并使用 cumprod() 方法:

(monthly_data_1 + 1).cumprod().plot(figsize=(16, 10), title="Portfolio 1 monthly stock price")
Portfolio 1 monthly stock price

这张月度数据的曲线图比每日数据的曲线图更“平滑”。

收益和波动性

请记住,在 mean-variance portfolio theory 重要的是预期收益和方差。要计算这些回报,我们将把某一天的股票价格除以前一天相同股票的价格。我们将通过将 close_data_1 DataFrame本身的一个版本,在该版本中,我们将每条记录向后移动一个日期 (shift(1) )。例如,如果在2012-01-03日期某只股票的估值为1,第二天(2012-01-02)该股票的估值为2,那么在我们转移的数据集中,2012-01-03这一天该股票的价值将为1。这样,我们将用1除以2,以此类推,并加上所有股票的所有值。我们还将通过将结果传递到对数刻度来标准化结果 np.log()

注解

趋势线更容易以对数刻度绘制,因为它们往往更符合最小值。此外,对数标度给出了更真实的价格走势观点。

rets_1 = np.log(close_data_1/close_data_1.shift(1)).dropna()

在变量中 rets_1 您可以看到生成的DataFrame。如果在变量资源管理器中双击变量名,您将看到有正值(股价上涨)和负值(收盘时股价下跌)。

Portfolio 1 daily returns

除了每只股票的回报外,我们还需要每只股票在投资组合中的具体权重,即每家公司在投资组合中有多少股票。在本研讨会中,我们将假设每家公司都有一份股份,因此权重将平均分配。

注解

股票是一种金融证券,代表你拥有一家公司的一部分(无论这部分的规模有多大)。另一方面,股票是股票的最小面额单位。股票由一股或几股组成。

该权重将是一个向量(Python列表),由投资组合中每只股票的相对权重(介于0和1之间)组成。这些权重的总和必须为1。因为我们将假设每只股票都是一股,所以分布将是相等的: [0.2, 0.2, 0.2, 0.2, 0.2]

weights_1 = [0.2, 0.2, 0.2, 0.2, 0.2]

有了这些信息,我们就可以计算出这个投资组合的预期收益。数学很简单:这是由投资组合权重向量和预期收益向量的点积给出的。这个结果必须乘以要计算回报的天数(一年大约有252个股票收盘价)。

让我们将所有这些放入一个函数中:

def portfolio_return(returns, weights):
    return np.dot(returns.mean(), weights) * 252

我们来看看这个投资组合如果持有一年的预期收益是多少:

portfolio_return(rets_1, weights_1)

# 0.2859066023606343

预计在一年内获得近30%的收益。还不错,对吧?但别忘了这枚硬币的另一面:波动性。这个计算有点复杂。首先,计算收益的年化协方差(乘以一年中的交易天数)和权重的点积。然后得到权重与前面结果的点积。最后,提取该结果的平方根。让我们也将其实现到一个函数中。

def portfolio_volatility(returns, weights):
    return np.dot(weights, np.dot(returns.cov() * 252, weights)) ** 0.5

让我们看看我们的投资组合在波动性方面的表现:

portfolio_volatility(rets_1, weights_1)

# 0.23704031354688784

如果高回报是可取的,那么高波动性就是不可取的。这个投资组合的风险比较大。

夏普比

这个 Sharpe 比率或指数是投资组合表现的衡量标准。它将投资组合的收益与其波动性联系起来,将预期/已实现的收益与预期/已实现的风险进行比较。其计算方法为零风险情况下的实际投资收益与预期收益之差除以投资的波动率。 它提供了每增加一个风险单位所获得的额外回报率的模型

让我们在函数中将其形式化:

def portfolio_sharpe(returns, weights):
    return portfolio_return(returns, weights) / portfolio_volatility(returns, weights)

让我们把这个应用到我们的投资组合中。

portfolio_sharpe(rets_1, weights_1)

# 1.2061518063427656

重要

夏普比率的衡量标准在上下文中理解得最好:当比较两个或更多的投资组合时,夏普比率较高的那个在相同的风险下提供更多的利润。

我们还可以使用 Monte Carlo 模拟将投资组合中每只股票的权重随机化,这样我们就可以看到夏普比率可以变化的范围。通过这种方式,我们可以绘制一些情景,这些情景一起可以让我们很好地洞察预期收益和预期波动性之间的关系。

我们将使用一个函数来实现这一点,我们将在 monte_carlo_sharpe function explained 部分。

def monte_carlo_sharpe(returns, symbols, weights):

    sim_weights = np.random.random((1000, len(symbols)))
    sim_weights = (sim_weights.T / sim_weights.sum(axis=1)).T

    volat_ret = [(portfolio_volatility(returns[symbols], weights), portfolio_return(returns[symbols], weights)) for weights in sim_weights]
    volat_ret = np.array(volat_ret)

    sharpe_ratio = volat_ret[:, 1] / volat_ret[:, 0]

    return volat_ret, sharpe_ratio

警告

您不需要在IPython控制台中键入以下代码。如果你写上面的函数就足够了。它只是一个代码表示,用来解释函数内部的内容。

monte_carlo_sharpe 功能说明

现在让我们分解函数以了解发生了什么。首先,我们创建一个长度为1,000,宽度为1,000的数字数组,表示投资组合中的股票数量。阵列的每一行都有随机权重,它们的总和始终为1:

sim_weights = np.random.random((1000, len(symbols)))
sim_weights = (sim_weights.T / sim_weights.sum(axis=1)).T

下一节使用列表理解来计算新随机权重的波动率和回报。结果列表被转换回Numpy数组:

volat_ret = [(portfolio_volatility(returns[symbols], weights), portfolio_return(returns[symbols], weights)) for weights in sim_weights]
volat_ret = np.array(volat_ret)

最后,我们通过将指数1(波动性)除以数值数组的指数0(回报)来获得夏普比率:

sharpe_ratio = volat_ret[:, 1] / volat_ret[:, 0]

使用 monte_carlo_sharpe 功能

我们使用函数得到投资组合1的模拟收益和波动率 (port_1_vr )和相关的夏普比(Sharpe Ratio (port_1_sr )。

在控制台中输入以下代码。

port_1_vr, port_1_sr = monte_carlo_sharpe(rets_1, SYMBOLS_1, weights_1)

注解

请记住,权重是随机初始化的,因此每次运行此代码时都会得到不同的结果。

这样,我们就为我们的产品组合获得了两个包含1,000个模拟案例的数组。但探索这一点的最好方式是用情节。

plt.figure(figsize=(16, 10))
fig = plt.scatter(port_1_vr[:, 0], port_1_vr[:, 1], c=port_1_sr, cmap="cool")
CB = plt.colorbar(fig)
CB.set_label("Sharpe ratio")
plt.xlabel("expected volatility")
plt.ylabel("expected return")
plt.title(" | ".join(SYMBOLS_1))

您应该在绘图窗格中看到类似以下内容:

Portfolio 1 Monte Carlo Sharpe simulation scatter plot

在回报和波动性之间可以观察到大致的线性关系:波动性越高,收益就越高。夏普比率显示出重要的可变性(它在所画线条的“宽度”中很明显)。

这似乎是一个很好的投资组合,因为它的表现很好,方差不是很大。

最优投资组合权重

我们可以用得到的数据来计算按年计算投资组合的最优权重吗?我们当然可以。让我们首先将前几年定为变量。

start_year, end_year = (2012, 2020)

现在我们将编写一个函数来计算这些最佳权重。

def optimal_weights(returns, symbols, actual_weights, start_y, end_y):

    bounds = len(symbols) * [(0, 1), ]
    constraints = {"type": "eq", "fun": lambda weights: weights.sum() - 1}
    opt_weights = {}

    for year in range(start_y, end_y):
        _rets = returns[symbols].loc[f"{year}-01-01":f"{year}-12-31"]
        _opt_w = minimize(lambda weights: -portfolio_sharpe(_rets, weights), actual_weights, bounds=bounds, constraints=constraints)["x"]
        opt_weights[year] = _opt_w
    return opt_weights

让我们用粗略的笔画来描述这个函数。 bounds 指示投资组合中每只股票的最大和最小权重。对于投资组合中的每只股票,最低权重将为0,最高权重将为1。 constraints 是一个函数,它确保所有操作的权重总和始终为1。然后初始化一个循环,该循环将对每年的数据进行分段。在变量中 _rets 将获得指定年份的报税表。在……里面 _opt_w 这个 portfolio_shape() 函数用于计算最大化夏普比的权重。这是使用 minimize() SciPy的函数(它以 portfolio_shape 函数,我们的股票在投资组合中的实际权重,以及 bounds 以及 constraints 变量)。请注意 - 在此之前签名 portfolio_sharpe ?这是因为 minimize() 目的是找出函数相对于参数的最小值,但我们对最大值感兴趣,因此我们得出以下结果 portfolio_sharpe 一个负面的。

我们将使用刚刚定义的函数来计算每年的最佳权重,并将结果保存在Pandas DataFrame中,以利用Variable Explorer显示选项。

opt_weights_1 = optimal_weights(rets_1, SYMBOLS_1, weights_1, start_year, end_year)
port_1_ow = pd.DataFrame.from_dict(opt_weights_1, orient='index')
port_1_ow.columns = SYMBOLS_1

双击 port_1_ow 变量资源管理器中的变量。在该表中,您可以通过单击窗格左下角的Format按钮指定要显示的小数位数,如下图所示:

Portfolio 1 optimal weights by year

我们将小数位数设置为4,并取消选中 Column min/max 复选框,以便更好地欣赏行值(年份)中的对比度。

例如,你可以看到,2015年是投资亚马逊和Netflix的特别好的一年,而2014年是苹果的一年。尽管Netflix多年来增长迅速,但它的高波动性意味着它的夏普比率在任何一年都不是很显著,特别是(正如我们上面所说的)2014和2019年。同样,在回报率/波动率方面,苹果和微软似乎是更安全的押注。

预期收益与已实现收益的比较

最后,我们将使用最优权重来计算预期收益,并将其与实际收益进行比较。

def exp_real_rets(returns, opt_weights, symbols, start_year, end_year):

    _rets = {}
    for year in range(start_year, end_year):
        prev_year = returns[symbols].loc[f"{year}-01-01":f"{year}-12-31"]
        current_year = returns[symbols].loc[f"{year + 1}-01-01":f"{year + 1}-12-31"]
        expected_pr = portfolio_return(prev_year, opt_weights[year])
        realized_pr = portfolio_return(current_year, opt_weights[year])
        _rets[year + 1] = [expected_pr, realized_pr]

    return _rets

在此函数中,我们将逐年实现的收益与理论预期收益进行比较。这是通过估计以下内容来实现的:

  1. 将上一年股票的最优权重应用于同年数据的收益 (expected_pr )。

  2. 将上一年股票的最优权重应用于下一年数据的收益 (realized_pr )。

我们将对Portfolio 1中的数据应用此函数,并将结果存储在DataFrame中,我们可以在Variable Explorer中查看该DataFrame。

port_1_exp_real = pd.DataFrame.from_dict(exp_real_rets(rets_1, opt_weights_1, SYMBOLS_1, start_year, end_year), orient='index')
port_1_exp_real.columns = ["expected", "realized"]

Expect列显示了使用最优投资组合时的预测值。另一方面,已实现列显示了使用这些权重将获得的实际利润。可以看出,有些年份存在着明显的差异。让我们从一个情节来看这一点。

port_1_exp_real.plot(kind="bar", figsize=(16, 10),title="Expected vs. realized Portfolio Returns")
Portfolio 1 expected and realized returns comparison

最显著的差异出现在2014年和2016年。在那些年,前一年的最佳投资组合权重(上图中的蓝色)是次年股票表现的糟糕指标(红色)。2013年,我们的模型估计谷歌和Netflix是很棒的投资。但到了2014年,如果我们比较当年年初和年底的价格,他们的股价就没有增长。2016年也发生了类似的事情,亚马逊前一年的增长趋势减弱,微软和苹果的股票价值增长。

让我们总结一下这些数字。

port_1_exp_real.mean()

# expected    0.408009
# realized    0.199700
# dtype: float64

我们的最优权重模型为我们提供了大约40%的利润,但由于实际市场波动,我们将获得近20%的利润。不错,但是我们每年计算的均值-方差投资组合模型不是很准确,是吗?

如果我们计算预期利润和已实现利润之间的相关性,这一结果就不那么令人振奋了。

port_1_exp_real[["expected", "realized"]].corr()

#           expected  realized
# expected  1.000000 -0.324053
# realized -0.324053  1.000000

正如我们所看到的,相关性是负的,这警告我们在使用这种类型的建模时应该谨慎。

第二个投资组合

现在,我们将使用不同性质的项目组合应用所有前面的代码。让我们假设,我们现在对制药感兴趣的不是科技公司,而是制药公司。我们将用辉瑞(Pfizer)、阿斯利康(Astra Zeneca)、强生(Johnson&Johnson)的股票建立投资组合。

下载数据

让我们下载数据并格式化它。

SYMBOLS_2 = ["PFE", "AZN", "JNJ"]  # Pfizer, Astra Zeneca, Johnson N Johnson

data_2 = yf.download(" ".join(SYMBOLS_2), start="2012-01-01", end="2021-01-01", group_by="Ticker")
data_2 = data_2.stack(level=1).rename_axis(["Date", "Ticker"]).reset_index(level=1)

close_data_2 = data_2[data_2.Ticker == "Close"].drop("Ticker", inplace=False, axis=1)

注解

如果您不想使用yFinance API,可以下载 close_data_2.csv 包含此公文包的结束信息的文件。将此文件复制到您的工作目录。使用以下指令加载数据: >>> close_data_2 = pd.read_csv("close_data_2.csv")

均值和标准差

我们将把数据放在月度格式中,观察平均值和标准差。

monthly_data_2 = close_data_2.resample("M").ffill().pct_change()

print("Mean:")
print(monthly_data_2.mean())
print("STD:")
print(monthly_data_2.std())

# Mean:
# AZN    0.008778
# PFE    0.006954
# JNJ    0.009126
# dtype: float64
# STD:
# AZN    0.063068
# PFE    0.053174
# JNJ    0.044063
# dtype: float64

就像在投资组合1中一样,所有的方法都是积极的。但其最大值(JNJ)每月仅勉强达到1%,这比投资组合1的增长速度要慢,与之前的投资组合(这里最大偏差为0.06)相比,变化也较小,这使其成为一种风险较低的投资。

每日和每月时间表

让我们用几个图表来更好地将上面的情况形象化。

rets_2 = np.log(close_data_2[SYMBOLS_2] / close_data_2[SYMBOLS_2].shift(1)).dropna()

(close_data_2[SYMBOLS_2] / close_data_2[SYMBOLS_2].iloc[0]).plot(figsize=(16, 10), title="Portfolio 2 daily stock price")

fig = plt.figure()
(monthly_data_2 + 1).cumprod().plot(figsize=(16, 10), title="Portfolio 2 monthly stock price")
Portfolio 2 daily stock price Portfolio 2 monthly stock price

这两个地块显示出多年来的稳步增长,但每年也有很高的变异性。只有作为长期投资,这似乎才是一个好的投资组合。

收益率、波动率与夏普比率

为了证实上一节所说的内容,让我们计算一下回报率、波动率和夏普比率(Sharpe Ratio)。

weights_2 = len(close_data_2.columns) * [1 / len(close_data_2.columns)]

print(f"Portfolio 2 returns: {portfolio_return(rets_2, weights_2):.4f}")
print(f"Portfolio 2 volatility: {portfolio_volatility(rets_2, weights_2):.4f}")
print(f"portfolio 2 Sharpe: {portfolio_sharpe(rets_2, weights_2):.4f}")

# Portfolio 2 returns: 0.0809
# Portfolio 2 volatility: 0.1637
# Portfolio 2 Sharpe: 0.4940

这个投资组合的收益明显低于之前的投资组合(0.0809<0.2859)。其波动率(0.1637<0.2370)也较低,但幅度较小。这也反映在较低的夏普比率(0.4940<1.2062)。这意味着,在均值-方差理论方法中,第一个投资组合比第二个投资组合是更好的投资。

如果我们应用蒙特卡罗模拟并用图形将其可视化,则可以清楚地观察到不同的行为:

port_2_vr, port_2_sr = monte_carlo_sharpe(rets_2, SYMBOLS_2, weights_2)

plt.figure(figsize=(16, 10))
fig = plt.scatter(port_2_vr[:, 0], port_2_vr[:, 1], c=port_2_sr, cmap="cool")
CB = plt.colorbar(fig)
CB.set_label("Sharpe ratio")
plt.xlabel("expected volatility")
plt.ylabel("expected return")
plt.title(" | ".join(SYMBOLS_2))
Portfolio 2 Monte Carlo Sharpe simulation scatter plot

在大多数情况下,高波动性与高回报并不相符。事实上,在模拟中存在较高的预期回报与较低的预期波动率相关的情景。

最优医药库存权重

现在让我们看看每个股票的最优权重是多少。 optimal_weights 功能。

start_year, end_year = (2012, 2020)
opt_weights_2 = optimal_weights(rets_2, SYMBOLS_2, weights_2, start_year, end_year)

port_2_ow = pd.DataFrame.from_dict(opt_weights_2, orient='index')
port_2_ow.columns = SYMBOLS_2
Portfolio 2 optimal weights by year

此外,我们可以使用这些最优权重来绘制该投资组合每年的预期收益和已实现收益。

port_2_exp_real = pd.DataFrame.from_dict(exp_real_rets(rets_2, opt_weights_2, SYMBOLS_2, start_year, end_year), orient='index')
port_2_exp_real.columns = ["expected", "realized"]

port_2_exp_real.plot(kind="bar", figsize=(16, 10),title="Expected vs. realized portfolio returns")
Portfolio 2 expected and realized returns comparison

由于这一投资组合的高波动性,我们的模型在几年中一直无法充分预测预期收益。2013年和2017年将实现高于预期的回报,但在2015年和2018年使用该模型将产生亏损。

最后,让我们看看预期均值和已实现均值之间的差异,以及数据之间的线性相关性。

print("Expected and realized means:")
print(port_2_exp_real.mean())
print("Expected and realized correlations:")
print(port_2_exp_real[["expected", "realized"]].corr())

# Expected and realized means:
# expected    0.155103
# realized    0.062207

# Expected and realized correlations:
#          expected  realized
# expected  1.000000 -0.023134
# realized -0.023134  1.000000

正如我们看到的,最优权重模型预测的年回报率接近15%,但实现的回报率仅为6%。负相关关系表明,就像投资组合1一样,这些值之间似乎没有任何对应关系。

那么,这种比较对投资组合1是有利的,但如果我们将投资组合1与加密货币等“风险更高”的投资进行比较会怎么样呢?下面我们来讨论一下。

第三个投资组合

下载加密货币数据

我们的第三个投资组合将由三种加密货币组成:比特币(BTC)、以太(ETH)和Litecoin(LTC)。要访问历史数据,我们将使用一个名为 Historic-Crypto

重要

如果您想在没有历史加密库的情况下使用数据,您可以 download the dataset 在您的工作目录中输入“crypto_hist.csv”,并使用以下指令将其加载到内存中 crypto_hist = pd.read_csv("crypto_hist.csv") ,并跳至部分 Monthly data

导入库:

from Historic_Crypto import Cryptocurrencies
from Historic_Crypto import HistoricalData

我们将使用 Cryptocurrencies 类以获取可用加密货币的列表。

crypto_list = Cryptocurrencies(coin_search="", extended_output=True).find_crypto_pairs()

注解

如果您需要有关使用此库的更多信息,可以使用Spyder帮助面板(在控制台中键入)进行快速查询 Cryptocurrencies 并使用 Ctrl-ICmd-I 以显示它)。或者您可以阅读他们的文档 official repository

变量资源管理器中出现了一个新变量: crypto_list 。它是一个Pandas DataFrame,它对加密货币交易的类型进行了基本描述。例如,我们可以查找哪个令牌是以美元进行的以太交易的令牌。

crypto_list.loc[crypto_list.base_currency == "ETH"]

在本例中,我们对ID为171的符号感兴趣:eth-USD。

我们可以使用History-Crypto的HistoricalData类下载使用我们投资组合的加密货币进行的交易的历史记录(以美元表示)。

# Download and format ETC data:
ETC_HIST = HistoricalData("ETH-USD", 3600 * 24, "2016-01-01-00-00", "2021-01-01-00-00").retrieve_data()
ETC_HIST.rename(columns={"close": "ETC"}, inplace=True)
ETC_HIST.drop(["low", "high", "open", "volume"], axis=1, inplace=True)


# Download and format BTC data:
BTC_HIST = HistoricalData("BTC-USD", 3600 * 24, "2016-01-01-00-00", "2021-01-01-00-00").retrieve_data()
BTC_HIST.rename(columns={"close": "BTC"}, inplace=True)
BTC_HIST.drop(["low", "high", "open", "volume"], axis=1, inplace=True)


# Download and format LTC data:
LTC_HIST = HistoricalData("LTC-USD", 3600 * 24, "2016-01-01-00-00", "2021-01-01-00-00").retrieve_data()
LTC_HIST.rename(columns={"close": "LTC"}, inplace=True)
LTC_HIST.drop(["low", "high", "open", "volume"], axis=1, inplace=True)

让我们合并产生的数据帧,将所有数据放在一个表中。

crypto_hist = pd.merge(BTC_HIST, ETC_HIST, on=["time"])
crypto_hist = pd.merge(crypto_hist, LTC_HIST, on=["time"])

我们不会在本节中展示过程和代码,而只展示分析结果。您可以按照前面几节中的步骤重新创建这些结果。

注解

使用公文包3的所有这一节来检查您对我们到目前为止已经介绍的概念和代码的理解程度。如果您有任何问题,您可以查阅 code 此研讨会附带的代码。但我们鼓励您尽可能多地尝试自己解决代码。

月度数据

让我们来看看加密货币价格增长的月度历史。

Portfolio 3 daily stock price

我们可以注意到,这里的规模要大得多。与其他两种硬币相比,ETH的增长比例相当显著。

收益率、波动率与夏普比率

让我们来考虑一下这个投资组合的回报率、波动率和夏普比率。

  • 返还:0.6203

  • 波动率:0.7587

  • 夏普:0.8176

除夏普比率外,这些数字都高于投资组合1和2的数据。这是一个非常不稳定的投资组合(实际上波动性是科技公司的三倍),这使得它最终没有投资组合1那么有利可图。回报更高(几乎是两倍),但风险可能无法弥补。

蒙特卡罗模拟

蒙特卡罗模拟还显示了风险和收益之间的非线性关系(如您所见,有时高风险只涉及适度的利润):

Portfolio 3 Monte Carlo Sharpe simulation scatter plot

可以看到,有些点位(右下角)表现出非常高的波动性,但预期回报却非常低。从这个意义上说,投资组合1代表了一种更安全的投资,因为较高的风险始终被较高的回报所抵消。

最优加密货币权重

Portfolio 3 optimal weights by year

最优投资组合权重(如果每年计算)表明,我们的投资组合在某些年份应该已经相当两极分化:建议在2016年和2019年初之前只购买比特币,在2018年只购买以太。另一方面,从2017年和2020年开始,我们的权重建议在比特币和以太之间进行更平衡的投资。我们的型号不推荐使用莱特温。

预期收益和已实现收益

Portfolio 3 expected and realized returns comparison

在这张图中,我们可以看到,在2017和2020年,获得的收益将超过预期收益(使用我们计算的权重)。在2019年,我们的模型预测了投资组合的大幅下降,但现实中,该投资组合当年既没有年化收益,也没有亏损。相比之下,在2018年,我们的模式会给我们带来重大损失,因为那一年加密货币的价值大幅下降。

我们投资组合的平均预期回报率为0.6972,高于我们原本会获得的0.4181的实现回报率。长期(从2016年到现在)投资这个投资组合将是一笔非常好的交易。由于变异性很大,短期投资风险很大。以毛利计算,这个投资组合的已实现收益是投资组合1的两倍多(0.4181>0.1997)。

最后一句话

均值-方差投资组合(MVP)理论是金融分析的众多工具之一。近年来,机器学习算法甚至被用来预测股票价格的行为,比任何标准的金融理论都要准确。

本研讨会中给出的例子并不是要作为您投资资金的指导。这只是学习使用Python和科学IDE进行金融分析的第一步。

在本研讨会中,您学习了如何执行以下操作:

  • 设置CONDA环境。

  • 使用Spyder编辑器编写和运行代码。

  • 使用IPython控制台编写和测试代码。

  • 使用API获取财务数据。

  • 绘制数据图表。

  • 在变量资源管理器中检查对象。

  • 使用绘图窗格在绘图之间浏览。

  • 操作Pandas DataFrame中的数据。

  • 建立一个金融投资组合。

  • 计算一段时间内投资组合的回报和波动性。

  • 获得投资组合中股票的最优权重。

有了这里学到的技能,您将能够处理更复杂的财务分析主题,例如您将在下一节的参考资料中找到的主题。

感谢您结束本次研讨会!我们希望您会发现它对您有帮助,并提供了丰富的信息。

如果您对Spyder的科学计算入门感兴趣,可以访问研讨会 Scientific Computing and Visualization with Spyder

家庭作业

如果您想检查您所学到的内容,我们建议您尝试获取第三个文件夹的结果。如果您有任何问题,可以在我们的存储库中查阅本研讨会附带的代码。

进一步阅读

用来应用MVP的大部分数学知识都是伊夫·希尔皮施(Yves Hilpisch)的优秀著作中概述的数学知识,我们向你推荐这本书:

  • Yves Hilpisch,Y.(2020)。 金融中的人工智能 。欧莱利*

这是一部伴随我们数十年的经典之作,也是沃伦·巴菲特(Warren Buffett)最喜欢的作品之一:

  • 格雷厄姆,B.(1949)。 聪明的投资者 。哈珀·柯林斯。

使用Python进行金融分析的另一个很好的资源是詹姆斯·马伟明写的下面这本书:

  • 马伟明,J.(2019年)。 掌握金融学的Python 。包装