mergeron 2024.738973.0__py3-none-any.whl → 2024.739079.10__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of mergeron might be problematic. Click here for more details.
- mergeron/__init__.py +28 -3
- mergeron/core/__init__.py +2 -77
- mergeron/core/damodaran_margin_data.py +66 -52
- mergeron/core/excel_helper.py +39 -37
- mergeron/core/ftc_merger_investigations_data.py +66 -35
- mergeron/core/guidelines_boundaries.py +261 -234
- mergeron/core/guidelines_boundary_functions.py +182 -27
- mergeron/core/guidelines_boundary_functions_extra.py +17 -14
- mergeron/core/proportions_tests.py +2 -4
- mergeron/core/pseudorandom_numbers.py +6 -11
- mergeron/data/__init__.py +3 -0
- mergeron/data/damodaran_margin_data.xls +0 -0
- mergeron/data/damodaran_margin_data_dict.msgpack +0 -0
- mergeron/{jinja_LaTex_templates/setup_tikz_tables.tex.jinja2 → data/jinja2_LaTeX_templates/setup_tikz_tables.tex} +45 -50
- mergeron/demo/__init__.py +3 -0
- mergeron/demo/visualize_empirical_margin_distribution.py +88 -0
- mergeron/ext/__init__.py +2 -4
- mergeron/ext/tol_colors.py +3 -3
- mergeron/gen/__init__.py +53 -46
- mergeron/gen/_data_generation_functions.py +28 -93
- mergeron/gen/data_generation.py +20 -24
- mergeron/gen/{investigations_stats.py → enforcement_stats.py} +59 -57
- mergeron/gen/market_sample.py +6 -10
- mergeron/gen/upp_tests.py +29 -26
- mergeron-2024.739079.10.dist-info/METADATA +109 -0
- mergeron-2024.739079.10.dist-info/RECORD +36 -0
- mergeron/core/InCommon RSA Server CA cert chain.pem +0 -68
- mergeron-2024.738973.0.dist-info/METADATA +0 -108
- mergeron-2024.738973.0.dist-info/RECORD +0 -32
- /mergeron/{core → data}/ftc_invdata.msgpack +0 -0
- /mergeron/{jinja_LaTex_templates → data/jinja2_LaTeX_templates}/clrrate_cis_summary_table_template.tex.jinja2 +0 -0
- /mergeron/{jinja_LaTex_templates → data/jinja2_LaTeX_templates}/ftcinvdata_byhhianddelta_table_template.tex.jinja2 +0 -0
- /mergeron/{jinja_LaTex_templates → data/jinja2_LaTeX_templates}/ftcinvdata_summary_table_template.tex.jinja2 +0 -0
- /mergeron/{jinja_LaTex_templates → data/jinja2_LaTeX_templates}/ftcinvdata_summarypaired_table_template.tex.jinja2 +0 -0
- /mergeron/{jinja_LaTex_templates → data/jinja2_LaTeX_templates}/mergeron.cls +0 -0
- /mergeron/{jinja_LaTex_templates → data/jinja2_LaTeX_templates}/mergeron_table_collection_template.tex.jinja2 +0 -0
- {mergeron-2024.738973.0.dist-info → mergeron-2024.739079.10.dist-info}/WHEEL +0 -0
|
@@ -4,26 +4,20 @@ with a canvas on which to draw boundaries for Guidelines standards.
|
|
|
4
4
|
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
7
9
|
from dataclasses import dataclass
|
|
8
|
-
from importlib.metadata import version
|
|
9
10
|
from typing import Literal, TypeAlias
|
|
10
11
|
|
|
11
12
|
import numpy as np
|
|
12
|
-
from attrs import field, frozen
|
|
13
|
+
from attrs import Attribute, field, frozen, validators
|
|
13
14
|
from mpmath import mp, mpf # type: ignore
|
|
15
|
+
from numpy.typing import NDArray
|
|
14
16
|
|
|
15
|
-
from .. import
|
|
16
|
-
from . import
|
|
17
|
-
from .guidelines_boundary_functions import (
|
|
18
|
-
dh_area,
|
|
19
|
-
round_cust,
|
|
20
|
-
shrratio_boundary_max,
|
|
21
|
-
shrratio_boundary_min,
|
|
22
|
-
shrratio_boundary_wtd_avg,
|
|
23
|
-
shrratio_boundary_xact_avg,
|
|
24
|
-
)
|
|
17
|
+
from .. import VERSION, RECConstants, UPPAggrSelector # noqa: TID252
|
|
18
|
+
from . import guidelines_boundary_functions as gbfn
|
|
25
19
|
|
|
26
|
-
__version__ =
|
|
20
|
+
__version__ = VERSION
|
|
27
21
|
|
|
28
22
|
|
|
29
23
|
mp.prec = 80
|
|
@@ -75,6 +69,14 @@ class GuidelinesThresholds:
|
|
|
75
69
|
diversion ratio limit, CMCR, and IPR
|
|
76
70
|
"""
|
|
77
71
|
|
|
72
|
+
presumption: HMGThresholds = field(kw_only=True, default=None)
|
|
73
|
+
"""
|
|
74
|
+
Presumption of harm defined in HMG
|
|
75
|
+
|
|
76
|
+
ΔHHI bound and corresponding default recapture rate, GUPPI bound,
|
|
77
|
+
diversion ratio limit, CMCR, and IPR
|
|
78
|
+
"""
|
|
79
|
+
|
|
78
80
|
imputed_presumption: HMGThresholds = field(kw_only=True, default=None)
|
|
79
81
|
"""
|
|
80
82
|
Presumption of harm imputed from guidelines
|
|
@@ -84,14 +86,6 @@ class GuidelinesThresholds:
|
|
|
84
86
|
GUPPI bound, diversion ratio limit, CMCR, and IPR
|
|
85
87
|
"""
|
|
86
88
|
|
|
87
|
-
presumption: HMGThresholds = field(kw_only=True, default=None)
|
|
88
|
-
"""
|
|
89
|
-
Presumption of harm defined in HMG
|
|
90
|
-
|
|
91
|
-
ΔHHI bound and corresponding default recapture rate, GUPPI bound,
|
|
92
|
-
diversion ratio limit, CMCR, and IPR
|
|
93
|
-
"""
|
|
94
|
-
|
|
95
89
|
def __attrs_post_init__(self, /) -> None:
|
|
96
90
|
# In the 2023 Guidlines, the agencies do not define a
|
|
97
91
|
# negative presumption, or safeharbor. Practically speaking,
|
|
@@ -102,7 +96,7 @@ class GuidelinesThresholds:
|
|
|
102
96
|
_hhi_p, _dh_s, _dh_p = {
|
|
103
97
|
1992: (0.18, 0.005, 0.01),
|
|
104
98
|
2010: (0.25, 0.01, 0.02),
|
|
105
|
-
2004: (0.20, 0.015, 0.
|
|
99
|
+
2004: (0.20, 0.015, 0.025),
|
|
106
100
|
2023: (0.18, 0.01, 0.01),
|
|
107
101
|
}[self.pub_year]
|
|
108
102
|
|
|
@@ -112,15 +106,30 @@ class GuidelinesThresholds:
|
|
|
112
106
|
HMGThresholds(
|
|
113
107
|
_dh_s,
|
|
114
108
|
_fc := int(np.ceil(1 / _hhi_p)),
|
|
115
|
-
_r := round_cust(_fc / (_fc + 1)),
|
|
109
|
+
_r := gbfn.round_cust(_fc / (_fc + 1), frac=0.05),
|
|
116
110
|
_g_s := guppi_from_delta(_dh_s, m_star=1.0, r_bar=_r),
|
|
117
|
-
_dr :=
|
|
111
|
+
_dr := (1 - _r),
|
|
118
112
|
_cmcr := 0.03, # Not strictly a Guidelines standard
|
|
119
113
|
_ipr := _g_s, # Not strictly a Guidelines standard
|
|
120
114
|
),
|
|
121
115
|
)
|
|
122
116
|
|
|
117
|
+
object.__setattr__(
|
|
118
|
+
self,
|
|
119
|
+
"presumption",
|
|
120
|
+
HMGThresholds(
|
|
121
|
+
_dh_p,
|
|
122
|
+
_fc,
|
|
123
|
+
_r,
|
|
124
|
+
_g_p := guppi_from_delta(_dh_p, m_star=1.0, r_bar=_r),
|
|
125
|
+
_dr,
|
|
126
|
+
_cmcr,
|
|
127
|
+
_ipr := _g_p,
|
|
128
|
+
),
|
|
129
|
+
)
|
|
130
|
+
|
|
123
131
|
# imputed_presumption is relevant for 2010 Guidelines
|
|
132
|
+
# merger to symmettry in numbers-equivalent of post-merger HHI
|
|
124
133
|
object.__setattr__(
|
|
125
134
|
self,
|
|
126
135
|
"imputed_presumption",
|
|
@@ -128,18 +137,18 @@ class GuidelinesThresholds:
|
|
|
128
137
|
HMGThresholds(
|
|
129
138
|
_dh_i := 2 * (0.5 / _fc) ** 2,
|
|
130
139
|
_fc,
|
|
131
|
-
_r_i := round_cust((_fc - 1 / 2) / (_fc + 1 / 2)),
|
|
132
|
-
_g_i := guppi_from_delta(
|
|
133
|
-
|
|
140
|
+
_r_i := gbfn.round_cust((_fc - 1 / 2) / (_fc + 1 / 2), frac=0.05),
|
|
141
|
+
_g_i := guppi_from_delta(_dh_p, m_star=1.0, r_bar=_r_i),
|
|
142
|
+
1 / 2 * (1 - _r_i),
|
|
134
143
|
_cmcr,
|
|
135
144
|
_g_i,
|
|
136
145
|
)
|
|
137
|
-
if self.pub_year
|
|
146
|
+
if self.pub_year in (2004, 2010)
|
|
138
147
|
else HMGThresholds(
|
|
139
148
|
_dh_i := 2 * (1 / (_fc + 1)) ** 2,
|
|
140
149
|
_fc,
|
|
141
150
|
_r,
|
|
142
|
-
_g_i := guppi_from_delta(
|
|
151
|
+
_g_i := guppi_from_delta(_dh_p, m_star=1.0, r_bar=_r),
|
|
143
152
|
_dr,
|
|
144
153
|
_cmcr,
|
|
145
154
|
_g_i,
|
|
@@ -147,30 +156,232 @@ class GuidelinesThresholds:
|
|
|
147
156
|
),
|
|
148
157
|
)
|
|
149
158
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
159
|
+
|
|
160
|
+
def _concentration_threshold_validator(
|
|
161
|
+
_instance: ConcentrationBoundary, _attribute: Attribute[float], _value: float, /
|
|
162
|
+
) -> None:
|
|
163
|
+
if not 0 <= _value <= 1:
|
|
164
|
+
raise ValueError("Concentration threshold must lie between 0 and 1.")
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def _concentration_measure_name_validator(
|
|
168
|
+
_instance: ConcentrationBoundary, _attribute: Attribute[str], _value: str, /
|
|
169
|
+
) -> None:
|
|
170
|
+
if _value not in ("ΔHHI", "Combined share", "Pre-merger HHI", "Post-merger HHI"):
|
|
171
|
+
raise ValueError(f"Invalid name for a concentration measure, {_value!r}.")
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
@frozen
|
|
175
|
+
class ConcentrationBoundary:
|
|
176
|
+
"""Concentration parameters, boundary coordinates, and area under concentration boundary."""
|
|
177
|
+
|
|
178
|
+
threshold: float = field(
|
|
179
|
+
kw_only=False,
|
|
180
|
+
default=0.01,
|
|
181
|
+
validator=(validators.instance_of(float), _concentration_threshold_validator),
|
|
182
|
+
)
|
|
183
|
+
precision: int = field(
|
|
184
|
+
kw_only=False, default=5, validator=validators.instance_of(int)
|
|
185
|
+
)
|
|
186
|
+
measure_name: Literal[
|
|
187
|
+
"ΔHHI", "Combined share", "Pre-merger HHI", "Post-merger HHI"
|
|
188
|
+
] = field(
|
|
189
|
+
kw_only=False,
|
|
190
|
+
default="ΔHHI",
|
|
191
|
+
validator=(validators.instance_of(str), _concentration_measure_name_validator),
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
coordinates: NDArray[np.float64] = field(init=False, kw_only=True)
|
|
195
|
+
"""Market-share pairs as Cartesian coordinates of points on the concentration boundary."""
|
|
196
|
+
|
|
197
|
+
area: float = field(init=False, kw_only=True)
|
|
198
|
+
"""Area under the concentration boundary."""
|
|
199
|
+
|
|
200
|
+
def __attrs_post_init__(self, /) -> None:
|
|
201
|
+
match self.measure_name:
|
|
202
|
+
case "ΔHHI":
|
|
203
|
+
_conc_fn = gbfn.hhi_delta_boundary
|
|
204
|
+
case "Combined share":
|
|
205
|
+
_conc_fn = gbfn.combined_share_boundary
|
|
206
|
+
case "Pre-merger HHI":
|
|
207
|
+
_conc_fn = gbfn.hhi_pre_contrib_boundary
|
|
208
|
+
case "Post-merger HHI":
|
|
209
|
+
_conc_fn = gbfn.hhi_post_contrib_boundary
|
|
210
|
+
|
|
211
|
+
_boundary = _conc_fn(self.threshold, prec=self.precision)
|
|
212
|
+
object.__setattr__(self, "coordinates", _boundary.coordinates)
|
|
213
|
+
object.__setattr__(self, "area", _boundary.area)
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def _divr_value_validator(
|
|
217
|
+
_instance: DiversionRatioBoundary, _attribute: Attribute[float], _value: float, /
|
|
218
|
+
) -> None:
|
|
219
|
+
if not 0 <= _value <= 1:
|
|
220
|
+
raise ValueError(
|
|
221
|
+
"Margin-adjusted benchmark share ratio must lie between 0 and 1."
|
|
162
222
|
)
|
|
163
223
|
|
|
164
224
|
|
|
225
|
+
def _rec_spec_validator(
|
|
226
|
+
_instance: DiversionRatioBoundary,
|
|
227
|
+
_attribute: Attribute[RECConstants],
|
|
228
|
+
_value: RECConstants,
|
|
229
|
+
/,
|
|
230
|
+
) -> None:
|
|
231
|
+
if _value == RECConstants.OUTIN and _instance.recapture_rate:
|
|
232
|
+
raise ValueError(
|
|
233
|
+
f"Invalid recapture specification, {_value!r}. "
|
|
234
|
+
"You may consider specifying `mergeron.RECConstants.INOUT` here, and "
|
|
235
|
+
'assigning the default recapture rate as attribute, "recapture_rate" of '
|
|
236
|
+
"this `DiversionRatioBoundarySpec` object."
|
|
237
|
+
)
|
|
238
|
+
if _value is None and _instance.agg_method != UPPAggrSelector.MAX:
|
|
239
|
+
raise ValueError(
|
|
240
|
+
f"Specified aggregation method, {_instance.agg_method} requires a recapture specification."
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
@frozen
|
|
245
|
+
class DiversionRatioBoundary:
|
|
246
|
+
"""
|
|
247
|
+
Diversion ratio specification, boundary coordinates, and area under boundary.
|
|
248
|
+
|
|
249
|
+
Along with the default diversion ratio and recapture rate,
|
|
250
|
+
a diversion ratio boundary specification includes the recapture form --
|
|
251
|
+
whether fixed for both merging firms' products ("proportional") or
|
|
252
|
+
consistent with share-proportionality, i.e., "inside-out";
|
|
253
|
+
the method of aggregating diversion ratios for the two products, and
|
|
254
|
+
the precision for the estimate of area under the divertion ratio boundary
|
|
255
|
+
(also defines the number of points on the boundary).
|
|
256
|
+
|
|
257
|
+
"""
|
|
258
|
+
|
|
259
|
+
diversion_ratio: float = field(
|
|
260
|
+
kw_only=False,
|
|
261
|
+
default=0.065,
|
|
262
|
+
validator=(validators.instance_of(float), _divr_value_validator),
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
recapture_rate: float = field(
|
|
266
|
+
kw_only=False, default=0.85, validator=validators.instance_of(float)
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
recapture_form: RECConstants | None = field(
|
|
270
|
+
kw_only=True,
|
|
271
|
+
default=RECConstants.INOUT,
|
|
272
|
+
validator=(
|
|
273
|
+
validators.instance_of((type(None), RECConstants)),
|
|
274
|
+
_rec_spec_validator,
|
|
275
|
+
),
|
|
276
|
+
)
|
|
277
|
+
"""
|
|
278
|
+
The form of the recapture rate.
|
|
279
|
+
|
|
280
|
+
When :attr:`mergeron.RECConstants.INOUT`, the recapture rate for
|
|
281
|
+
he product having the smaller market-share is assumed to equal the default,
|
|
282
|
+
and the recapture rate for the product with the larger market-share is
|
|
283
|
+
computed assuming MNL demand. Fixed recapture rates are specified as
|
|
284
|
+
:attr:`mergeron.RECConstants.FIXED`. (To specify that recapture rates be
|
|
285
|
+
constructed from the generated purchase-probabilities for products in
|
|
286
|
+
the market and for the outside good, specify :attr:`mergeron.RECConstants.OUTIN`.)
|
|
287
|
+
|
|
288
|
+
The GUPPI boundary is a continuum of diversion ratio boundaries conditional on
|
|
289
|
+
price-cost margins, :math:`d_{ij} = g_i * p_i / (m_j * p_j)`,
|
|
290
|
+
with :math:`d_{ij}` the diverion ratio from product :math:`i` to product :math:`j`;
|
|
291
|
+
:math:`g_i` the GUPPI for product :math:`i`;
|
|
292
|
+
:math:`m_j` the margin for product :math:`j`; and
|
|
293
|
+
:math:`p_i, p_j` the prices of goods :math:`i, j`, respectively.
|
|
294
|
+
|
|
295
|
+
"""
|
|
296
|
+
|
|
297
|
+
agg_method: UPPAggrSelector = field(
|
|
298
|
+
kw_only=True,
|
|
299
|
+
default=UPPAggrSelector.MAX,
|
|
300
|
+
validator=validators.instance_of(UPPAggrSelector),
|
|
301
|
+
)
|
|
302
|
+
"""
|
|
303
|
+
Method for aggregating the distinct diversion ratio measures for the two products.
|
|
304
|
+
|
|
305
|
+
Distinct diversion ratio or GUPPI measures for the two merging-firms' products are
|
|
306
|
+
aggregated using the method specified by the `agg_method` attribute, which is specified
|
|
307
|
+
using the enum :class:`mergeron.UPPAggrSelector`.
|
|
308
|
+
|
|
309
|
+
"""
|
|
310
|
+
|
|
311
|
+
precision: int = field(
|
|
312
|
+
kw_only=False, default=5, validator=validators.instance_of(int)
|
|
313
|
+
)
|
|
314
|
+
"""
|
|
315
|
+
The number of decimal places of precision for the estimated area under the UPP boundary.
|
|
316
|
+
|
|
317
|
+
Leaving this attribute unspecified will result in the default precision,
|
|
318
|
+
which varies based on the `agg_method` attribute, reflecting
|
|
319
|
+
the limit of precision available from the underlying functions. The number of
|
|
320
|
+
boundary points generated is also defined based on this attribute.
|
|
321
|
+
|
|
322
|
+
"""
|
|
323
|
+
|
|
324
|
+
coordinates: NDArray[np.float64] = field(init=False, kw_only=True)
|
|
325
|
+
"""Market-share pairs as Cartesian coordinates of points on the diversion ratio boundary."""
|
|
326
|
+
|
|
327
|
+
area: float = field(init=False, kw_only=True)
|
|
328
|
+
"""Area under the diversion ratio boundary."""
|
|
329
|
+
|
|
330
|
+
def __attrs_post_init__(self, /) -> None:
|
|
331
|
+
_share_ratio = critical_share_ratio(
|
|
332
|
+
self.diversion_ratio, r_bar=self.recapture_rate
|
|
333
|
+
)
|
|
334
|
+
_upp_agg_kwargs: gbfn.ShareRatioBoundaryKeywords = {
|
|
335
|
+
"recapture_form": getattr(self.recapture_form, "value", "inside-out"),
|
|
336
|
+
"prec": self.precision,
|
|
337
|
+
}
|
|
338
|
+
match self.agg_method:
|
|
339
|
+
case UPPAggrSelector.DIS:
|
|
340
|
+
_upp_agg_fn = gbfn.shrratio_boundary_wtd_avg
|
|
341
|
+
_upp_agg_kwargs |= {"agg_method": "distance", "weighting": None}
|
|
342
|
+
case UPPAggrSelector.AVG:
|
|
343
|
+
_upp_agg_fn = gbfn.shrratio_boundary_xact_avg # type: ignore
|
|
344
|
+
case UPPAggrSelector.MAX:
|
|
345
|
+
_upp_agg_fn = gbfn.shrratio_boundary_max # type: ignore
|
|
346
|
+
_upp_agg_kwargs = {"prec": 10} # replace here
|
|
347
|
+
case UPPAggrSelector.MIN:
|
|
348
|
+
_upp_agg_fn = gbfn.shrratio_boundary_min # type: ignore
|
|
349
|
+
_upp_agg_kwargs |= {"prec": 10} # update here
|
|
350
|
+
case _:
|
|
351
|
+
_upp_agg_fn = gbfn.shrratio_boundary_wtd_avg
|
|
352
|
+
|
|
353
|
+
_aggregator: Literal["arithmetic mean", "geometric mean", "distance"]
|
|
354
|
+
if self.agg_method.value.endswith("average"):
|
|
355
|
+
_aggregator = "arithmetic mean"
|
|
356
|
+
elif self.agg_method.value.endswith("geometric mean"):
|
|
357
|
+
_aggregator = "geometric mean"
|
|
358
|
+
else:
|
|
359
|
+
_aggregator = "distance"
|
|
360
|
+
|
|
361
|
+
_wgt_type: Literal["cross-product-share", "own-share", None]
|
|
362
|
+
if self.agg_method.value.startswith("cross-product-share"):
|
|
363
|
+
_wgt_type = "cross-product-share"
|
|
364
|
+
elif self.agg_method.value.startswith("own-share"):
|
|
365
|
+
_wgt_type = "own-share"
|
|
366
|
+
else:
|
|
367
|
+
_wgt_type = None
|
|
368
|
+
|
|
369
|
+
_upp_agg_kwargs |= {"agg_method": _aggregator, "weighting": _wgt_type}
|
|
370
|
+
|
|
371
|
+
_boundary = _upp_agg_fn(_share_ratio, self.recapture_rate, **_upp_agg_kwargs)
|
|
372
|
+
object.__setattr__(self, "coordinates", _boundary.coordinates)
|
|
373
|
+
object.__setattr__(self, "area", _boundary.area)
|
|
374
|
+
|
|
375
|
+
|
|
165
376
|
def guppi_from_delta(
|
|
166
|
-
_delta_bound: float = 0.01, /, *, m_star: float = 1.00, r_bar: float = 0.
|
|
377
|
+
_delta_bound: float = 0.01, /, *, m_star: float = 1.00, r_bar: float = 0.8
|
|
167
378
|
) -> float:
|
|
168
379
|
"""
|
|
169
380
|
Translate ∆HHI bound to GUPPI bound.
|
|
170
381
|
|
|
171
382
|
Parameters
|
|
172
383
|
----------
|
|
173
|
-
|
|
384
|
+
_delta_bound
|
|
174
385
|
Specified ∆HHI bound.
|
|
175
386
|
m_star
|
|
176
387
|
Parametric price-cost margin.
|
|
@@ -182,7 +393,7 @@ def guppi_from_delta(
|
|
|
182
393
|
GUPPI bound corresponding to ∆HHI bound, at given margin and recapture rate.
|
|
183
394
|
|
|
184
395
|
"""
|
|
185
|
-
return round_cust(
|
|
396
|
+
return gbfn.round_cust(
|
|
186
397
|
m_star * r_bar * (_s_m := np.sqrt(_delta_bound / 2)) / (1 - _s_m),
|
|
187
398
|
frac=0.005,
|
|
188
399
|
rounding_mode="ROUND_HALF_DOWN",
|
|
@@ -215,13 +426,13 @@ def critical_share_ratio(
|
|
|
215
426
|
for given margin and recapture rate.
|
|
216
427
|
|
|
217
428
|
"""
|
|
218
|
-
return round_cust(
|
|
429
|
+
return gbfn.round_cust(
|
|
219
430
|
mpf(f"{_guppi_bound}") / mp.fmul(f"{m_star}", f"{r_bar}"), frac=frac
|
|
220
431
|
)
|
|
221
432
|
|
|
222
433
|
|
|
223
434
|
def share_from_guppi(
|
|
224
|
-
_guppi_bound: float = 0.065, /, *, m_star: float = 1.00, r_bar: float = 0.
|
|
435
|
+
_guppi_bound: float = 0.065, /, *, m_star: float = 1.00, r_bar: float = 0.8
|
|
225
436
|
) -> float:
|
|
226
437
|
"""
|
|
227
438
|
Symmetric-firm share for given GUPPI, margin, and recapture rate.
|
|
@@ -243,197 +454,13 @@ def share_from_guppi(
|
|
|
243
454
|
|
|
244
455
|
"""
|
|
245
456
|
|
|
246
|
-
return round_cust(
|
|
457
|
+
return gbfn.round_cust(
|
|
247
458
|
(_d0 := critical_share_ratio(_guppi_bound, m_star=m_star, r_bar=r_bar))
|
|
248
459
|
/ (1 + _d0)
|
|
249
460
|
)
|
|
250
461
|
|
|
251
462
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
"""
|
|
256
|
-
Generate the list of share combination on the ΔHHI boundary.
|
|
257
|
-
|
|
258
|
-
Parameters
|
|
259
|
-
----------
|
|
260
|
-
_dh_val:
|
|
261
|
-
Merging-firms' ΔHHI bound.
|
|
262
|
-
prec
|
|
263
|
-
Number of decimal places for rounding reported shares.
|
|
264
|
-
|
|
265
|
-
Returns
|
|
266
|
-
-------
|
|
267
|
-
Array of share-pairs, area under boundary.
|
|
268
|
-
|
|
269
|
-
"""
|
|
270
|
-
|
|
271
|
-
_dh_val = mpf(f"{_dh_val}")
|
|
272
|
-
_s_naught = 1 / 2 * (1 - mp.sqrt(1 - 2 * _dh_val))
|
|
273
|
-
_s_mid = mp.sqrt(_dh_val / 2)
|
|
274
|
-
|
|
275
|
-
_dh_step_sz = mp.power(10, -6)
|
|
276
|
-
_s_1 = np.array(mp.arange(_s_mid, _s_naught - mp.eps, -_dh_step_sz))
|
|
277
|
-
_s_2 = _dh_val / (2 * _s_1)
|
|
278
|
-
|
|
279
|
-
# Boundary points
|
|
280
|
-
_dh_half = np.row_stack((
|
|
281
|
-
np.column_stack((_s_1, _s_2)),
|
|
282
|
-
np.array([(mpf("0.0"), mpf("1.0"))]),
|
|
283
|
-
))
|
|
284
|
-
_dh_bdry_pts = np.row_stack((np.flip(_dh_half, 0), np.flip(_dh_half[1:], 1)))
|
|
285
|
-
|
|
286
|
-
_s_1_pts, _s_2_pts = np.split(_dh_bdry_pts, 2, axis=1)
|
|
287
|
-
return GuidelinesBoundary(
|
|
288
|
-
np.column_stack((
|
|
289
|
-
np.array(_s_1_pts, np.float64),
|
|
290
|
-
np.array(_s_2_pts, np.float64),
|
|
291
|
-
)),
|
|
292
|
-
dh_area(_dh_val, prec=prec),
|
|
293
|
-
)
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
def combined_share_boundary(
|
|
297
|
-
_s_intcpt: float = 0.0625, /, *, bdry_dps: int = 10
|
|
298
|
-
) -> GuidelinesBoundary:
|
|
299
|
-
"""
|
|
300
|
-
Share combinations on the merging-firms' combined share boundary.
|
|
301
|
-
|
|
302
|
-
Assumes symmetric merging-firm margins. The combined-share is
|
|
303
|
-
congruent to the post-merger HHI contribution boundary, as the
|
|
304
|
-
post-merger HHI bound is the square of the combined-share bound.
|
|
305
|
-
|
|
306
|
-
Parameters
|
|
307
|
-
----------
|
|
308
|
-
_s_intcpt:
|
|
309
|
-
Merging-firms' combined share.
|
|
310
|
-
bdry_dps
|
|
311
|
-
Number of decimal places for rounding reported shares.
|
|
312
|
-
|
|
313
|
-
Returns
|
|
314
|
-
-------
|
|
315
|
-
Array of share-pairs, area under boundary.
|
|
316
|
-
|
|
317
|
-
"""
|
|
318
|
-
_s_intcpt = mpf(f"{_s_intcpt}")
|
|
319
|
-
_s_mid = _s_intcpt / 2
|
|
320
|
-
|
|
321
|
-
_s1_pts = (0, _s_mid, _s_intcpt)
|
|
322
|
-
return GuidelinesBoundary(
|
|
323
|
-
np.column_stack((
|
|
324
|
-
np.array(_s1_pts, np.float64),
|
|
325
|
-
np.array(_s1_pts[::-1], np.float64),
|
|
326
|
-
)),
|
|
327
|
-
round(float(_s_intcpt * _s_mid), bdry_dps),
|
|
328
|
-
)
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
def hhi_pre_contrib_boundary(
|
|
332
|
-
_hhi_contrib: float = 0.03125, /, *, bdry_dps: int = 5
|
|
333
|
-
) -> GuidelinesBoundary:
|
|
334
|
-
"""
|
|
335
|
-
Share combinations on the premerger HHI contribution boundary.
|
|
336
|
-
|
|
337
|
-
Parameters
|
|
338
|
-
----------
|
|
339
|
-
_hhi_contrib:
|
|
340
|
-
Merging-firms' pre-merger HHI contribution bound.
|
|
341
|
-
bdry_dps
|
|
342
|
-
Number of decimal places for rounding reported shares.
|
|
343
|
-
|
|
344
|
-
Returns
|
|
345
|
-
-------
|
|
346
|
-
Array of share-pairs, area under boundary.
|
|
347
|
-
|
|
348
|
-
"""
|
|
349
|
-
_hhi_contrib = mpf(f"{_hhi_contrib}")
|
|
350
|
-
_s_mid = mp.sqrt(_hhi_contrib / 2)
|
|
351
|
-
|
|
352
|
-
_bdry_step_sz = mp.power(10, -bdry_dps)
|
|
353
|
-
# Range-limit is 0 less a step, which is -1 * step-size
|
|
354
|
-
_s_1 = np.array(mp.arange(_s_mid, -_bdry_step_sz, -_bdry_step_sz), np.float64)
|
|
355
|
-
_s_2 = np.sqrt(_hhi_contrib - _s_1**2).astype(np.float64)
|
|
356
|
-
_bdry_pts_mid = np.column_stack((_s_1, _s_2))
|
|
357
|
-
return GuidelinesBoundary(
|
|
358
|
-
np.row_stack((np.flip(_bdry_pts_mid, 0), np.flip(_bdry_pts_mid[1:], 1))),
|
|
359
|
-
round(float(mp.pi * _hhi_contrib / 4), bdry_dps),
|
|
360
|
-
)
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
def hhi_post_contrib_boundary(
|
|
364
|
-
_hhi_contrib: float = 0.800, /, *, bdry_dps: int = 10
|
|
365
|
-
) -> GuidelinesBoundary:
|
|
366
|
-
"""
|
|
367
|
-
Share combinations on the postmerger HHI contribution boundary.
|
|
368
|
-
|
|
369
|
-
The post-merger HHI contribution boundary is identical to the
|
|
370
|
-
combined-share boundary.
|
|
371
|
-
|
|
372
|
-
Parameters
|
|
373
|
-
----------
|
|
374
|
-
_hhi_contrib:
|
|
375
|
-
Merging-firms' pre-merger HHI contribution bound.
|
|
376
|
-
bdry_dps
|
|
377
|
-
Number of decimal places for rounding reported shares.
|
|
378
|
-
|
|
379
|
-
Returns
|
|
380
|
-
-------
|
|
381
|
-
Array of share-pairs, area under boundary.
|
|
382
|
-
|
|
383
|
-
"""
|
|
384
|
-
return combined_share_boundary(np.sqrt(_hhi_contrib), bdry_dps=bdry_dps)
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
def diversion_ratio_boundary(_bdry_spec: UPPBoundarySpec) -> GuidelinesBoundary:
|
|
388
|
-
_share_ratio = critical_share_ratio(
|
|
389
|
-
_bdry_spec.diversion_ratio, r_bar=_bdry_spec.rec
|
|
463
|
+
if __name__ == "__main__":
|
|
464
|
+
print(
|
|
465
|
+
"This module defines classes with methods for generating boundaries for concentration and diversion-ratio screens."
|
|
390
466
|
)
|
|
391
|
-
match _bdry_spec.agg_method:
|
|
392
|
-
case UPPAggrSelector.AVG:
|
|
393
|
-
return shrratio_boundary_xact_avg(
|
|
394
|
-
_share_ratio,
|
|
395
|
-
_bdry_spec.rec,
|
|
396
|
-
recapture_form=_bdry_spec.recapture_form.value, # type: ignore
|
|
397
|
-
prec=_bdry_spec.precision,
|
|
398
|
-
)
|
|
399
|
-
case UPPAggrSelector.MAX:
|
|
400
|
-
return shrratio_boundary_max(
|
|
401
|
-
_share_ratio, _bdry_spec.rec, prec=_bdry_spec.precision
|
|
402
|
-
)
|
|
403
|
-
case UPPAggrSelector.MIN:
|
|
404
|
-
return shrratio_boundary_min(
|
|
405
|
-
_share_ratio,
|
|
406
|
-
_bdry_spec.rec,
|
|
407
|
-
recapture_form=_bdry_spec.recapture_form.value, # type: ignore
|
|
408
|
-
prec=_bdry_spec.precision,
|
|
409
|
-
)
|
|
410
|
-
case UPPAggrSelector.DIS:
|
|
411
|
-
return shrratio_boundary_wtd_avg(
|
|
412
|
-
_share_ratio,
|
|
413
|
-
_bdry_spec.rec,
|
|
414
|
-
agg_method="distance",
|
|
415
|
-
weighting=None,
|
|
416
|
-
recapture_form=_bdry_spec.recapture_form.value, # type: ignore
|
|
417
|
-
prec=_bdry_spec.precision,
|
|
418
|
-
)
|
|
419
|
-
case _:
|
|
420
|
-
_weighting = (
|
|
421
|
-
"cross-product-share"
|
|
422
|
-
if _bdry_spec.agg_method.value.startswith("cross-product-share")
|
|
423
|
-
else "own-share"
|
|
424
|
-
)
|
|
425
|
-
|
|
426
|
-
_agg_method = (
|
|
427
|
-
"arithmetic"
|
|
428
|
-
if _bdry_spec.agg_method.value.endswith("average")
|
|
429
|
-
else "distance"
|
|
430
|
-
)
|
|
431
|
-
|
|
432
|
-
return shrratio_boundary_wtd_avg(
|
|
433
|
-
_share_ratio,
|
|
434
|
-
_bdry_spec.rec,
|
|
435
|
-
agg_method=_agg_method, # type: ignore
|
|
436
|
-
weighting=_weighting, # type: ignore
|
|
437
|
-
recapture_form=_bdry_spec.recapture_form.value, # type: ignore
|
|
438
|
-
prec=_bdry_spec.precision,
|
|
439
|
-
)
|