program define ge_gravity version 11.2 *! A simple Stata program for solving for GE effects of trade policies, by Tom Zylkin *! Department of Economics, University of Richmond *! This version: v1.1, March 2019 *! *! Suggested citation: Baier, Yotov, and Zylkin (2019): *! "On the Widely Differing Effects of Free Trade Agreements: Lessons from Twenty Years of Trade Integration" *! Journal of International Economics, 116, 206-226. * v1.0: first version * v1.1: now allows you to save computed changes in nominal wages and price indices syntax anything [if] [in] /// ,theta(real) gen_w(name) gen_X(name) [gen_rw(name) MULTiplicative gen_nw(name) gen_P(name)] * Ex: "GE_gravity exporter importer X beta, theta(-4) gen_w(welf) gen_X(new_pi)" will generate GE * welfare effects "gen_w" and new trade values "gen_X" for a set of countries * with initial trade values "X", resulting from a set of partial effects "beta", * under the assumption that the trade elasticity, "theta", is 4. * Note the assumptions used here: * - Only one sector (i.e., best suited for aggregate trade) * - One factor of production (labor) * - Trade in final goods only (no intermediates) * - Linear (as opposed to multiplicative) trade balances. * In addition, note that trade values are normalized by holding total nominal world * output constant. This provides a numeraire. gettoken exp_id rest : anything gettoken imp_id rest : rest gettoken X rest : rest gettoken beta close : rest qui marksample touse // Check to make sure each obs. is uniquely ID'd by a single origin and destination local id_flag = 0 local check_ids = "`exp_id' `imp_id'" local are_both_ids_strings = 0 foreach v of varlist `check_ids' { cap confirm numeric variable `v' if _rc { local are_both_ids_strings = `are_both_ids_strings' + 1 local `v'_is_string = 1 } else{ local `v'_is_string = 0 } } if `are_both_ids_strings' == 0 { mata: id_check("`check_ids'", "`touse'") } if `are_both_ids_strings' == 2 { mata: id_check_string("`check_ids'", "`touse'") } if `are_both_ids_strings' == 1 { foreach v of varlist `check_ids' { tempvar `v'2 if ``v'_is_string' == 1 { qui egen ``v'2' = group(`v') } else{ qui gen ``v'2' = `v' } local check_ids2 = "`check_ids2'" + "``v'2' " } mata: id_check("`check_ids2'", "`touse'") } if `id_flag' != 0 { di in red "Error: the set of origin and destination IDs do not uniquely describe the data" di in red "If this is not a mistake, try collapsing the data first using collapse (sum)" exit 111 } tempname elasticity scalar `elasticity' = `theta' if "`gen_rw'" == ""{ tempname gen_rw } if "`gen_nw'" == ""{ tempname gen_nw } if "`gen_P'" == ""{ tempname gen_P } cap gen `gen_w' = . cap gen `gen_X' = . cap gen `gen_rw' = . cap gen `gen_nw' = . cap gen `gen_P' = . di "sorting..." sort `exp_id' `imp_id' di "solving..." mata: ge_solver("`X'", "`beta'", "`elasticity'", "`multiplicative'", "`gen_w'", "`gen_X'", "`gen_rw'", "`gen_nw'", "`gen_P'", "`touse'") di "solved!" end * Iteration procedure: * * 0. (initial competitive equilibrium): Y_i = w_i * L_i = sum_j { pi_ij * E_j } * * = A_i * w_i^-theta * sum_j {d_ij^-theta * E_j / P_j^-theta} (eq 1) * * where: P_j^-theta = sum_i {A_i * w_i^-theta * d_ij^-theta} (eq 2) * * 0.1. (new equilibrium in changes): Y_i * w_i_hat = w_i_hat^-theta * sum_j { pi_ij * e^beta * E_j' / P_j_hat^-theta } (eq 3) * * where: P_j_hat^-theta = sum_i { pi_ij * e^beta * w_i_hat^-theta } (eq 4) * * and: E_j' = Y_j * w_j_hat + D_j (eq 5) * * 1. Update w_i_hat 1 time using (eq 3). * * 1.5. Normalize all prices so world output stays the same (acts as numeraire): sum_j { Y_j * w_j_hat } = sum_j { Y_j } * * 2. Update P_j_hat^-theta 1 time using (eq 4). * * 3. Update E_j' 1 time using (eq 5) * * 4. Return to Step 1, using new values for w_hat, P_j_hat^-theta, and E_j'. Iterate until convergence. mata: void ge_solver(string scalar trade, string scalar partials, string scalar elasticity, string scalar mult_opt, string scalar gen1, string scalar gen2, string scalar gen3, string scalar gen4, string scalar gen5, string scalar ok) { /* read data from Stata memory */ X = st_data(.,tokens(trade),ok) beta = st_data(.,tokens(partials),ok) theta = st_numscalar(tokens(elasticity)) /* ensure data set includes all possible flows for each location */ N = sqrt(rows(X)) if (floor(N) != N) { displayas("err") printf("\nNon-square data set detected. The size of the data should be NxN. Check whether every location has N trade partners, including itself. Exiting.\n \n") exit(1) } /* flash warning if trade matrix has missing values */ if (missing(X)>0) { displayas("err") printf("Flow values missing for at least 1 pair; assumed to be zero.\n") X=editmissing(X,0) displayas("text") } /* check for negative trade values */ if (min(X)<0) { displayas("err") printf("\nNegative flow values detected. Exiting.\n \n") exit(1) } /* Set up X_ij trade matrix: exporters (rows) by importers (columns) */ X = colshape(X, N) /* check that internal trade is included for all countries */ if(min(diagonal(X))==0) { displayas("err") printf("\nX_ii is missing or zero for at least 1 location. Exiting.\n \n") exit(1) } /* flash warning if any beta values are missing */ if (missing(beta)>0) { displayas("err") printf("beta values missing for at least 1 pair; assumed to be zero.\n") beta=editmissing(beta,0) displayas("text") } /* "B" (= e^beta) is the matrix of partial effects */ B = colshape(exp(beta), N) /* flash warning if betas on the diagonal are not zero. */ if (min(diagonal(B):== 1) != 1) { displayas("err") printf("Non-zero beta values for some X_ii terms detected. These have been set to zero.\n") B = B - diag(B) :+ I(N) // should be all 1's on the diagonal displayas("text") } /* Set up Y and E vectors; calculate trade balances */ E = colsum(X)' Y = rowsum(X) D = E - Y // for if user specifies "multiplicative" option for trade imbalances if (mult_opt == "") { mult = 0 } else { mult = 1 } /* set up pi_ij matrix of trade shares */ pi = X :/ (E'#J(N,1,1)) /* Initialize w_i_hat = P_j_hat = 1 */ w_hat = P_hat = J(N,1,1) /* while loop */ X_new = X crit = 1 j = 0 max_iter = 1000000 tol = .00000001 do { X_last_step = X_new /* Step 1: update w_i_hat */ w_hat = ( ((pi :* B) * (E :/ P_hat)) :/ Y ) :^ (1/(1+theta)) /* Step 1.5: Normalize so total world output stays the same */ w_hat = w_hat :* (sum(Y) :/ sum(Y :* w_hat)) /* Step 2: update P_j_hat */ P_hat = (pi' :* B') * (w_hat :^-theta) /* Step 3: update E_j' */ if (mult) { E = (Y+D) :* w_hat } else{ E = Y :* w_hat + D // default is to have additive trade imbalances } /* Calculate new trade shares (to verify convergence) */ pi_new = (pi :* B) :* ((w_hat:^-theta)#J(1,N,1)) :/ (P_hat'#J(N,1,1)) X_new = pi_new :* (E'#J(N,1,1)) crit = max(abs(ln(X_new) - ln(X_last_step))) j = j + 1 } while (crit > tol & j < max_iter) /* Post welfare effects and new trade values to Stata memory */ X_new = colshape(X_new,1) real_wage = w_hat :/ (P_hat):^(-1/theta) if (mult) { welfare = real_wage } else { welfare = ((Y :* w_hat) + D) :/ (Y+D) :/ (P_hat):^(-1/theta) } welfare = welfare # J(N, 1, 1) real_wage = real_wage # J(N, 1, 1) nom_wage = w_hat # J(N, 1, 1) price_index = ((P_hat):^(-1/theta)) # J(N, 1, 1) /* For in case you want to check the change real wages: */ /* real_wage' welfare' X_new' */ /* could also put: real_wage = (diagonal(pi_new) :/ diagonal(pi)) :^ (-1/theta) (ACR formula) */ st_store(.,tokens(gen1), ok, welfare ) st_store(.,tokens(gen2), ok, X_new ) st_store(.,tokens(gen3), ok, real_wage ) st_store(.,tokens(gen4), ok, nom_wage ) st_store(.,tokens(gen5), ok, price_index ) } /*************************************************************/ /* CHECK_ID (checks whether ID vars uniquely describe data) */ /*************************************************************/ // Sometimes users may make a mistake in providing duplicate observations for the same trade flow. // This will generate a error letting them know. If this is not done by accident, // perhaps the data needs to be aggregated by collapsing the data. mata: void id_check(string scalar idvars,| string scalar touse) { st_view(id_vars,.,tokens(idvars), touse) uniq_ids = uniqrows(id_vars) if (rows(id_vars) != rows(uniq_ids)) { st_local("id_flag", "1") } } mata: void id_check_string(string scalar idvars,| string scalar touse) { st_sview(id_vars,.,tokens(idvars), touse) uniq_ids = uniqrows(id_vars) if (rows(id_vars) != rows(uniq_ids)) { st_local("id_flag", "1") } } end