27. 商品价格#

27.1. 大纲#

在全球超过一半的国家中,商品总出口的大部分

商品的例子包括铜、钻石、铁矿石、锂、棉花和咖啡豆。

本讲将介绍商品价格理论。

相比本系列的其他讲座,这一讲内容较为高级。

我们需要计算一个由价格函数描述的均衡。

我们将解一个方程,其中价格函数是未知量。

这比解一个未知数或向量的方程要难得多。

本讲将讨论一种解函数方程的方法,这类方程的未知对象是函数。

本讲需要使用yfinance库。

!pip install yfinance
Hide code cell output
Collecting yfinance
  Downloading yfinance-0.2.55-py2.py3-none-any.whl.metadata (5.8 kB)
Requirement already satisfied: pandas>=1.3.0 in /home/runner/miniconda3/envs/quantecon/lib/python3.12/site-packages (from yfinance) (2.2.2)
Requirement already satisfied: numpy>=1.16.5 in /home/runner/miniconda3/envs/quantecon/lib/python3.12/site-packages (from yfinance) (1.26.4)
Requirement already satisfied: requests>=2.31 in /home/runner/miniconda3/envs/quantecon/lib/python3.12/site-packages (from yfinance) (2.32.3)
Collecting multitasking>=0.0.7 (from yfinance)
  Downloading multitasking-0.0.11-py3-none-any.whl.metadata (5.5 kB)
Requirement already satisfied: platformdirs>=2.0.0 in /home/runner/miniconda3/envs/quantecon/lib/python3.12/site-packages (from yfinance) (3.10.0)
Requirement already satisfied: pytz>=2022.5 in /home/runner/miniconda3/envs/quantecon/lib/python3.12/site-packages (from yfinance) (2024.1)
Collecting frozendict>=2.3.4 (from yfinance)
  Downloading frozendict-2.4.6-py312-none-any.whl.metadata (23 kB)
Collecting peewee>=3.16.2 (from yfinance)
  Downloading peewee-3.17.9.tar.gz (3.0 MB)
?25l     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 0.0/3.0 MB ? eta -:--:--
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 3.0/3.0 MB 131.9 MB/s eta 0:00:00
?25h
  Installing build dependencies ... ?25l-
 \
 |
 done
?25h  Getting requirements to build wheel ... ?25l- done
?25h  Preparing metadata (pyproject.toml) ... ?25l-
 done
?25hRequirement already satisfied: beautifulsoup4>=4.11.1 in /home/runner/miniconda3/envs/quantecon/lib/python3.12/site-packages (from yfinance) (4.12.3)
Requirement already satisfied: soupsieve>1.2 in /home/runner/miniconda3/envs/quantecon/lib/python3.12/site-packages (from beautifulsoup4>=4.11.1->yfinance) (2.5)
Requirement already satisfied: python-dateutil>=2.8.2 in /home/runner/miniconda3/envs/quantecon/lib/python3.12/site-packages (from pandas>=1.3.0->yfinance) (2.9.0.post0)
Requirement already satisfied: tzdata>=2022.7 in /home/runner/miniconda3/envs/quantecon/lib/python3.12/site-packages (from pandas>=1.3.0->yfinance) (2023.3)
Requirement already satisfied: charset-normalizer<4,>=2 in /home/runner/miniconda3/envs/quantecon/lib/python3.12/site-packages (from requests>=2.31->yfinance) (3.3.2)
Requirement already satisfied: idna<4,>=2.5 in /home/runner/miniconda3/envs/quantecon/lib/python3.12/site-packages (from requests>=2.31->yfinance) (3.7)
Requirement already satisfied: urllib3<3,>=1.21.1 in /home/runner/miniconda3/envs/quantecon/lib/python3.12/site-packages (from requests>=2.31->yfinance) (2.2.3)
Requirement already satisfied: certifi>=2017.4.17 in /home/runner/miniconda3/envs/quantecon/lib/python3.12/site-packages (from requests>=2.31->yfinance) (2024.8.30)
Requirement already satisfied: six>=1.5 in /home/runner/miniconda3/envs/quantecon/lib/python3.12/site-packages (from python-dateutil>=2.8.2->pandas>=1.3.0->yfinance) (1.16.0)
Downloading yfinance-0.2.55-py2.py3-none-any.whl (109 kB)
Downloading frozendict-2.4.6-py312-none-any.whl (16 kB)
Downloading multitasking-0.0.11-py3-none-any.whl (8.5 kB)
Building wheels for collected packages: peewee
  Building wheel for peewee (pyproject.toml) ... ?25l-
 \
 |
 done
?25h  Created wheel for peewee: filename=peewee-3.17.9-cp312-cp312-linux_x86_64.whl size=303871 sha256=debeecdc8f2e92da85d2b3fc728dfe8addc9e1b7319157841dd8da27cbcfd2c2
  Stored in directory: /home/runner/.cache/pip/wheels/43/ef/2d/2c51d496bf084945ffdf838b4cc8767b8ba1cc20eb41588831
