27. 商品价格#

27.1. 大纲#

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

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

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

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

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

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

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

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

本讲需要使用yfinance库。

!pip install yfinance

Hide code cell output

Collecting yfinance
  Downloading yfinance-0.2.66-py2.py3-none-any.whl.metadata (6.0 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.12.tar.gz (19 kB)
  Preparing metadata (setup.py) ... ?25l-
 done
?25hRequirement 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.18.2.tar.gz (949 kB)
?25l     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 0.0/949.2 kB ? eta -:--:--
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 949.2/949.2 kB 23.5 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)
Collecting curl_cffi>=0.7 (from yfinance)
  Downloading curl_cffi-0.13.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (13 kB)
Requirement already satisfied: protobuf>=3.19.0 in /home/runner/miniconda3/envs/quantecon/lib/python3.12/site-packages (from yfinance) (4.25.3)
Collecting websockets>=13.0 (from yfinance)
  Downloading websockets-15.0.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (6.8 kB)
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: cffi>=1.12.0 in /home/runner/miniconda3/envs/quantecon/lib/python3.12/site-packages (from curl_cffi>=0.7->yfinance) (1.17.1)
Requirement already satisfied: certifi>=2024.2.2 in /home/runner/miniconda3/envs/quantecon/lib/python3.12/site-packages (from curl_cffi>=0.7->yfinance) (2024.8.30)
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: pycparser in /home/runner/miniconda3/envs/quantecon/lib/python3.12/site-packages (from cffi>=1.12.0->curl_cffi>=0.7->yfinance) (2.21)
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.66-py2.py3-none-any.whl (123 kB)
Downloading curl_cffi-0.13.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (8.3 MB)
?25l   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 0.0/8.3 MB ? eta -:--:--
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 8.3/8.3 MB 133.5 MB/s eta 0:00:00
?25hDownloading frozendict-2.4.6-py312-none-any.whl (16 kB)
Downloading websockets-15.0.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl (182 kB)
Building wheels for collected packages: multitasking, peewee
  Building wheel for multitasking (setup.py) ... ?25l-
 \
 |
 done
?25h  Created wheel for multitasking: filename=multitasking-0.0.12-py3-none-any.whl size=15548 sha256=805327a155ce36a3d2f979770ee683736f24038d5e5b88f6c0080af5b147cf68
  Stored in directory: /home/runner/.cache/pip/wheels/cc/bd/6f/664d62c99327abeef7d86489e6631cbf45b56fbf7ef1d6ef00
  Building wheel for peewee (pyproject.toml) ... ?25l-
 \
 |
 done
?25h  Created wheel for peewee: filename=peewee-3.18.2-cp312-cp312-linux_x86_64.whl size=303902 sha256=31e7a320bbfe872f9b2787d8b417ce05b4f5a3a5b2d63e7c20b83abb1f991685
  Stored in directory: /home/runner/.cache/pip/wheels/d1/df/a9/0202b051c65b11c992dd6db9f2babdd2c44ec7d35d511be5d3
Successfully built multitasking peewee
Installing collected packages: peewee, multitasking, websockets, frozendict, curl_cffi, yfinance
Successfully installed curl_cffi-0.13.0 frozendict-2.4.6 multitasking-0.0.12 peewee-3.18.2 websockets-15.0.1 yfinance-0.2.66

我们将使用以下导入

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/7880e1512cc4e6108d3a1f8def63b47b9efb491b0eb7715bd2a4c75337e2f28e.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/3c86ff2d9e9b5e95e97ae68de4f6722b5b1ffb8354afbfd3358b5aad41215534.png