A model of a vertical supply chain like in Sheu and Taragin (2021) ``Simulating mergers in a vertical supply chain with bargaining’’ can be used to evaluate effects of vertical mergers. That model uses a simultaneous timing assumption for equilibrium. This document shows how to calibrate the model and implement a merger simulation.
First, we load packages that will be useful. BB and rootSolve are packages with optimization tools.
library(mergersim)
library(BB)
library(rootSolve)
library(antitrust)
library(numDeriv) # for jacobian() functionConsider a market where \(W\) denotes the set of wholesalers and \(R\) denotes the set of retailers. Denote a specific wholesaler by \(w\) and a specific retailer by \(r\). It will also be useful to define \(R^w\) as the set of retailers that carry \(w\)’s products, and \(W^r\) is the set of wholesalers that supply retailer \(r\). For simplicity, we will assume in this exercise that before any mergers occur, each wholesaler supplies only one product.
Final consumers are indexed by \(i\) and select one retailer at which to purchase one wholesaler’s product. The utility received by consumer \(i\) for purchasing from retailer \(r\) the product owned by wholesaler \(w\) is given by:
\[\begin{equation*} u_{irw} = \delta_{rw} + \alpha p_{rw} + \varepsilon_{irw} \end{equation*}\]
Where \(\alpha < 0\) is the price sensitivity and we assume that the idiosyncratic error term follows the type 1 extreme value distribution.
In the example, we will assume that there are \(|W|=2\) wholesalers and \(|R|=3\) retailers. Each good \(j\) is a retailer-wholesaler combination.
alpha <- -0.9
R <- 3
W <- 2
delta <- c(0.2, 0.3, 0.9, 1.0, 0.8, 0.9)
c_R_vec <- matrix(.1, nrow = (R*W), ncol = 1)
c_W_vec <- matrix(.2, nrow = (R*W), ncol = 1)There is also an outside option which is normalized to have \(\delta_{00} = p_{00} = 0\).
Suppose that before any merger occurs, all retailers and suppliers are independently owned. And suppose we are investigating a potential merger between retailer \(r_1\) and wholesaler \(w_1\). Then, we can create ownership vectors to reflect the pre-merger market.
The Bertrand model of competition posits that downstream retailers offer differentiated products and compete on the basis of price setting. In particular, for a retailer \(r\) that offers product portfolio \(W^r\), specify the profit function as:
\[\begin{equation*} \Pi^r = \sum_{w \in W^r} (p_{rw} - p^W_{rw} - c^R_{rw}) \cdot s_{rw} \cdot M \end{equation*}\]
Which yields a first order condition:
\[\begin{equation} \sum_{x \in W^r} (p_{rx} - p^W_{rx} - c^R_{rx}) \cdot \frac{\partial s_{rx}}{\partial p_{rw}} + s_{rw} = 0 \end{equation}\]
This system of FOC’s can be expressed in matrix notation. Each retailer-wholesaler pair can be denoted as a distinct product, \(j = (r,w)\), and \(J\) denotes the total number of products. Let \(\Omega^R\) denote the J-by-J ownership matrix of downstream products, \(t(dd)\) denotes the transpose of a matrix where element \((j,k)\) equals \(\frac{\partial s_j}{\partial p_k}\), \(m\) is vector length \(J\) of margins where the jth element is \((p_j - p^W_j- c_j)\), and \(s\) is a vector of length \(J\) of product market shares. Then the stacked FOC’s can be denoted as:
\[\begin{equation*} 0 = (\Omega^R * t(dd)) \%*\% m + s \end{equation*}\]
We can use the true demand parameters and marginal costs to determine the equilibrium prices and shares. First, define the Bertrand FOC function. Then use multiroot to find the equilibrium prices that set the FOCs to zero:
p_W_vec <- matrix(.25, nrow = (R*W), ncol = 1) # dummy wholesale prices
p_r_start <- c_R_vec*1.1
out1 <- multiroot(f = mergersim:::bertrand_foc_novert ,start = p_r_start,
own_down = own_down_pre, alpha= alpha,
delta = delta, cost = c_R_vec,
price_w = p_W_vec)
p1 <- out1$root
p1
#> [1] 1.683099 1.683099 1.889802 1.889802 1.852530 1.852530As an alternative to multiroot, you could also use BBoptim to find the prices that satisfy the first-order conditions. With data consistent with the model, both methods should yield the same equiblrium prices. It is possible that with some observed data, BBoptim provides a more robust search for equilibrium values.
p_r_start <- as.numeric(c_R_vec*1.1)
out1b <- BBoptim(f = mergersim:::bertrand_foc_novert, par = p_r_start,
own_down = own_down_pre, alpha= alpha,
delta = delta, cost = c_R_vec,
price_w = p_W_vec, sumFOC = TRUE)
#> iter: 0 f-value: 0.1973327 pgrad: 0.07226812
#> iter: 10 f-value: 2.314745e-07 pgrad: 5.893354e-05
#> Successful convergence.
p1b <- out1b$par
p1b
#> [1] 1.682608 1.683009 1.889792 1.889778 1.852468 1.852441The upstream involves bilateral Nash-in-Nash bargaining between each retailer and wholesaler. Wholesaler payoffs are given as:
\[\begin{equation*} \Pi^w = \sum_{r \in R^w} ( p^W_{rw} - c^W_{rw}) \cdot s_{rw} \cdot M \end{equation*}\]
For each retailer-wholesaler pair \((r,w)\), the Nash bargaining objective function is then given by: \[\begin{equation*} \max_{p^W_{rw}} \left( \Pi^r - d^r(W^r/\{w\}) \right)^\lambda \cdot \left( \Pi^w - d^w(R^w/\{r\}) \right)^{1-\lambda} \end{equation*}\]
where \(d^r\) and \(d^w\) denote the disagreement payoff for retailer \(r\) and wholesaler \(w\), respectively. Equilibrium wholesale prices \(p^W\) are those that maximize this objective function. The first order condition can be used to find the equilibrium prices:
\[\begin{equation} \left( p^W_{rw} - c^W_{rw} \right) \cdot s_{rw} - \sum_{t \in R^w / \{r\}} ( p^W_{tw} - c^W_{tw} ) \cdot \Delta s_{tw} = \frac{1-\lambda}{\lambda} \left( (p_{rw} - p^W_{rw} - c^R_{rw}) \cdot s_{rw} - \sum_{x \in W^r / \{w\}} (p_{rx} - p^W_{rx} - c^R_{rx}) \cdot \Delta s_{rx} \right) \end{equation}\]
where \(\Delta s_{tw}\) is defined as the increase in the share of \(s_{tw}\) that occurs as a result of disagreement between \((r,w)\).
We write a function to define the bargaining first order conditions.
And then with placeholder retail prices, we can then use this first order condition to find equilibrium wholesale prices.
lambda <- 0.5
x0 <- as.numeric(c_W_vec*1.0)
mergersim:::bargain_foc_novert_sim(price_w = x0, own_down = own_down_pre, own_up = own_up_pre,
alpha= alpha, delta = delta,
cost_w = c_W_vec, cost_r = c_R_vec,
lambda = lambda, price_r = (p1*2))
#> [1] 0.04298411
out2 <- BBoptim(par = x0, fn = mergersim:::bargain_foc_novert_sim,
own_down = own_down_pre, own_up = own_up_pre,
alpha= alpha, delta = delta,
cost_w = c_W_vec, cost_r = c_R_vec,
lambda = lambda, price_r = (p1*1))
#> iter: 0 f-value: 0.03762495 pgrad: 0.02387511
#> iter: 10 f-value: 9.23201e-08 pgrad: 4.147732e-05
#> Successful convergence.
out2$par
#> [1] 0.9778942 0.9954343 1.0257930 1.0433438 1.0177135 1.0352643
p_W2 <- out2$par
shares2 <- (exp(delta + alpha*p1))/(1+sum(exp(delta + alpha*p1)))In equilibrium, retail prices and wholesale prices need to be jointly determined. The functions defined above can be solved simultaneously to find equilibrium retail and wholesale prices of the model.
# Check if values make sense
print(p_R1)
#> [1] 2.942863 2.974344 3.066814 3.095804 3.044848 3.074182
print(p_W1)
#> [1] 1.609646 1.640788 1.612805 1.641989 1.613095 1.642563
shares1 <- (exp(delta + alpha*p_R1))/(1+sum(exp(delta + alpha*p_R1)))
as.numeric(shares1)
#> [1] 0.04798858 0.05155402 0.08643620 0.09306666 0.07977231 0.08586495
sum(shares1)
#> [1] 0.4446827
# GFT
mergersim:::bargain_foc_novert_sim(price_w = p_W1, own_down = own_down_pre, own_up = own_up_pre,
alpha= alpha,
delta = delta, cost_w = c_W_vec, cost_r = c_R_vec, lambda = lambda,
price_r = p_R1,
returnGFT = TRUE)
#> $r_gft
#> [,1]
#> R1 0.05597466
#> R1 0.06037794
#> R2 0.10511455
#> R2 0.11398520
#> R3 0.09632519
#> R3 0.10436054
#>
#> $w_gft
#> [,1]
#> W1 0.05580904
#> W2 0.06025085
#> W1 0.10505161
#> W2 0.11386821
#> W1 0.09627565
#> W2 0.10428306
# wholesaler profits
#as.numeric(own_W_pre %*% ((p_W1 - c_W_vec)*shares1) )
# retailer profits
#as.numeric(own_R_pre %*% ((p_R1 - p_W1 - c_R_vec)*shares1) )The first order conditions need to be adjusted when there is vertical integration. Let’s adjust the FOCs to account for vertical integration between retailer 1 and wholesaler 1.
And then use these to determine equilibrium prices in a post-merger market.
# Create post ownership
own_up_post <- own_up_pre
own_down_post <- own_down_pre
own_down_post[own_down_post == "R1"] <- "W1"
bertrand_foc_vert(price_r = p_R1,own_down=own_down_post,own_up=own_up_post,
alpha=alpha,delta=delta,
cost_r = c_R_vec, price_w = p_W1, cost_w = c_W_vec)
#> [,1]
#> W1 -4.778884e-02
#> W1 1.405045e-02
#> R2 1.024178e-05
#> R2 2.737772e-05
#> R3 -4.687353e-06
#> R3 5.286390e-06
bargain_foc_vert_sim(price_w = p_W1,own_down=own_down_post,own_up=own_up_post,
alpha=alpha,delta=delta, cost_w =c_W_vec,
cost_r =c_R_vec, lambda=0.5, price_r = p_R1)
#> [1] 0.0001292351
# Test that foc_vert gives same result as before when VI = 0.
mergersim:::bargain_foc_novert_sim(price_w = p_W1, own_down = own_down_pre, own_up = own_up_pre,
alpha= alpha, delta = delta, cost_w = c_W_vec,
cost_r = c_R_vec, lambda = lambda, price_r = p_R1)
#> [1] 1.742127e-08
bargain_foc_vert_sim(price_w = p_W1,own_down=own_down_pre,own_up=own_up_pre,
alpha=alpha,delta=delta, cost_w =c_W_vec,
cost_r =c_R_vec, lambda=0.5, price_r = p_R1)
#> [1] 1.742127e-08
# And for upstream
bertrand_foc_vert(price_r = p_R1, own_down = own_down_pre, own_up = own_up_pre,
alpha = alpha, delta = delta,
cost_r = c_R_vec, price_w = p_W1,
cost_w = c_W_vec)
#> [,1]
#> R1 2.892024e-05
#> R1 1.532181e-05
#> R2 1.024178e-05
#> R2 2.737772e-05
#> R3 -4.687353e-06
#> R3 5.286390e-06
mergersim:::bertrand_foc_novert(price_r = p_R1, own_down = own_down_pre, alpha = alpha,
delta = delta, cost = c_R_vec, price_w = p_W1)
#> [,1]
#> R1 2.892024e-05
#> R1 1.532181e-05
#> R2 1.024178e-05
#> R2 2.737772e-05
#> R3 -4.687353e-06
#> R3 5.286390e-06Calibration takes place in two steps. First, we use the downstream model to calibrate demand parameters. Then we will use the upstream model to calibrate the bargaining weight. In this code, we require the assumption that pre-merger there is no vertical integration, and that all relevant costs are observed. It is, however, possible to calibrate the model using cost data from only one downstream and one upstream firm.
First we define a function to calibrate the downstream model of Bertrand competition.
Our goal is to see if we can recover the underlying demand parameters based on the observed market shares and retail prices. Note that costs in the downstream include both retailer costs and the wholesale price paid by the retailer for the goods.
J <- length(p_R1)
alpha_start <- -1
delta_start <- rep(0.5,J)
x00b <- -1.1
wt_matrix <- diag(c(rep(1,J),rep(10,J)))
c_j <- c_R_vec # eventually remove c_j from code
out3 <- BBoptim(f = bertrand_vert_calibrate, par = x00b,
own_down = own_down_pre, price = p_R1,
shares = shares1, cost = c_j, price_w = p_W1)
alpha3 <- out3$par
delta3 <- log(shares1) - log(1-sum(shares1)) - alpha3*p_R1
# recover true parameters
alpha
#> [1] -0.9
delta
#> [1] 0.2 0.3 0.9 1.0 0.8 0.9
alpha3
#> [1] -0.8999888
delta3
#> [1] 0.1999670 0.2999666 0.8999656 0.9999653 0.7999658 0.8999655The calibrated demand parameters match the true parameter values.
# note that this optimization recovers the true demand parameters and shares
shares3 <- (exp(delta3 + alpha3*p_R1))/(1+sum(exp(delta3 + alpha3*p_R1)))
shares3
#> [1] 0.04798858 0.05155402 0.08643620 0.09306666 0.07977231 0.08586495
as.numeric(shares1)
#> [1] 0.04798858 0.05155402 0.08643620 0.09306666 0.07977231 0.08586495Next, we calibrate the upstream market, which in particular requires recovering the bargaining weight. If all relevant costs are observed, the only remaining parameter to recover is the bargaining weight, which we can recover from the gains from trade of the bargaining model. First we define the calibration function bargain_vert_sim_calibrate.
And next see if we can recover the true bargaining weight
The functions above assume all costs are observed. Functions that end with the number 2 are modified versions that can accept missing costs.
Suppose that we only observed costs from one downstream firm. We can use these functions to still recover the true demand parameters.
# Set some costs to NA -- (this line not fully general yet)
c_R_vec_NA <- c(c_R_vec[own_down_pre == "R1"], NA, NA, NA, NA)
x00b <- -1.1
bertrand_vert_calibrate(param = x00b,
own_down = own_down_pre, price = p_R1,
shares = shares1, cost = c_R_vec_NA, price_w = p_W1 )
#> iter: 0 f-value: 0.0002437925 pgrad: 0.001064769
#> [,1]
#> [1,] 0.07958337
out3d <- BBoptim(f = bertrand_vert_calibrate, par = x00b,
own_down = own_down_pre, price = p_R1,
shares = shares1, cost = c_R_vec_NA, price_w = p_W1)
#> iter: 0 f-value: 0.0002437925 pgrad: 0.001064769
#> iter: 0 f-value: 0.0002437922 pgrad: 0.001064768
#> iter: 0 f-value: 0.07958337 pgrad: 0.6490849
#> iter: 0 f-value: 0.003920012 pgrad: 0.0008117139
#> iter: 0 f-value: 0.0005521595 pgrad: 0.001350247
#> iter: 0 f-value: 1.554035e-05 pgrad: 0.0002636499
#> iter: 0 f-value: 1.554041e-05 pgrad: 0.0002636504
#> iter: 0 f-value: 9.557828e-06 pgrad: 0.0002122097
#> iter: 0 f-value: 9.557779e-06 pgrad: 0.0002122092
#> iter: 0 f-value: 2.729944e-07 pgrad: 3.622622e-05
#> iter: 0 f-value: 2.729862e-07 pgrad: 3.622568e-05
#> iter: 0 f-value: 3.22003e-09 pgrad: 3.959217e-06
#> iter: 0 f-value: 3.220901e-09 pgrad: 3.959677e-06
#> Successful convergence.
alpha3d <- out3d$par
delta3d <- log(shares1) - log(1-sum(shares1)) - alpha3d*p_R1
# still recover true parameters
alpha
#> [1] -0.9
delta
#> [1] 0.2 0.3 0.9 1.0 0.8 0.9
alpha3d
#> [1] -0.8996829
delta3d
#> [1] 0.1990669 0.2990569 0.8990276 0.9990184 0.7990346 0.8990253Then one can use the demand parameters and first order conditions to back out the implied values for the missing cost information.
x00b <- rep(1,J)
mergersim:::bertrand_vert_calibrate_costs(param = x00b,
own_down = own_down_pre, price = p_R1,
shares = shares1, alpha = alpha, delta = delta,
price_w = p_W1 )
out3e <- BBoptim(f = mergersim:::bertrand_vert_calibrate_costs, par = x00b,
own_down = own_down_pre, price = p_R1,
shares = shares1, alpha = alpha, delta = delta,
price_w = p_W1)When data is inconsistent with this model, even the best fitting calibrated demand parameters will not perfectly match the model prices and shares to the observed prices and shares.
Note that in this case, when data is generated from the model, there is a near perfect positive correlation between downstream prices and downstream margins. If data being input into this model does not exhibit a positive correlation between downstream margins and shares, this model will have a difficult time calibrating parameters to match the data.
Once the true underlying parameters have been recovered, the ownership matrices can be changed to predict the effects of vertical integration in the market.
# ( Ideally, first recover pre-merger prices, using VI functions.)
bertrand_foc_vert(price_r=p_R1,own_down=own_down_post,own_up=own_up_post,
alpha=alpha,delta=delta,
cost_r=c_R_vec,price_w=p_W1, cost_w=c_W_vec)
bargain_foc_vert_sim(price_w=p_W1,own_down = own_down_post,own_up=own_up_post,
alpha=alpha,delta=delta, cost_w=c_W_vec,
cost_r=c_R_vec, lambda=0.5, price_r = p_R1)
tol <- .0001
error <- 1
p_W0_post <- matrix(.25, nrow = (R*W), ncol = 1) # dummy wholesale prices
p_R0_post <- p_r_start
while (error > tol) {
# Use EITHER multiroot or BBoptim
# multiroot:
# out1 <- multiroot(f = bertrand_foc_vert ,start = p_R0_post,
# own_down = own_down_post, own_up = own_up_post,
# alpha = alpha3, delta = delta3, c_R = c_R_vec,
# p_W = p_W0_post, c_W = c_W_vec)
#
# p_R1_post <- out1$root
# BBoptim:
out1 <- BBoptim(f = bertrand_foc_vert, par = p_R0_post,
own_down = own_down_post, own_up = own_up_post,
alpha = alpha3, delta = delta3, cost_r = c_R_vec,
price_w = p_W0_post, cost_w = c_W_vec, sumFOC = TRUE)
p_R1_post <- out1$par
out2 <- BBoptim(par = as.numeric(p_W0_post), fn = bargain_foc_vert_sim,
own_down = own_down_post, own_up = own_up_post,
alpha= alpha3, delta = delta3,
cost_w = c_W_vec, cost_r = c_R_vec, lambda = lambda_end,
price_r = p_R1_post)
p_W1_post <- out2$par
error <- max(abs(c(p_W1_post-p_W0_post,p_R1_post-p_R0_post)))
print(error)
p_W0_post <- p_W1_post
p_R0_post <- p_R1_post
}p_R1
#> [1] 2.942863 2.974344 3.066814 3.095804 3.044848 3.074182
p_W1
#> [1] 1.609646 1.640788 1.612805 1.641989 1.613095 1.642563
p_R1_post
#> [1] 1.876456 3.280460 3.206903 3.044468 3.186708 3.023596
p_W1_post
#> [1] 0.250000 1.606545 1.775417 1.613407 1.775412 1.613352
# retail price change with vertical integration
(p_R1_post-p_R1)/p_R1
#> [1] -0.36237072 0.10291877 0.04567885 -0.01658255 0.04659025 -0.01645509We can also show that the R package ‘antitrust’ can be used to recover the same true demand parameters.
shareDown <- as.numeric(shares1)
priceDown <- as.numeric(p_R1)
ownerPreDown <- own_down_pre
priceUp <- as.numeric(p_W1)
ownerPreUp <- own_up_pre
priceOutSide <- 0
# Downstream margin as a percent
marginDown <- (priceDown - c_R_vec - priceUp)/ priceDown
# Upstream margin as a percent
marginUp <- (priceUp - c_W_vec) / priceUp
## Simulate a vertical merger
ownerPostUp <- own_up_post
ownerPostDown <- own_down_post
simres_vert <- vertical.barg(sharesDown =shareDown,
pricesDown = priceDown,
marginsDown = as.numeric(marginDown),
ownerPreDown = ownerPreDown,
ownerPostDown = ownerPostDown,
pricesUp = priceUp,
marginsUp = as.numeric(marginUp),
ownerPreUp = ownerPreUp,
ownerPostUp = ownerPostUp,
priceOutside = priceOutSide)
summary(simres_vert)
#>
#> Merger simulation results under 'VertBargBertLogit' demand:
#>
#> priceUpPre priceUpPost priceUpDelta priceDownPre priceDownPost priceDownDelta sharesPre
#> * Prod1 1.6 0.2 -87.7 2.9 1.9 -36.4 10
#> * Prod2 1.6 1.6 -1.8 3.0 3.3 10.5 11
#> * Prod3 1.6 1.8 10.1 3.1 3.2 4.5 20
#> Prod4 1.6 1.6 -1.8 3.1 3.0 -1.7 21
#> * Prod5 1.6 1.8 10.1 3.0 3.2 4.6 18
#> Prod6 1.6 1.6 -1.8 3.1 3.0 -1.6 19
#> sharesPost outputDelta
#> * Prod1 16.8 61.31
#> * Prod2 9.1 -19.35
#> * Prod3 17.5 -10.77
#> Prod4 21.2 -0.30
#> * Prod5 16.0 -10.85
#> Prod6 19.4 -0.43
#>
#> Notes: '*' indicates merging parties' products.
#> Deltas are percent changes.
#> Output is based on revenues.
simres_vert@down@slopes
#> $alpha
#> [1] -0.9001873
#>
#> $meanval
#> [1] 0.2005513 0.3005572 0.9005745 1.0005800 0.8005704 0.9005759
#>
#> $sigma
#> [1] 0.1
(simres_vert@down@pricePost - simres_vert@down@pricePre) / simres_vert@down@pricePre
#> [1] -0.36360082 0.10537542 0.04533485 -0.01696478 0.04634040 -0.01641208