Successfully built peewee
Installing collected packages: peewee, multitasking, frozendict, yfinance
Successfully installed frozendict-2.4.6 multitasking-0.0.11 peewee-3.17.9 yfinance-0.2.55

我们将使用以下导入

import numpy as np
import yfinance as yf
import matplotlib.pyplot as plt
from scipy.interpolate import interp1d
from scipy.optimize import brentq
from scipy.stats import beta

import matplotlib as mpl
FONTPATH = "fonts/SourceHanSerifSC-SemiBold.otf"
mpl.font_manager.fontManager.addfont(FONTPATH)
plt.rcParams['font.family'] = ['Source Han Serif SC']

27.2. 数据#

下图显示了自 2016 年初以来以美元计价的棉花价格。

Hide code cell source
s = yf.download('CT=F', '2016-1-1', '2023-4-1', auto_adjust=False)['Adj Close']
Hide code cell output
[*********************100%***********************]  1 of 1 completed

Hide code cell source
fig, ax = plt.subplots()

ax.plot(s, marker='o', alpha=0.5, ms=1)
ax.set_ylabel(r'棉花价格(美元)', fontsize=12)
ax.set_xlabel(r'日期', fontsize=12)

plt.show()
_images/f228744eec0ae5ec905d501d875f8b033a1dc5ea2fcd188df8d584211a701143.png

该图显示了棉花价格的巨大波动,令人惊讶。

是什么导致了这些波动?

一般来说,价格取决于以下各方的选择和行为:

  1. 供应商,

  2. 消费者,以及

  3. 投机者。

我们的重点将是这些方之间的互动。

我们将通过一个动态的供需模型将它们联系在一起,称为 竞争性储存模型

该模型由 [Samuelson, 1971][Wright and Williams, 1982][Scheinkman and Schechtman, 1983][Deaton and Laroque, 1992][Deaton and Laroque, 1996][Chambers and Bailey, 1996] 开发。

27.3. 竞争性储存模型#

在竞争性储存模型中,商品被视为一种资产。这些商品具有两个特点:

  1. 可以被投机者交易,并且

  2. 对消费者有内在价值。

总需求是消费者需求和投机者需求的总和。

供应是外生的,取决于“收成”。

Note

如今,基本的计算机芯片和集成电路等高度标准化的产品,在金融市场上也被视为商品。对这类商品来说,”收成”一词并不太恰当。

不过为了简化问题,我们还是沿用这个术语。

均衡价格是通过竞争决定的。

它是当前状态的一个函数(决定当前的收成并预测未来的收成)。

27.4. 模型#

考虑一个单一商品的市场,其价格在时间 tpt

该商品在时间 t 的收成为 Zt

我们假设序列 {Zt}t1 是独立同分布(IID)的,具有共同的密度函数 ϕ,其中 ϕ 为非负。

投机者可以在各期之间储存该商品,当前期购买的 It 单位在下一期将产生 αIt 单位。

这里的参数 α(0,1) 是该商品的贬值率。

为了简化问题,风险自由利率取为零,因此购买 It 单位的预期利润为

Etpt+1αItptIt=(αEtpt+1pt)It

其中 Etpt+1 是在时间 tpt+1 的期望。

27.5. 均衡#

在本节中,我们定义均衡并讨论如何计算它。

27.5.1. 均衡条件#

假设投机者是风险中性的,这意味着他们在预期利润为正时会购买商品。

因此,如果预期利润为正,则市场不处于均衡状态。

所以要达到均衡,价格必须满足“无套利”条件:

(27.1)#αEtpt+1pt0

这表明当预期价格低于当前价格时,就不存在套利空间。

利润最大化给出了额外条件:

(27.2)#αEtpt+1pt<0 意味着 It=0

我们还要求市场出清,即每期供应等于需求。

假设消费者根据价格 p 产生需求量 D(p)

P:=D1 为逆需求函数。

关于数量:

  • 供应是投机者的持有量和当前收成的总和,并且

  • 需求是消费者购买和投机者购买的总和。

用数学语言表述:

  • 供应由 Xt=αIt1+Zt 给出,取值在 S:=R+ 中。

  • 需求为 D(pt)+It

因此,市场均衡条件为:

(27.3)#αIt1+Zt=D(pt)+It

初始条件 X0S 为给定值。

27.5.2. 一个均衡函数#

如何找到均衡呢?

我们的思路是寻找一个只依赖当前状态的价格系统。

(我们的解法使用了 拟设(ansatz),这是一种基于推测的猜想——在这里是对价格函数的猜想。)

具体来说,我们在 S 上取一个函数 p,对每个 tpt=p(Xt)

价格和数量随后满足:

(27.4)#pt=p(Xt)It=XtD(pt)Xt+1=αIt+Zt+1

我们选择 p 使这些价格和数量满足上述均衡条件。

更准确地说,我们要找一个 p,使得 (27.1)(27.2) 对应的系统 (27.4) 成立。

