26. The Cobweb Model#
The cobweb model is a model of prices and quantities in a given market, and how they evolve over time.
26.1. Overview#
The cobweb model dates back to the 1930s and, while simple, it remains significant because it shows the fundamental importance of expectations.
To give some idea of how the model operates, and why expectations matter, imagine the following scenario.
There is a market for soybeans, say, where prices and traded quantities depend on the choices of buyers and sellers.
The buyers are represented by a demand curve — they buy more at low prices and less at high prices.
The sellers have a supply curve — they wish to sell more at high prices and less at low prices.
However, the sellers (who are farmers) need time to grow their crops.
Suppose now that the price is currently high.
Seeing this high price, and perhaps expecting that the high price will remain for some time, the farmers plant many fields with soybeans.
Next period the resulting high supply floods the market, causing the price to drop.
Seeing this low price, the farmers now shift out of soybeans, restricting supply and causing the price to climb again.
You can imagine how these dynamics could cause cycles in prices and quantities that persist over time.
The cobweb model puts these ideas into equations so we can try to quantify them, and to study conditions under which cycles persist (or disappear).
In this lecture, we investigate and simulate the basic model under different assumptions regarding the way that producers form expectations.
Our discussion and simulations draw on high quality lectures by Cars Hommes.
We will use the following imports.
import numpy as np
import matplotlib.pyplot as plt
26.2. History#
Early papers on the cobweb cycle include [Waugh, 1964] and [Harlow, 1960].
The paper [Harlow, 1960] uses the cobweb theorem to explain the prices of hog in the US over 1920–1950.
The next plot replicates part of Figure 2 from that paper, which plots the price of hogs at yearly frequency.
Notice the cyclical price dynamics, which match the kind of cyclical soybean price dynamics discussed above.
hog_prices = [55, 57, 80, 70, 60, 65, 72, 65, 51, 49, 45, 80, 85,
78, 80, 68, 52, 65, 83, 78, 60, 62, 80, 87, 81, 70,
69, 65, 62, 85, 87, 65, 63, 75, 80, 62]
years = np.arange(1924, 1960)
fig, ax = plt.subplots()
ax.plot(years, hog_prices, '-o', ms=4, label='hog price')
ax.set_xlabel('year')
ax.set_ylabel('dollars')
ax.legend()
ax.grid()
plt.show()
26.3. The model#
Let’s return to our discussion of a hypothetical soybean market, where price is determined by supply and demand.
We suppose that demand for soybeans is given by
where
(
Because the crop of soybeans for time
We suppose that supply is nonlinear in expected prices, and takes the form
where
Let’s make a plot of supply and demand for particular choices of the parameter values.
First we store the parameters in a class and define the functions above as methods.
class Market:
def __init__(self,
a=8, # demand parameter
b=1, # demand parameter
c=6, # supply parameter
d=1, # supply parameter
λ=2.0): # supply parameter
self.a, self.b, self.c, self.d = a, b, c, d
self.λ = λ
def demand(self, p):
a, b = self.a, self.b
return a - b * p
def supply(self, p):
c, d, λ = self.c, self.d, self.λ
return np.tanh(λ * (p - c)) + d
Now let’s plot.
p_grid = np.linspace(5, 8, 200)
m = Market()
fig, ax = plt.subplots()
ax.plot(p_grid, m.demand(p_grid), label="$D$")
ax.plot(p_grid, m.supply(p_grid), label="$S$")
ax.set_xlabel("price")
ax.set_ylabel("quantity")
ax.legend()
plt.show()
Market equilibrium requires that supply equals demand, or
Rewriting in terms of
Finally, to complete the model, we need to describe how price expectations are formed.
We will assume that expected prices at time
In particular, we suppose that
where
Thus, we are assuming that producers expect the time-
(We could of course add additional lags and readers are encouraged to experiment with such cases.)
Combining the last two equations gives the dynamics for prices:
The price dynamics depend on the parameter values and also on the function
26.4. Naive expectations#
To go further in our analysis we need to specify the function
Let’s start with naive expectations, which refers to the case where producers expect the next period spot price to be whatever the price is in the current period.
In other words,
Using (26.2), we then have
We can write this as
where
Here we represent the function
def g(model, current_price):
"""
Function to find the next price given the current price
and Market model
"""
a, b = model.a, model.b
next_price = - (model.supply(current_price) - a) / b
return next_price
Let’s try to understand how prices will evolve using a 45-degree diagram, which is a tool for studying one-dimensional dynamics.
The function plot45
defined below helps us draw the 45-degree diagram.
Show source
def plot45(model, pmin, pmax, p0, num_arrows=5):
"""
Function to plot a 45 degree plot
Parameters
==========
model: Market model
pmin: Lower price limit
pmax: Upper price limit
p0: Initial value of price (needed to simulate prices)
num_arrows: Number of simulations to plot
"""
pgrid = np.linspace(pmin, pmax, 200)
fig, ax = plt.subplots()
ax.set_xlim(pmin, pmax)
ax.set_ylim(pmin, pmax)
hw = (pmax - pmin) * 0.01
hl = 2 * hw
arrow_args = dict(fc="k", ec="k", head_width=hw,
length_includes_head=True, lw=1,
alpha=0.6, head_length=hl)
ax.plot(pgrid, g(model, pgrid), 'b-',
lw=2, alpha=0.6, label='g')
ax.plot(pgrid, pgrid, lw=1, alpha=0.7, label=r'$45\degree$')
x = p0
xticks = [pmin]
xtick_labels = [pmin]
for i in range(num_arrows):
if i == 0:
ax.arrow(x, 0.0, 0.0, g(model, x),
**arrow_args)
else:
ax.arrow(x, x, 0.0, g(model, x) - x,
**arrow_args)
ax.plot((x, x), (0, x), ls='dotted')
ax.arrow(x, g(model, x),
g(model, x) - x, 0, **arrow_args)
xticks.append(x)
xtick_labels.append(r'$p_{}$'.format(str(i)))
x = g(model, x)
xticks.append(x)
xtick_labels.append(r'$p_{}$'.format(str(i+1)))
ax.plot((x, x), (0, x), '->', alpha=0.5, color='orange')
xticks.append(pmax)
xtick_labels.append(pmax)
ax.set_ylabel(r'$p_{t+1}$')
ax.set_xlabel(r'$p_t$')
ax.set_xticks(xticks)
ax.set_yticks(xticks)
ax.set_xticklabels(xtick_labels)
ax.set_yticklabels(xtick_labels)
bbox = (0., 1.04, 1., .104)
legend_args = {'bbox_to_anchor': bbox, 'loc': 'upper right'}
ax.legend(ncol=2, frameon=False, **legend_args, fontsize=14)
plt.show()
Now we can set up a market and plot the 45-degree diagram.
m = Market()
The plot shows the function
Think of
Since
Clearly,
If
lies above the 45-degree line at , then we have .If
lies below the 45-degree line at , then we have .If
hits the 45-degree line at , then we have , so is a steady state.
Consider the sequence of prices starting at
We find
Then from
We can see the start of a cycle.
To confirm this, let’s plot a time series.
def ts_plot_price(model, # Market model
p0, # Initial price
y_a=3, y_b= 12, # Controls y-axis
ts_length=10): # Length of time series
"""
Function to simulate and plot the time series of price.
"""
fig, ax = plt.subplots()
ax.set_xlabel(r'$t$', fontsize=12)
ax.set_ylabel(r'$p_t$', fontsize=12)
p = np.empty(ts_length)
p[0] = p0
for t in range(1, ts_length):
p[t] = g(model, p[t-1])
ax.plot(np.arange(ts_length),
p,
'bo-',
alpha=0.6,
lw=2,
label=r'$p_t$')
ax.legend(loc='best', fontsize=10)
ax.set_ylim(y_a, y_b)
ax.set_xticks(np.arange(ts_length))
plt.show()
We see that a cycle has formed and the cycle is persistent.
(You can confirm this by plotting over a longer time horizon.)
The cycle is “stable”, in the sense that prices converge to it from most starting conditions.
For example,
26.5. Adaptive expectations#
Naive expectations are quite simple and also important in driving the cycle that we found.
What if expectations are formed in a different way?
Next we consider adaptive expectations.
This refers to the case where producers form expectations for the next period price as a weighted average of their last guess and the current spot price.
That is,
Another way to write this is
This equation helps to show that expectations shift
up when prices last period were above expectations
down when prices last period were below expectations
Using (26.4), we obtain the dynamics
Let’s try to simulate the price and observe the dynamics using different values of
def find_next_price_adaptive(model, curr_price_exp):
"""
Function to find the next price given the current price expectation
and Market model
"""
return - (model.supply(curr_price_exp) - model.a) / model.b
The function below plots price dynamics under adaptive expectations for different values of
def ts_price_plot_adaptive(model, p0, ts_length=10, α=[1.0, 0.9, 0.75]):
fig, axs = plt.subplots(1, len(α), figsize=(12, 5))
for i_plot, a in enumerate(α):
pe_last = p0
p_values = np.empty(ts_length)
p_values[0] = p0
for i in range(1, ts_length):
p_values[i] = find_next_price_adaptive(model, pe_last)
pe_last = a*p_values[i] + (1 - a)*pe_last
axs[i_plot].plot(np.arange(ts_length), p_values)
axs[i_plot].set_title(r'$\alpha={}$'.format(a))
axs[i_plot].set_xlabel('t')
axs[i_plot].set_ylabel('price')
plt.show()
Let’s call the function with prices starting at
Note that if
Decreasing the value of
This increased stability can be seen in the figures.
26.6. Exercises#
Exercise 26.1
Using the default Market
class and naive expectations, plot a time series simulation of supply (rather than the price).
Show, in particular, that supply also cycles.
Solution to Exercise 26.1
def ts_plot_supply(model, p0, ts_length=10):
"""
Function to simulate and plot the supply function
given the initial price.
"""
pe_last = p0
s_values = np.empty(ts_length)
for i in range(ts_length):
# store quantity
s_values[i] = model.supply(pe_last)
# update price
pe_last = - (s_values[i] - model.a) / model.b
fig, ax = plt.subplots()
ax.plot(np.arange(ts_length),
s_values,
'bo-',
alpha=0.6,
lw=2,
label=r'supply')
ax.legend(loc='best', fontsize=10)
ax.set_xticks(np.arange(ts_length))
ax.set_xlabel("time")
ax.set_ylabel("quantity")
plt.show()
Exercise 26.2
Backward looking average expectations
Backward looking average expectations refers to the case where producers form expectations for the next period price as a linear combination of their last guess and the second last guess.
That is,
Simulate and plot the price dynamics for
Solution to Exercise 26.2
def find_next_price_blae(model, curr_price_exp):
"""
Function to find the next price given the current price expectation
and Market model
"""
return - (model.supply(curr_price_exp) - model.a) / model.b
def ts_plot_price_blae(model, p0, p1, alphas, ts_length=15):
"""
Function to simulate and plot the time series of price
using backward looking average expectations.
"""
fig, axes = plt.subplots(len(alphas), 1, figsize=(8, 16))
for ax, a in zip(axes.flatten(), alphas):
p = np.empty(ts_length)
p[0] = p0
p[1] = p1
for t in range(2, ts_length):
pe = a*p[t-1] + (1 - a)*p[t-2]
p[t] = -(model.supply(pe) - model.a) / model.b
ax.plot(np.arange(ts_length),
p,
'o-',
alpha=0.6,
label=r'$\alpha={}$'.format(a))
ax.legend(loc='best', fontsize=10)
ax.set_xlabel(r'$t$', fontsize=12)
ax.set_ylabel(r'$p_t$', fontsize=12)
plt.show()