mergeron 2024.738963.0__tar.gz → 2024.738973.0__tar.gz

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.

Files changed (32) hide show
  1. {mergeron-2024.738963.0 → mergeron-2024.738973.0}/PKG-INFO +1 -1
  2. {mergeron-2024.738963.0 → mergeron-2024.738973.0}/pyproject.toml +1 -1
  3. {mergeron-2024.738963.0 → mergeron-2024.738973.0}/src/mergeron/core/__init__.py +18 -8
  4. mergeron-2024.738973.0/src/mergeron/core/guidelines_boundaries.py +439 -0
  5. mergeron-2024.738963.0/src/mergeron/core/guidelines_boundaries.py → mergeron-2024.738973.0/src/mergeron/core/guidelines_boundary_functions.py +592 -1015
  6. mergeron-2024.738963.0/src/mergeron/core/guidelines_boundaries_specialized_functions.py → mergeron-2024.738973.0/src/mergeron/core/guidelines_boundary_functions_extra.py +48 -9
  7. {mergeron-2024.738963.0 → mergeron-2024.738973.0}/src/mergeron/gen/__init__.py +20 -37
  8. mergeron-2024.738963.0/src/mergeron/gen/_data_generation_functions_nonpublic.py → mergeron-2024.738973.0/src/mergeron/gen/_data_generation_functions.py +77 -19
  9. {mergeron-2024.738963.0 → mergeron-2024.738973.0}/src/mergeron/gen/data_generation.py +17 -14
  10. mergeron-2024.738973.0/src/mergeron/gen/market_sample.py +79 -0
  11. {mergeron-2024.738963.0 → mergeron-2024.738973.0}/src/mergeron/gen/upp_tests.py +99 -66
  12. {mergeron-2024.738963.0 → mergeron-2024.738973.0}/README.rst +0 -0
  13. {mergeron-2024.738963.0 → mergeron-2024.738973.0}/src/mergeron/License.txt +0 -0
  14. {mergeron-2024.738963.0 → mergeron-2024.738973.0}/src/mergeron/__init__.py +0 -0
  15. {mergeron-2024.738963.0 → mergeron-2024.738973.0}/src/mergeron/core/InCommon RSA Server CA cert chain.pem +0 -0
  16. {mergeron-2024.738963.0 → mergeron-2024.738973.0}/src/mergeron/core/damodaran_margin_data.py +0 -0
  17. {mergeron-2024.738963.0 → mergeron-2024.738973.0}/src/mergeron/core/excel_helper.py +0 -0
  18. {mergeron-2024.738963.0 → mergeron-2024.738973.0}/src/mergeron/core/ftc_invdata.msgpack +0 -0
  19. {mergeron-2024.738963.0 → mergeron-2024.738973.0}/src/mergeron/core/ftc_merger_investigations_data.py +0 -0
  20. {mergeron-2024.738963.0 → mergeron-2024.738973.0}/src/mergeron/core/proportions_tests.py +0 -0
  21. {mergeron-2024.738963.0 → mergeron-2024.738973.0}/src/mergeron/core/pseudorandom_numbers.py +0 -0
  22. {mergeron-2024.738963.0 → mergeron-2024.738973.0}/src/mergeron/ext/__init__.py +0 -0
  23. {mergeron-2024.738963.0 → mergeron-2024.738973.0}/src/mergeron/ext/tol_colors.py +0 -0
  24. {mergeron-2024.738963.0 → mergeron-2024.738973.0}/src/mergeron/gen/investigations_stats.py +0 -0
  25. {mergeron-2024.738963.0 → mergeron-2024.738973.0}/src/mergeron/jinja_LaTex_templates/clrrate_cis_summary_table_template.tex.jinja2 +0 -0
  26. {mergeron-2024.738963.0 → mergeron-2024.738973.0}/src/mergeron/jinja_LaTex_templates/ftcinvdata_byhhianddelta_table_template.tex.jinja2 +0 -0
  27. {mergeron-2024.738963.0 → mergeron-2024.738973.0}/src/mergeron/jinja_LaTex_templates/ftcinvdata_summary_table_template.tex.jinja2 +0 -0
  28. {mergeron-2024.738963.0 → mergeron-2024.738973.0}/src/mergeron/jinja_LaTex_templates/ftcinvdata_summarypaired_table_template.tex.jinja2 +0 -0
  29. {mergeron-2024.738963.0 → mergeron-2024.738973.0}/src/mergeron/jinja_LaTex_templates/mergeron.cls +0 -0
  30. {mergeron-2024.738963.0 → mergeron-2024.738973.0}/src/mergeron/jinja_LaTex_templates/mergeron_table_collection_template.tex.jinja2 +0 -0
  31. {mergeron-2024.738963.0 → mergeron-2024.738973.0}/src/mergeron/jinja_LaTex_templates/setup_tikz_tables.tex.jinja2 +0 -0
  32. {mergeron-2024.738963.0 → mergeron-2024.738973.0}/src/mergeron/py.typed +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: mergeron