(27.5)#p(x)=max{α0p(αI(x)+z)ϕ(z)dz,P(x)}(xS)

其中

(27.6)#I(x):=xD(p(x))(xS)

事实证明,这样的 p 是充分的,因为它满足 (27.1)(27.2) 对应的系统 (27.4)

要理解这一点,我们首先注意到:

Etpt+1=Etp(Xt+1)=Etp(αI(Xt)+Zt+1)=0p(αI(Xt)+z)ϕ(z)dz

因此,条件 (27.1) 要求:

α0p(αI(Xt)+z)ϕ(z)dzp(Xt)

这个不等式直接来自 (27.5)

其次,对于 (27.2),假设:

α0p(αI(Xt)+z)ϕ(z)dz<p(Xt)

那么根据 (27.5),我们得到 p(Xt)=P(Xt)

此时有 D(p(Xt))=Xt,并且 It=I(Xt)=0

因此,条件 (27.1)(27.2) 都成立。

我们找到了一个均衡,验证了 ansatz。

27.5.3. 计算均衡#

现在我们知道,均衡可以通过找到一个满足 (27.5) 的函数 p 来获得。

在温和的条件下,可以证明在 S 上恰好存在一个满足 (27.5) 的函数。

此外,我们可以通过逐次逼近来计算这个函数。

具体来说,我们从一个初始函数猜测开始,然后使用 (27.5) 来更新它。

这会生成一系列函数 p1,p2,

我们继续这个过程,直到它收敛,即 pkpk+1 非常接近。

然后,我们将最终计算得到的 pk 作为 p 的近似值。

为了实现更新步骤,将 (27.5)(27.6) 结合起来是很有帮助的。

这给出了更新规则:

(27.7)#pk+1(x)=max{α0pk(α(xD(pk+1(x)))+z)ϕ(z)dz,P(x)}

换句话说,我们将 pk 视为给定,并在每个 x 处求解 q

(27.8)#q=max{α0pk(α(xD(q))+z)ϕ(z)dz,P(x)}

实际上,我们无法对每个 x 进行这样的计算,所以我们选择一系列离散点 x1,,xn 来进行计算。

对这些点,我们可以得到相应的值 q1,,qn

然后,我们在网格点 x1,,xn 上对这些值 q1,,qn 进行线性插值,从而得到 pk+1

我们不断重复这个过程,直到结果收敛。

27.6. 代码#

下面的代码实现了这个迭代过程。我们从 p0=P 开始。

我们选择一个偏移的贝塔分布作为分布 ϕ(当然也可以选择其他分布)。

(27.8) 中的积分通过 Monte Carlo 方法来计算。

α, a, c = 0.8, 1.0, 2.0
beta_a, beta_b = 5, 5
mc_draw_size = 250
gridsize = 150
grid_max = 35
grid = np.linspace(a, grid_max, gridsize)

beta_dist = beta(5, 5)
Z = a + beta_dist.rvs(mc_draw_size) * c    # 随机冲击的观测值
D = P = lambda x: 1.0 / x
tol = 1e-4


def T(p_array):

    new_p = np.empty_like(p_array)

    # 插值以获得p 作为函数。

    p = interp1d(grid,
                 p_array,
                 fill_value=(p_array[0], p_array[-1]),
                 bounds_error=False)

    # 更新
    for i, x in enumerate(grid):

        h = lambda q: q - max(α * np.mean(p(α * (x - D(q)) + Z)), P(x))
        new_p[i] = brentq(h, 1e-8, 100)

    return new_p


fig, ax = plt.subplots()

price = P(grid)
ax.plot(grid, price, alpha=0.5, lw=1, label="反需求曲线")
error = tol + 1
while error > tol:
    new_price = T(price)
    error = max(np.abs(new_price - price))
    price = new_price

ax.plot(grid, price, 'k-', alpha=0.5, lw=2, label=r'$p^*$')
ax.legend()
ax.set_xlabel('$x$')
ax.set_ylabel("价格")

plt.show()
_images/11139c55d89c078cf830ce7efda8a56de476a8d4afa36093551ba276d75f17e8.png

上图显示了逆需求曲线 P,也就是 p0,以及我们对 p 的近似。

一旦我们得到了 p 的近似值,就可以模拟价格的时间序列。

# 将价格数组转化为价格函数。
p_star = interp1d(grid,
                  price,
                  fill_value=(price[0], price[-1]),
                  bounds_error=False)

def carry_over(x):
    return α * (x - D(p_star(x)))

def generate_cp_ts(init=1, n=50):
    X = np.empty(n)
    X[0] = init
    for t in range(n-1):
            Z = a + c * beta_dist.rvs()
            X[t+1] = carry_over(X[t]) + Z
    return p_star(X)

fig, ax = plt.subplots()
ax.plot(generate_cp_ts(), label="价格")
ax.set_xlabel("时间")
ax.legend()
plt.show()
_images/c9765ab7542a8f67d38abf74f05b3cba051abf7806cfedc20770fd76840b0d72.png