3
- Version: 2024.738963.0
3
+ Version: 2024.738973.0
4
4
  Summary: Analysis of standards defined in Horizontal Merger Guidelines
5
5
  License: MIT
6
6
  Keywords: merger policy analysis,merger guidelines,merger screening,policy presumptions,concentration standards,upward pricing pressure,GUPPI
@@ -1,7 +1,7 @@
1
1
  [tool.poetry]
2
2
  name = "mergeron"
3
3
  # See ./get_version_str.py
4
- version = "2024.738963.0"
4
+ version = "2024.738973.0"
5
5
  description = "Analysis of standards defined in Horizontal Merger Guidelines"
6
6
  keywords = [
7
7
  "merger policy analysis",
@@ -1,15 +1,24 @@
1
1
  from __future__ import annotations
2
2
 
3
+ from dataclasses import dataclass
3
4
  from importlib.metadata import version
4
5
 
5
- from attrs import Attribute, define, field, validators
6
+ import numpy as np
7
+ from attrs import Attribute, field, frozen, validators
8
+ from numpy.typing import NDArray
6
9
 
7
10
  from .. import _PKG_NAME, RECConstants, UPPAggrSelector # noqa: TID252
8
11
 
9
12
  __version__ = version(_PKG_NAME)
10
13
 
11
14
 
12
- def _delta_value_validator(
15
+ @dataclass(frozen=True)
16
+ class GuidelinesBoundary:
17
+ coordinates: NDArray[np.float64]
18
+ area: float
19
+
20
+
21
+ def _divr_value_validator(
13
22
  _instance: UPPBoundarySpec, _attribute: Attribute[float], _value: float, /
14
23
  ) -> None:
15
24
  if not 0 <= _value <= 1:
@@ -38,15 +47,15 @@ def _rec_spec_validator(
38
47
  )
39
48
 
40
49
 
41
- @define(slots=True, frozen=True)
50
+ @frozen
42
51
  class UPPBoundarySpec:
43
- share_ratio: float = field(
52
+ diversion_ratio: float = field(
44
53
  kw_only=False,
45
- default=0.075,
46
- validator=(validators.instance_of(float), _delta_value_validator),
54
+ default=0.045,
55
+ validator=(validators.instance_of(float), _divr_value_validator),
47
56
  )
48
57
  rec: float = field(
49
- kw_only=False, default=0.80, validator=validators.instance_of(float)
58
+ kw_only=False, default=0.855, validator=validators.instance_of(float)
50
59
  )
51
60
 
52
61
  agg_method: UPPAggrSelector = field(
@@ -54,11 +63,12 @@ class UPPBoundarySpec:
54
63
  default=UPPAggrSelector.MAX,
55
64
  validator=validators.instance_of(UPPAggrSelector),
56
65
  )
66
+
57
67
  recapture_form: RECConstants | None = field(
58
68
  kw_only=True,
59
69
  default=RECConstants.INOUT,
60
70
  validator=(
61
- validators.optional(validators.instance_of(RECConstants)), # type: ignore
71
+ validators.instance_of((type(None), RECConstants)),
62
72
  _rec_spec_validator,
63
73
  ),
64
74
  )
@@ -0,0 +1,439 @@
1
+ """
2
+ Methods for defining and analyzing boundaries for Guidelines standards,
3
+ with a canvas on which to draw boundaries for Guidelines standards.
4
+
5
+ """
6
+
7
+ from dataclasses import dataclass
8
+ from importlib.metadata import version
9
+ from typing import Literal, TypeAlias
10
+
11
+ import numpy as np
12
+ from attrs import field, frozen
13
+ from mpmath import mp, mpf # type: ignore
14
+
15
+ from .. import _PKG_NAME, UPPAggrSelector # noqa: TID252
16
+ from . import GuidelinesBoundary, UPPBoundarySpec
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
+ )
25
+
26
+ __version__ = version(_PKG_NAME)
27
+
28
+
29
+ mp.prec = 80
30
+ mp.trap_complex = True
31
+
32
+ HMGPubYear: TypeAlias = Literal[1992, 2004, 2010, 2023]
33
+
34
+
35
+ @dataclass(frozen=True)
36
+ class HMGThresholds:
37
+ delta: float
38
+ fc: float
39
+ rec: float
40
+ guppi: float
41
+ divr: float
42
+ cmcr: float
43
+ ipr: float
44
+
45
+
46
+ @frozen
47
+ class GuidelinesThresholds:
48
+ """
49
+ Guidelines threholds by Guidelines publication year
50
+
51
+ ΔHHI, Recapture Rate, GUPPI, Diversion ratio, CMCR, and IPR thresholds
52
+ constructed from concentration standards in Guidelines published in
53
+ 1992, 2004, 2010, and 2023.
54
+
55
+ The 2004 Guidelines refernced here are the EU Commission
56
+ guidelines on assessment of horizontal mergers. These
57
+ guidelines also define a presumption for mergers with
58
+ post-merger HHI in [1000, 2000) and ΔHHI >= 250 points,
59
+ whi is not modeled here.
60
+
61
+ All other Guidelines modeled here are U.S. merger guidelines.
62
+
63
+ """
64
+
65
+ pub_year: HMGPubYear
66
+ """
67
+ Year of publication of the Guidelines
68
+ """
69
+
70
+ safeharbor: HMGThresholds = field(kw_only=True, default=None)
71
+ """
72
+ Negative presumption quantified on various measures
73
+
74
+ ΔHHI safeharbor bound, default recapture rate, GUPPI bound,
75
+ diversion ratio limit, CMCR, and IPR
76
+ """
77
+
78
+ imputed_presumption: HMGThresholds = field(kw_only=True, default=None)
79
+ """
80
+ Presumption of harm imputed from guidelines
81
+
82
+ ΔHHI bound inferred from strict numbers-equivalent
83
+ of (post-merger) HHI presumption, and corresponding default recapture rate,
84
+ GUPPI bound, diversion ratio limit, CMCR, and IPR
85
+ """
86
+
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
+ def __attrs_post_init__(self, /) -> None:
96
+ # In the 2023 Guidlines, the agencies do not define a
97
+ # negative presumption, or safeharbor. Practically speaking,
98
+ # given resource constraints and loss aversion, it is likely
99
+ # that staff only investigates mergers that meet the presumption;
100
+ # thus, here, the tentative delta safeharbor under
101
+ # the 2023 Guidelines is 100 points
102
+ _hhi_p, _dh_s, _dh_p = {
103
+ 1992: (0.18, 0.005, 0.01),
104
+ 2010: (0.25, 0.01, 0.02),
105
+ 2004: (0.20, 0.015, 0.015),
106
+ 2023: (0.18, 0.01, 0.01),
107
+ }[self.pub_year]
108
+
109
+ object.__setattr__(
110
+ self,
111
+ "safeharbor",
112
+ HMGThresholds(
113
+ _dh_s,
114
+ _fc := int(np.ceil(1 / _hhi_p)),
115
+ _r := round_cust(_fc / (_fc + 1)),
116
+ _g_s := guppi_from_delta(_dh_s, m_star=1.0, r_bar=_r),
117
+ _dr := round_cust(1 / (_fc + 1)),
118
+ _cmcr := 0.03, # Not strictly a Guidelines standard
119
+ _ipr := _g_s, # Not strictly a Guidelines standard
120
+ ),
121
+ )
122
+
123
+ # imputed_presumption is relevant for 2010 Guidelines
124
+ object.__setattr__(
125
+ self,
126
+ "imputed_presumption",
127
+ (
128
+ HMGThresholds(
129
+ _dh_i := 2 * (0.5 / _fc) ** 2,
130
+ _fc,
131
+ _r_i := round_cust((_fc - 1 / 2) / (_fc + 1 / 2)),
132
+ _g_i := guppi_from_delta(_dh_i, m_star=1.0, r_bar=_r_i),
133
+ round_cust((1 / 2) / (_fc + 1 / 2)),
134
+ _cmcr,
135
+ _g_i,
136
+ )
137
+ if self.pub_year == 2010
138
+ else HMGThresholds(
139
+ _dh_i := 2 * (1 / (_fc + 1)) ** 2,
140
+ _fc,
141
+ _r,
142
+ _g_i := guppi_from_delta(_dh_i, m_star=1.0, r_bar=_r),
143
+ _dr,
144
+ _cmcr,
145
+ _g_i,
146
+ )
147
+ ),
148
+ )
149
+
150
+ object.__setattr__(
151
+ self,
152
+ "presumption",
153
+ HMGThresholds(
154
+ _dh_p,
155
+ _fc,
156
+ _r,
157
+ _g_p := guppi_from_delta(_dh_p, m_star=1.0, r_bar=_r),
158
+ _dr,
159
+ _cmcr,
160
+ _ipr := _g_p,
161
+ ),
162
+ )
163
+
164
+
165
+ def guppi_from_delta(
166
+ _delta_bound: float = 0.01, /, *, m_star: float = 1.00, r_bar: float = 0.855
167
+ ) -> float:
168
+ """
169
+ Translate ∆HHI bound to GUPPI bound.
170
+
171
+ Parameters
172
+ ----------
173
+ _deltasf
174
+ Specified ∆HHI bound.
175
+ m_star
176
+ Parametric price-cost margin.
177
+ r_bar
178
+ Default recapture rate.
179
+
180
+ Returns
181
+ -------
182
+ GUPPI bound corresponding to ∆HHI bound, at given margin and recapture rate.
183
+
184
+ """
185
+ return round_cust(
186
+ m_star * r_bar * (_s_m := np.sqrt(_delta_bound / 2)) / (1 - _s_m),
187
+ frac=0.005,
188
+ rounding_mode="ROUND_HALF_DOWN",
189
+ )
190
+
191
+
192
+ def critical_share_ratio(
193
+ _guppi_bound: float = 0.075,
194
+ /,
195
+ *,
196
+ m_star: float = 1.00,
197
+ r_bar: float = 1.00,
198
+ frac: float = 1e-16,
199
+ ) -> mpf:
200
+ """
201
+ Corollary to GUPPI bound.
202
+
203
+ Parameters
204
+ ----------
205
+ _guppi_bound
206
+ Specified GUPPI bound.
207
+ m_star
208
+ Parametric price-cost margin.
209
+ r_bar
210
+ Default recapture rate.
211
+
212
+ Returns
213
+ -------
214
+ Critical share ratio (share ratio bound) corresponding to the GUPPI bound
215
+ for given margin and recapture rate.
216
+
217
+ """
218
+ return round_cust(
219
+ mpf(f"{_guppi_bound}") / mp.fmul(f"{m_star}", f"{r_bar}"), frac=frac
220
+ )
221
+
222
+
223
+ def share_from_guppi(
224
+ _guppi_bound: float = 0.065, /, *, m_star: float = 1.00, r_bar: float = 0.855
225
+ ) -> float:
226
+ """
227
+ Symmetric-firm share for given GUPPI, margin, and recapture rate.
228
+
229
+ Parameters
230
+ ----------
231
+ _guppi_bound
232
+ GUPPI bound.
233
+ m_star
234
+ Parametric price-cost margin.
235
+ r_bar
236
+ Default recapture rate.
237
+
238
+ Returns
239
+ -------
240
+ float
241
+ Symmetric firm market share on GUPPI boundary, for given margin and
242
+ recapture rate.
243
+
244
+ """
245
+
246
+ return round_cust(
247
+ (_d0 := critical_share_ratio(_guppi_bound, m_star=m_star, r_bar=r_bar))
248
+ / (1 + _d0)
249
+ )
250
+
251
+
252
+ def hhi_delta_boundary(
253
+ _dh_val: float = 0.01, /, *, prec: int = 5
254
+ ) -> GuidelinesBoundary:
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
390
+ )
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
+ )