mergeron 2025.739319.3__py3-none-any.whl → 2025.739341.8__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 +21 -23
- mergeron/core/__init__.py +21 -5
- mergeron/core/empirical_margin_distribution.py +213 -158
- mergeron/core/ftc_merger_investigations_data.py +31 -35
- mergeron/core/guidelines_boundaries.py +27 -20
- mergeron/core/guidelines_boundary_functions.py +22 -32
- mergeron/core/guidelines_boundary_functions_extra.py +15 -30
- mergeron/core/pseudorandom_numbers.py +21 -18
- mergeron/data/__init__.py +13 -11
- mergeron/data/damodaran_margin_data_serialized.zip +0 -0
- mergeron/gen/__init__.py +32 -41
- mergeron/gen/data_generation.py +19 -23
- mergeron/gen/data_generation_functions.py +27 -38
- mergeron/gen/enforcement_stats.py +144 -23
- mergeron/gen/upp_tests.py +4 -9
- mergeron-2025.739341.8.dist-info/METADATA +94 -0
- mergeron-2025.739341.8.dist-info/RECORD +20 -0
- {mergeron-2025.739319.3.dist-info → mergeron-2025.739341.8.dist-info}/WHEEL +1 -1
- mergeron/data/damodaran_margin_data.xls +0 -0
- mergeron/demo/__init__.py +0 -3
- mergeron/demo/visualize_empirical_margin_distribution.py +0 -94
- mergeron-2025.739319.3.dist-info/METADATA +0 -174
- mergeron-2025.739319.3.dist-info/RECORD +0 -22
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
"""
|
|
2
|
-
Methods to parse FTC Merger Investigations Data, downloading source documents
|
|
3
|
-
as necessary
|
|
2
|
+
Methods to parse FTC Merger Investigations Data, downloading source documents as needed.
|
|
4
3
|
|
|
5
|
-
|
|
4
|
+
Notes
|
|
6
5
|
-----
|
|
7
6
|
Reported row and column totals from source data are not stored.
|
|
8
7
|
|
|
@@ -19,7 +18,6 @@ from types import MappingProxyType
|
|
|
19
18
|
from typing import Any
|
|
20
19
|
from zipfile import ZIP_DEFLATED, ZipFile
|
|
21
20
|
|
|
22
|
-
import msgpack_numpy as m # type: ignore
|
|
23
21
|
import numpy as np
|
|
24
22
|
import urllib3
|
|
25
23
|
from bs4 import BeautifulSoup
|
|
@@ -38,8 +36,6 @@ from . import (
|
|
|
38
36
|
|
|
39
37
|
__version__ = VERSION
|
|
40
38
|
|
|
41
|
-
m.patch()
|
|
42
|
-
|
|
43
39
|
WORK_DIR = globals().get("WORK_DIR", PKG_WORK_DIR)
|
|
44
40
|
"""Redefined, in case the user defines WORK_DIR betweeen module imports."""
|
|
45
41
|
|
|
@@ -49,7 +45,7 @@ if not FID_WORK_DIR.is_dir():
|
|
|
49
45
|
|
|
50
46
|
INVDATA_ARCHIVE_PATH = WORK_DIR / mdat.FTC_MERGER_INVESTIGATIONS_DATA.name
|
|
51
47
|
if not INVDATA_ARCHIVE_PATH.is_file():
|
|
52
|
-
shutil.copy2(mdat.FTC_MERGER_INVESTIGATIONS_DATA, INVDATA_ARCHIVE_PATH)
|
|
48
|
+
shutil.copy2(mdat.FTC_MERGER_INVESTIGATIONS_DATA, INVDATA_ARCHIVE_PATH) # type: ignore
|
|
53
49
|
|
|
54
50
|
TABLE_NO_RE = re.compile(r"Table \d+\.\d+")
|
|
55
51
|
TABLE_TYPES = ("ByHHIandDelta", "ByFirmCount")
|
|
@@ -95,6 +91,7 @@ CNT_FCOUNT_DICT = {
|
|
|
95
91
|
|
|
96
92
|
|
|
97
93
|
def reverse_map(_dict: Mapping[Any, Any]) -> Mapping[Any, Any]:
|
|
94
|
+
"""Reverse a mapping."""
|
|
98
95
|
return {_v: _k for _k, _v in _dict.items()}
|
|
99
96
|
|
|
100
97
|
|
|
@@ -105,8 +102,7 @@ def construct_data(
|
|
|
105
102
|
flag_pharma_for_exclusion: bool = True,
|
|
106
103
|
rebuild_data: bool = False,
|
|
107
104
|
) -> INVData:
|
|
108
|
-
"""Construct FTC merger investigations data for non-overlapping periods
|
|
109
|
-
from reported data on cumulative periods.
|
|
105
|
+
"""Construct FTC merger investigations data for added non-overlapping periods.
|
|
110
106
|
|
|
111
107
|
FTC merger investigations data are reported in cumulative periods,
|
|
112
108
|
e.g., 1996-2003 and 1996-2011, but the analyst may want data reported in
|
|
@@ -114,10 +110,7 @@ def construct_data(
|
|
|
114
110
|
reported merger investigations data, the above example is the only instance
|
|
115
111
|
in which the 1996-2003 data can be subtracted from the cumulative data to
|
|
116
112
|
extract merger investigations data for the later period.
|
|
117
|
-
See also, Kwoka
|
|
118
|
-
|
|
119
|
-
.. [1] Kwoka, J., Greenfield, D., & Gu, C. (2015). Mergers, merger control,
|
|
120
|
-
and remedies: A retrospective analysis of U.S. policy. MIT Press.
|
|
113
|
+
See also, Kwoka, Sec. 2.3.3. [#]_
|
|
121
114
|
|
|
122
115
|
Parameters
|
|
123
116
|
----------
|
|
@@ -133,8 +126,13 @@ def construct_data(
|
|
|
133
126
|
-------
|
|
134
127
|
A dictionary of merger investigations data keyed to reporting periods
|
|
135
128
|
|
|
136
|
-
|
|
129
|
+
References
|
|
130
|
+
----------
|
|
137
131
|
|
|
132
|
+
.. [#] Kwoka, J., Greenfield, D., & Gu, C. (2015). Mergers, merger control,
|
|
133
|
+
and remedies: A retrospective analysis of U.S. policy. MIT Press.
|
|
134
|
+
|
|
135
|
+
"""
|
|
138
136
|
if _archive_path.is_file() and not rebuild_data:
|
|
139
137
|
with (
|
|
140
138
|
ZipFile(_archive_path, "r") as _yzh,
|
|
@@ -256,7 +254,7 @@ def _construct_no_evidence_data(_invdata: INVData_in, _data_period: str, /) -> N
|
|
|
256
254
|
|
|
257
255
|
|
|
258
256
|
def _construct_new_period_data(
|
|
259
|
-
_invdata:
|
|
257
|
+
_invdata: INVData_in,
|
|
260
258
|
_data_period: str,
|
|
261
259
|
/,
|
|
262
260
|
*,
|
|
@@ -396,6 +394,7 @@ def _construct_new_period_data(
|
|
|
396
394
|
def invdata_build_aggregate_table(
|
|
397
395
|
_data_typesub: dict[str, INVTableData], _aggr_table_list: Sequence[str]
|
|
398
396
|
) -> INVTableData:
|
|
397
|
+
"""Aggregate selected FTC merger investigations data tables within a given time period."""
|
|
399
398
|
hdr_table_no = _aggr_table_list[0]
|
|
400
399
|
|
|
401
400
|
return INVTableData(
|
|
@@ -424,16 +423,16 @@ def _parse_invdata() -> INVData:
|
|
|
424
423
|
by range of HHI and ∆HHI.
|
|
425
424
|
|
|
426
425
|
"""
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
import pymupdf # type: ignore
|
|
426
|
+
raise ValueError(
|
|
427
|
+
"This function is defined here as documentation.\n"
|
|
428
|
+
"NOTE: License for `pymupdf`, upon which this function depends,"
|
|
429
|
+
" may be incompatible with the MIT license,"
|
|
430
|
+
" under which this pacakge is distributed."
|
|
431
|
+
" Making this fumction operable requires the user to modify"
|
|
432
|
+
" the source code as well as to install an additional package"
|
|
433
|
+
" not distributed with this package or identified as a requirement."
|
|
434
|
+
)
|
|
435
|
+
import pymupdf # type: ignore
|
|
437
436
|
|
|
438
437
|
invdata_docnames = _download_invdata(FID_WORK_DIR)
|
|
439
438
|
|
|
@@ -591,7 +590,7 @@ def _parse_table_blocks(
|
|
|
591
590
|
)
|
|
592
591
|
|
|
593
592
|
table_array = process_table_func(_table_blocks)
|
|
594
|
-
if not isinstance(table_array, np.ndarray) or table_array.dtype !=
|
|
593
|
+
if not isinstance(table_array, np.ndarray) or table_array.dtype != int:
|
|
595
594
|
print(table_num)
|
|
596
595
|
print(_table_blocks)
|
|
597
596
|
raise ValueError
|
|
@@ -612,7 +611,7 @@ def _process_table_blks_conc_type(
|
|
|
612
611
|
conc_row_pat = re.compile(r"((?:0|\d,\d{3}) (?:- \d+,\d{3}|\+)|TOTAL)")
|
|
613
612
|
|
|
614
613
|
col_titles_array = tuple(CONC_DELTA_DICT.values())
|
|
615
|
-
col_totals: ArrayBIGINT = np.zeros(len(col_titles_array),
|
|
614
|
+
col_totals: ArrayBIGINT = np.zeros(len(col_titles_array), int)
|
|
616
615
|
invdata_array: ArrayBIGINT = np.array(None)
|
|
617
616
|
|
|
618
617
|
for tbl_blk in _table_blocks:
|
|
@@ -620,7 +619,7 @@ def _process_table_blks_conc_type(
|
|
|
620
619
|
row_list: list[str] = _blk_str.strip().split("\n")
|
|
621
620
|
row_title: str = row_list.pop(0)
|
|
622
621
|
row_key: int = CONC_HHI_DICT[row_title]
|
|
623
|
-
row_total = np.array(row_list.pop().replace(",", "").split("/"),
|
|
622
|
+
row_total = np.array(row_list.pop().replace(",", "").split("/"), int)
|
|
624
623
|
row_array_list: list[list[int]] = []
|
|
625
624
|
while row_list:
|
|
626
625
|
enfd_val, clsd_val = row_list.pop(0).split("/")
|
|
@@ -633,7 +632,7 @@ def _process_table_blks_conc_type(
|
|
|
633
632
|
int(enfd_val) + int(clsd_val),
|
|
634
633
|
]
|
|
635
634
|
]
|
|
636
|
-
row_array = np.array(row_array_list,
|
|
635
|
+
row_array = np.array(row_array_list, int)
|
|
637
636
|
# Check row totals
|
|
638
637
|
assert_array_equal(row_total, np.einsum("ij->j", row_array[:, 2:4]))
|
|
639
638
|
|
|
@@ -669,14 +668,12 @@ def _process_table_blks_cnt_type(
|
|
|
669
668
|
cnt_row_pat = re.compile(r"(\d+ (?:to \d+|\+)|TOTAL)")
|
|
670
669
|
|
|
671
670
|
invdata_array: ArrayBIGINT = np.array(None)
|
|
672
|
-
col_totals: ArrayBIGINT = np.zeros(3,
|
|
671
|
+
col_totals: ArrayBIGINT = np.zeros(3, int) # "enforced", "closed", "total"
|
|
673
672
|
|
|
674
673
|
for _tbl_blk in _table_blocks:
|
|
675
674
|
if cnt_row_pat.match(_blk_str := _tbl_blk[-3]):
|
|
676
675
|
row_list_s = _blk_str.strip().replace(",", "").split("\n")
|
|
677
|
-
row_list = np.array(
|
|
678
|
-
[CNT_FCOUNT_DICT[row_list_s[0]], *row_list_s[1:]], np.uint64
|
|
679
|
-
)
|
|
676
|
+
row_list = np.array([CNT_FCOUNT_DICT[row_list_s[0]], *row_list_s[1:]], int)
|
|
680
677
|
del row_list_s
|
|
681
678
|
if row_list[3] != row_list[1] + row_list[2]:
|
|
682
679
|
raise ValueError(
|
|
@@ -694,8 +691,7 @@ def _process_table_blks_cnt_type(
|
|
|
694
691
|
continue
|
|
695
692
|
|
|
696
693
|
if not np.array_equal(
|
|
697
|
-
np.array(list(col_totals[1:]), np.
|
|
698
|
-
np.einsum("ij->j", invdata_array[:, 1:]),
|
|
694
|
+
np.array(list(col_totals[1:]), int), np.einsum("ij->j", invdata_array[:, 1:])
|
|
699
695
|
):
|
|
700
696
|
raise ValueError("Column totals don't compute.")
|
|
701
697
|
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
"""
|
|
2
|
-
Methods for defining and analyzing boundaries for Guidelines standards
|
|
3
|
-
|
|
2
|
+
Methods for defining and analyzing boundaries for Guidelines standards.
|
|
3
|
+
|
|
4
|
+
Includes function to create a canvas on which to draw boundaries for
|
|
5
|
+
Guidelines standards.
|
|
4
6
|
|
|
5
7
|
"""
|
|
6
8
|
|
|
@@ -34,6 +36,8 @@ mp.trap_complex = True
|
|
|
34
36
|
|
|
35
37
|
@frozen
|
|
36
38
|
class HMGThresholds:
|
|
39
|
+
"""Thresholds for Guidelines standards."""
|
|
40
|
+
|
|
37
41
|
delta: float
|
|
38
42
|
fc: float
|
|
39
43
|
rec: float
|
|
@@ -47,53 +51,55 @@ class HMGThresholds:
|
|
|
47
51
|
@frozen
|
|
48
52
|
class GuidelinesThresholds:
|
|
49
53
|
"""
|
|
50
|
-
Guidelines
|
|
54
|
+
Guidelines thresholds by Guidelines publication year.
|
|
51
55
|
|
|
52
56
|
ΔHHI, Recapture Ratio, GUPPI, Diversion ratio, CMCR, and IPR thresholds
|
|
53
57
|
constructed from concentration standards in Guidelines published in
|
|
54
58
|
1992, 2010, and 2023.
|
|
55
|
-
|
|
56
59
|
"""
|
|
57
60
|
|
|
58
61
|
pub_year: HMGPubYear = field(
|
|
59
62
|
kw_only=False, default=2023, validator=validators.in_([1992, 2010, 2023])
|
|
60
63
|
)
|
|
61
64
|
"""
|
|
62
|
-
Year of publication of the Guidelines
|
|
65
|
+
Year of publication of the Guidelines.
|
|
63
66
|
"""
|
|
64
|
-
|
|
65
67
|
safeharbor: HMGThresholds = field(kw_only=True, default=None, init=False)
|
|
66
68
|
"""
|
|
67
|
-
Negative presumption quantified on various measures
|
|
69
|
+
Negative presumption quantified on various measures.
|
|
68
70
|
|
|
69
71
|
ΔHHI safeharbor bound, default recapture ratio, GUPPI bound,
|
|
70
|
-
diversion ratio limit, CMCR, and IPR
|
|
72
|
+
diversion ratio limit, CMCR, and IPR.
|
|
71
73
|
"""
|
|
72
74
|
|
|
73
75
|
presumption: HMGThresholds = field(kw_only=True, default=None, init=False)
|
|
74
76
|
"""
|
|
75
|
-
Presumption of harm defined in HMG
|
|
77
|
+
Presumption of harm defined in HMG.
|
|
76
78
|
|
|
77
79
|
ΔHHI bound and corresponding default recapture ratio, GUPPI bound,
|
|
78
|
-
diversion ratio limit, CMCR, and IPR
|
|
80
|
+
diversion ratio limit, CMCR, and IPR.
|
|
79
81
|
"""
|
|
80
82
|
|
|
81
83
|
imputed_presumption: HMGThresholds = field(kw_only=True, default=None, init=False)
|
|
82
84
|
"""
|
|
83
|
-
Presumption of harm imputed from
|
|
85
|
+
Presumption of harm imputed from Guidelines.
|
|
84
86
|
|
|
85
87
|
ΔHHI bound inferred from strict numbers-equivalent
|
|
86
88
|
of (post-merger) HHI presumption, and corresponding default recapture ratio,
|
|
87
|
-
GUPPI bound, diversion ratio limit, CMCR, and IPR
|
|
89
|
+
GUPPI bound, diversion ratio limit, CMCR, and IPR.
|
|
88
90
|
"""
|
|
89
91
|
|
|
90
92
|
def __attrs_post_init__(self, /) -> None:
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
93
|
+
"""
|
|
94
|
+
Initialize Guidelines thresholds, based on Guidelines publication year.
|
|
95
|
+
|
|
96
|
+
In the 2023 Guidelines, 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
|
+
"""
|
|
97
103
|
hhi_p, dh_s, dh_p = {
|
|
98
104
|
1992: (0.18, 0.005, 0.01),
|
|
99
105
|
2010: (0.25, 0.01, 0.02),
|
|
@@ -120,7 +126,7 @@ class GuidelinesThresholds:
|
|
|
120
126
|
|
|
121
127
|
# imputed_presumption is relevant for presumptions implicating
|
|
122
128
|
# mergers *to* symmetry in numbers-equivalent of post-merger HHI
|
|
123
|
-
# as in 2010 U.S.Guidelines
|
|
129
|
+
# as in 2010 U.S.Guidelines.
|
|
124
130
|
object.__setattr__(
|
|
125
131
|
self,
|
|
126
132
|
"imputed_presumption",
|
|
@@ -185,6 +191,7 @@ class ConcentrationBoundary:
|
|
|
185
191
|
"""Market-share pairs as Cartesian coordinates of points on the concentration boundary."""
|
|
186
192
|
|
|
187
193
|
def __attrs_post_init__(self, /) -> None:
|
|
194
|
+
"""Initialize boundary and area based on other attributes."""
|
|
188
195
|
match self.measure_name:
|
|
189
196
|
case "ΔHHI":
|
|
190
197
|
conc_fn = gbfn.hhi_delta_boundary
|
|
@@ -316,6 +323,7 @@ class DiversionRatioBoundary:
|
|
|
316
323
|
"""Market-share pairs as Cartesian coordinates of points on the diversion ratio boundary."""
|
|
317
324
|
|
|
318
325
|
def __attrs_post_init__(self, /) -> None:
|
|
326
|
+
"""Initialize boundary and area based on other attributes."""
|
|
319
327
|
share_ratio = critical_share_ratio(
|
|
320
328
|
self.diversion_ratio, r_bar=self.recapture_ratio
|
|
321
329
|
)
|
|
@@ -448,7 +456,6 @@ def share_from_guppi(
|
|
|
448
456
|
recapture ratio.
|
|
449
457
|
|
|
450
458
|
"""
|
|
451
|
-
|
|
452
459
|
return gbfn.round_cust(
|
|
453
460
|
(_d0 := critical_share_ratio(_guppi_bound, m_star=m_star, r_bar=r_bar))
|
|
454
461
|
/ (1 + _d0)
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
"""Helper functions for defining and analyzing boundaries for Guidelines standards."""
|
|
2
|
+
|
|
1
3
|
import decimal
|
|
2
4
|
from collections.abc import Callable
|
|
3
5
|
from typing import Literal, TypedDict
|
|
@@ -44,7 +46,7 @@ def dh_area(_delta_bound: float | MPFloat = 0.01, /, *, dps: int = 9) -> float:
|
|
|
44
46
|
|
|
45
47
|
.. math::
|
|
46
48
|
|
|
47
|
-
2 s1 s_2 &= ΔHHI\\
|
|
49
|
+
2 s1 s_2 &= ΔHHI \\
|
|
48
50
|
s_1 + s_2 &= 1
|
|
49
51
|
|
|
50
52
|
Parameters
|
|
@@ -59,7 +61,6 @@ def dh_area(_delta_bound: float | MPFloat = 0.01, /, *, dps: int = 9) -> float:
|
|
|
59
61
|
Area under ΔHHI boundary.
|
|
60
62
|
|
|
61
63
|
"""
|
|
62
|
-
|
|
63
64
|
_delta_bound = mpf(f"{_delta_bound}")
|
|
64
65
|
_s_naught = (1 - mp.sqrt(1 - 2 * _delta_bound)) / 2
|
|
65
66
|
|
|
@@ -89,7 +90,6 @@ def hhi_delta_boundary(
|
|
|
89
90
|
Array of share-pairs, area under boundary.
|
|
90
91
|
|
|
91
92
|
"""
|
|
92
|
-
|
|
93
93
|
_delta_bound = mpf(f"{_delta_bound}")
|
|
94
94
|
_s_naught = 1 / 2 * (1 - mp.sqrt(1 - 2 * _delta_bound))
|
|
95
95
|
_s_mid = mp.sqrt(_delta_bound / 2)
|
|
@@ -202,7 +202,7 @@ def hhi_post_contrib_boundary(
|
|
|
202
202
|
|
|
203
203
|
|
|
204
204
|
# hand-rolled root finding
|
|
205
|
-
def shrratio_boundary_wtd_avg(
|
|
205
|
+
def shrratio_boundary_wtd_avg(
|
|
206
206
|
_delta_star: float = 0.075,
|
|
207
207
|
_r_val: float = DEFAULT_REC_RATIO,
|
|
208
208
|
/,
|
|
@@ -214,13 +214,13 @@ def shrratio_boundary_wtd_avg( # noqa: PLR0914
|
|
|
214
214
|
recapture_form: Literal["inside-out", "proportional"] = "inside-out",
|
|
215
215
|
dps: int = 5,
|
|
216
216
|
) -> GuidelinesBoundary:
|
|
217
|
-
"""
|
|
217
|
+
R"""
|
|
218
218
|
Share combinations on the share-weighted average diversion ratio boundary.
|
|
219
219
|
|
|
220
220
|
Parameters
|
|
221
221
|
----------
|
|
222
222
|
_delta_star
|
|
223
|
-
Share ratio (:math
|
|
223
|
+
Share ratio (:math:`\overline{d} / \overline{r}`)
|
|
224
224
|
_r_val
|
|
225
225
|
recapture ratio
|
|
226
226
|
agg_method
|
|
@@ -299,7 +299,6 @@ def shrratio_boundary_wtd_avg( # noqa: PLR0914
|
|
|
299
299
|
|
|
300
300
|
|
|
301
301
|
"""
|
|
302
|
-
|
|
303
302
|
_delta_star, _r_val = (mpf(f"{_v}") for _v in (_delta_star, _r_val))
|
|
304
303
|
_s_mid = mp.fdiv(_delta_star, 1 + _delta_star)
|
|
305
304
|
|
|
@@ -407,7 +406,7 @@ def shrratio_boundary_wtd_avg( # noqa: PLR0914
|
|
|
407
406
|
)
|
|
408
407
|
|
|
409
408
|
|
|
410
|
-
def shrratio_boundary_xact_avg(
|
|
409
|
+
def shrratio_boundary_xact_avg(
|
|
411
410
|
_delta_star: float = 0.075,
|
|
412
411
|
_r_val: float = DEFAULT_REC_RATIO,
|
|
413
412
|
/,
|
|
@@ -415,9 +414,8 @@ def shrratio_boundary_xact_avg( # noqa: PLR0914
|
|
|
415
414
|
recapture_form: Literal["inside-out", "proportional"] = "inside-out",
|
|
416
415
|
dps: int = 5,
|
|
417
416
|
) -> GuidelinesBoundary:
|
|
418
|
-
"""
|
|
419
|
-
Share combinations for the
|
|
420
|
-
merging-firm margins.
|
|
417
|
+
R"""
|
|
418
|
+
Share combinations for the exact average diversion/share-ratio boundary.
|
|
421
419
|
|
|
422
420
|
Notes
|
|
423
421
|
-----
|
|
@@ -455,7 +453,7 @@ def shrratio_boundary_xact_avg( # noqa: PLR0914
|
|
|
455
453
|
Parameters
|
|
456
454
|
----------
|
|
457
455
|
_delta_star
|
|
458
|
-
Share ratio (:math
|
|
456
|
+
Share ratio (:math:`\overline{d} / \overline{r}`).
|
|
459
457
|
_r_val
|
|
460
458
|
Recapture ratio
|
|
461
459
|
recapture_form
|
|
@@ -469,7 +467,6 @@ def shrratio_boundary_xact_avg( # noqa: PLR0914
|
|
|
469
467
|
Array of share-pairs, area under boundary, area under boundary.
|
|
470
468
|
|
|
471
469
|
"""
|
|
472
|
-
|
|
473
470
|
_delta_star, _r_val = (mpf(f"{_v}") for _v in (_delta_star, _r_val))
|
|
474
471
|
_s_mid = _delta_star / (1 + _delta_star)
|
|
475
472
|
_step_size = mp.power(10, -dps)
|
|
@@ -570,9 +567,8 @@ def shrratio_boundary_min(
|
|
|
570
567
|
recapture_form: str = "inside-out",
|
|
571
568
|
dps: int = 10,
|
|
572
569
|
) -> GuidelinesBoundary:
|
|
573
|
-
"""
|
|
574
|
-
Share combinations on the minimum
|
|
575
|
-
merging-firm margins.
|
|
570
|
+
R"""
|
|
571
|
+
Share combinations on the minimum diversion-ratio/share-ratio boundary.
|
|
576
572
|
|
|
577
573
|
Notes
|
|
578
574
|
-----
|
|
@@ -584,7 +580,7 @@ def shrratio_boundary_min(
|
|
|
584
580
|
Parameters
|
|
585
581
|
----------
|
|
586
582
|
_delta_star
|
|
587
|
-
Share ratio (:math
|
|
583
|
+
Share ratio (:math:`\overline{d} / \overline{r}`).
|
|
588
584
|
_r_val
|
|
589
585
|
Recapture ratio.
|
|
590
586
|
recapture_form
|
|
@@ -598,7 +594,6 @@ def shrratio_boundary_min(
|
|
|
598
594
|
Array of share-pairs, area under boundary.
|
|
599
595
|
|
|
600
596
|
"""
|
|
601
|
-
|
|
602
597
|
_delta_star, _r_val = (mpf(f"{_v}") for _v in (_delta_star, _r_val))
|
|
603
598
|
_s_intcpt = mpf("1.00")
|
|
604
599
|
_s_mid = _delta_star / (1 + _delta_star)
|
|
@@ -623,14 +618,13 @@ def shrratio_boundary_min(
|
|
|
623
618
|
def shrratio_boundary_max(
|
|
624
619
|
_delta_star: float = 0.075, _: float = DEFAULT_REC_RATIO, /, *, dps: int = 10
|
|
625
620
|
) -> GuidelinesBoundary:
|
|
626
|
-
"""
|
|
627
|
-
Share combinations on the minimum
|
|
628
|
-
merging-firm margins.
|
|
621
|
+
R"""
|
|
622
|
+
Share combinations on the minimum diversion-ratio/share-ratio boundary.
|
|
629
623
|
|
|
630
624
|
Parameters
|
|
631
625
|
----------
|
|
632
626
|
_delta_star
|
|
633
|
-
Share ratio (:math
|
|
627
|
+
Share ratio (:math:`\overline{d} / \overline{r}`).
|
|
634
628
|
_
|
|
635
629
|
Placeholder for recapture ratio included for consistency with other
|
|
636
630
|
share-ratio boundary functions.
|
|
@@ -642,7 +636,6 @@ def shrratio_boundary_max(
|
|
|
642
636
|
Array of share-pairs, area under boundary.
|
|
643
637
|
|
|
644
638
|
"""
|
|
645
|
-
|
|
646
639
|
_delta_star = mpf(f"{_delta_star}")
|
|
647
640
|
_s_intcpt = _delta_star
|
|
648
641
|
_s_mid = _delta_star / (1 + _delta_star)
|
|
@@ -691,8 +684,8 @@ def _shrratio_boundary_intcpt(
|
|
|
691
684
|
def lerp[LerpT: (float, MPFloat, ArrayDouble, ArrayBIGINT)](
|
|
692
685
|
_x1: LerpT, _x2: LerpT, _r: float | MPFloat = 0.25, /
|
|
693
686
|
) -> LerpT:
|
|
694
|
-
"""
|
|
695
|
-
From the function of the same name in the C++ standard [
|
|
687
|
+
R"""
|
|
688
|
+
From the function of the same name in the C++ standard [#]_.
|
|
696
689
|
|
|
697
690
|
Constructs the weighted average, :math:`w_1 x_1 + w_2 x_2`, where
|
|
698
691
|
:math:`w_1 = 1 - r` and :math:`w_2 = r`.
|
|
@@ -707,7 +700,7 @@ def lerp[LerpT: (float, MPFloat, ArrayDouble, ArrayBIGINT)](
|
|
|
707
700
|
Returns
|
|
708
701
|
-------
|
|
709
702
|
The linear interpolation, or weighted average,
|
|
710
|
-
:math:`x_1 + r
|
|
703
|
+
:math:`x_1 + r \cdot (x_1 - x_2) \equiv (1 - r) \cdot x_1 + r \cdot x_2`.
|
|
711
704
|
|
|
712
705
|
Raises
|
|
713
706
|
------
|
|
@@ -717,10 +710,9 @@ def lerp[LerpT: (float, MPFloat, ArrayDouble, ArrayBIGINT)](
|
|
|
717
710
|
References
|
|
718
711
|
----------
|
|
719
712
|
|
|
720
|
-
.. [
|
|
713
|
+
.. [#] C++ Reference, https://en.cppreference.com/w/cpp/numeric/lerp
|
|
721
714
|
|
|
722
715
|
"""
|
|
723
|
-
|
|
724
716
|
if not 0 <= _r <= 1:
|
|
725
717
|
raise ValueError("Specified interpolation weight must lie in [0, 1].")
|
|
726
718
|
elif _r == 0:
|
|
@@ -739,7 +731,7 @@ def round_cust(
|
|
|
739
731
|
rounding_mode: str = "ROUND_HALF_UP",
|
|
740
732
|
) -> float:
|
|
741
733
|
"""
|
|
742
|
-
|
|
734
|
+
Round to given fraction; the nearest 0.5% by default.
|
|
743
735
|
|
|
744
736
|
Parameters
|
|
745
737
|
----------
|
|
@@ -766,7 +758,6 @@ def round_cust(
|
|
|
766
758
|
the specified precision, :code:`frac`.
|
|
767
759
|
|
|
768
760
|
"""
|
|
769
|
-
|
|
770
761
|
if rounding_mode not in {
|
|
771
762
|
decimal.ROUND_05UP,
|
|
772
763
|
decimal.ROUND_CEILING,
|
|
@@ -793,11 +784,10 @@ def boundary_plot(
|
|
|
793
784
|
mktshare_axes_flag: bool = True,
|
|
794
785
|
backend: Literal["pgf"] | str | None = "pgf",
|
|
795
786
|
) -> tuple[mpl.pyplot, mpl.pyplot.Figure, mpl.axes.Axes, Callable[..., mpl.axes.Axes]]:
|
|
796
|
-
"""
|
|
787
|
+
"""Set up basic figure and axes for plots of safe harbor boundaries.
|
|
797
788
|
|
|
798
789
|
See, https://matplotlib.org/stable/tutorials/text/pgf.html
|
|
799
790
|
"""
|
|
800
|
-
|
|
801
791
|
if backend == "pgf":
|
|
802
792
|
mpl.use("pgf")
|
|
803
793
|
|
|
@@ -16,8 +16,8 @@ from mpmath import mp, mpf # type: ignore
|
|
|
16
16
|
from scipy.spatial.distance import minkowski as distance_function # type: ignore
|
|
17
17
|
from sympy import lambdify, simplify, solve, symbols # type: ignore
|
|
18
18
|
|
|
19
|
-
from .. import DEFAULT_REC_RATIO, VERSION
|
|
20
|
-
from . import GuidelinesBoundary, MPFloat
|
|
19
|
+
from .. import DEFAULT_REC_RATIO, VERSION # noqa: TID252
|
|
20
|
+
from . import GuidelinesBoundary, GuidelinesBoundaryCallable, MPFloat
|
|
21
21
|
from . import guidelines_boundary_functions as gbf
|
|
22
22
|
|
|
23
23
|
__version__ = VERSION
|
|
@@ -27,13 +27,6 @@ mp.dps = 32
|
|
|
27
27
|
mp.trap_complex = True
|
|
28
28
|
|
|
29
29
|
|
|
30
|
-
@frozen
|
|
31
|
-
class GuidelinesBoundaryCallable:
|
|
32
|
-
boundary_function: Callable[[ArrayDouble], ArrayDouble]
|
|
33
|
-
area: float
|
|
34
|
-
s_naught: float = 0
|
|
35
|
-
|
|
36
|
-
|
|
37
30
|
def dh_area_quad(_dh_val: float = 0.01, /) -> float:
|
|
38
31
|
"""
|
|
39
32
|
Area under the ΔHHI boundary.
|
|
@@ -52,7 +45,6 @@ def dh_area_quad(_dh_val: float = 0.01, /) -> float:
|
|
|
52
45
|
Area under ΔHHI boundary.
|
|
53
46
|
|
|
54
47
|
"""
|
|
55
|
-
|
|
56
48
|
_dh_val = mpf(f"{_dh_val}")
|
|
57
49
|
_s_naught = (1 - mp.sqrt(1 - 2 * _dh_val)) / 2
|
|
58
50
|
|
|
@@ -79,7 +71,6 @@ def hhi_delta_boundary_qdtr(_dh_val: float = 0.01, /) -> GuidelinesBoundaryCalla
|
|
|
79
71
|
Callable to generate array of share-pairs, area under boundary.
|
|
80
72
|
|
|
81
73
|
"""
|
|
82
|
-
|
|
83
74
|
_dh_val = mpf(f"{_dh_val}")
|
|
84
75
|
|
|
85
76
|
_s_1, _s_2 = symbols("s_1, s_2", positive=True)
|
|
@@ -113,14 +104,13 @@ def shrratio_boundary_qdtr_wtd_avg(
|
|
|
113
104
|
weighting: Literal["own-share", "cross-product-share"] | None = "own-share",
|
|
114
105
|
recapture_form: Literal["inside-out", "proportional"] = "inside-out",
|
|
115
106
|
) -> GuidelinesBoundaryCallable:
|
|
116
|
-
"""
|
|
117
|
-
Share combinations for the share-weighted average
|
|
118
|
-
merging-firm margins.
|
|
107
|
+
R"""
|
|
108
|
+
Share combinations for the share-weighted average share-ratio boundary.
|
|
119
109
|
|
|
120
110
|
Parameters
|
|
121
111
|
----------
|
|
122
112
|
_delta_star
|
|
123
|
-
corollary to GUPPI bound (:math
|
|
113
|
+
corollary to GUPPI bound (:math:`\overline{g} / (m^* \cdot \overline{r})`)
|
|
124
114
|
_r_val
|
|
125
115
|
recapture ratio
|
|
126
116
|
weighting
|
|
@@ -134,7 +124,6 @@ def shrratio_boundary_qdtr_wtd_avg(
|
|
|
134
124
|
Array of share-pairs, area under boundary.
|
|
135
125
|
|
|
136
126
|
"""
|
|
137
|
-
|
|
138
127
|
_delta_star = mpf(f"{_delta_star}")
|
|
139
128
|
_s_mid = _delta_star / (1 + _delta_star)
|
|
140
129
|
s_naught = 0
|
|
@@ -222,7 +211,7 @@ def shrratio_boundary_qdtr_wtd_avg(
|
|
|
222
211
|
)
|
|
223
212
|
|
|
224
213
|
|
|
225
|
-
def shrratio_boundary_distance(
|
|
214
|
+
def shrratio_boundary_distance(
|
|
226
215
|
_delta_star: float = 0.075,
|
|
227
216
|
_r_val: float = DEFAULT_REC_RATIO,
|
|
228
217
|
/,
|
|
@@ -232,9 +221,8 @@ def shrratio_boundary_distance( # noqa: PLR0914
|
|
|
232
221
|
recapture_form: Literal["inside-out", "proportional"] = "inside-out",
|
|
233
222
|
dps: int = 5,
|
|
234
223
|
) -> gbf.GuidelinesBoundary:
|
|
235
|
-
"""
|
|
236
|
-
Share combinations for the
|
|
237
|
-
symmetric merging-firm margins.
|
|
224
|
+
R"""
|
|
225
|
+
Share combinations for the share-ratio boundaries using various aggregators.
|
|
238
226
|
|
|
239
227
|
Reimplements the arithmetic-averages and distance estimations from function,
|
|
240
228
|
`shrratio_boundary_wtd_avg` but uses the Minkowski-distance function,
|
|
@@ -245,7 +233,7 @@ def shrratio_boundary_distance( # noqa: PLR0914
|
|
|
245
233
|
Parameters
|
|
246
234
|
----------
|
|
247
235
|
_delta_star
|
|
248
|
-
corollary to GUPPI bound (:math
|
|
236
|
+
corollary to GUPPI bound (:math:`\overline{g} / (m^* \cdot \overline{r})`)
|
|
249
237
|
_r_val
|
|
250
238
|
recapture ratio
|
|
251
239
|
agg_method
|
|
@@ -263,7 +251,6 @@ def shrratio_boundary_distance( # noqa: PLR0914
|
|
|
263
251
|
Array of share-pairs, area under boundary.
|
|
264
252
|
|
|
265
253
|
"""
|
|
266
|
-
|
|
267
254
|
_delta_star = mpf(f"{_delta_star}")
|
|
268
255
|
|
|
269
256
|
# parameters for iteration
|
|
@@ -379,7 +366,7 @@ def shrratio_boundary_distance( # noqa: PLR0914
|
|
|
379
366
|
)
|
|
380
367
|
|
|
381
368
|
|
|
382
|
-
def shrratio_boundary_xact_avg_mp(
|
|
369
|
+
def shrratio_boundary_xact_avg_mp(
|
|
383
370
|
_delta_star: float = 0.075,
|
|
384
371
|
_r_val: float = DEFAULT_REC_RATIO,
|
|
385
372
|
/,
|
|
@@ -387,7 +374,7 @@ def shrratio_boundary_xact_avg_mp( # noqa: PLR0914
|
|
|
387
374
|
recapture_form: Literal["inside-out", "proportional"] = "inside-out",
|
|
388
375
|
dps: int = 5,
|
|
389
376
|
) -> gbf.GuidelinesBoundary:
|
|
390
|
-
"""
|
|
377
|
+
R"""
|
|
391
378
|
Share combinations along the simple average diversion-ratio boundary.
|
|
392
379
|
|
|
393
380
|
Notes
|
|
@@ -426,7 +413,7 @@ def shrratio_boundary_xact_avg_mp( # noqa: PLR0914
|
|
|
426
413
|
Parameters
|
|
427
414
|
----------
|
|
428
415
|
_delta_star
|
|
429
|
-
Share ratio (:math
|
|
416
|
+
Share ratio (:math:`\overline{d} / \overline{r}`).
|
|
430
417
|
_r_val
|
|
431
418
|
Recapture ratio
|
|
432
419
|
recapture_form
|
|
@@ -440,7 +427,6 @@ def shrratio_boundary_xact_avg_mp( # noqa: PLR0914
|
|
|
440
427
|
Array of share-pairs, area under boundary, area under boundary.
|
|
441
428
|
|
|
442
429
|
"""
|
|
443
|
-
|
|
444
430
|
_delta_star = mpf(f"{_delta_star}")
|
|
445
431
|
_s_mid = _delta_star / (1 + _delta_star)
|
|
446
432
|
_bdry_step_sz = 10**-dps
|
|
@@ -534,7 +520,7 @@ def shrratio_boundary_xact_avg_mp( # noqa: PLR0914
|
|
|
534
520
|
|
|
535
521
|
# shrratio_boundary_wtd_avg_autoroot
|
|
536
522
|
# this function is about half as fast as the manual one! ... and a touch less precise
|
|
537
|
-
def _shrratio_boundary_wtd_avg_autoroot(
|
|
523
|
+
def _shrratio_boundary_wtd_avg_autoroot(
|
|
538
524
|
_delta_star: float = 0.075,
|
|
539
525
|
_r_val: float = DEFAULT_REC_RATIO,
|
|
540
526
|
/,
|
|
@@ -546,13 +532,13 @@ def _shrratio_boundary_wtd_avg_autoroot( # noqa: PLR0914
|
|
|
546
532
|
recapture_form: Literal["inside-out", "proportional"] = "inside-out",
|
|
547
533
|
dps: int = 5,
|
|
548
534
|
) -> GuidelinesBoundary:
|
|
549
|
-
"""
|
|
535
|
+
R"""
|
|
550
536
|
Share combinations on the share-weighted average diversion ratio boundary.
|
|
551
537
|
|
|
552
538
|
Parameters
|
|
553
539
|
----------
|
|
554
540
|
_delta_star
|
|
555
|
-
Share ratio (:math
|
|
541
|
+
Share ratio (:math:`\overline{d} / \overline{r}`)
|
|
556
542
|
_r_val
|
|
557
543
|
recapture ratio
|
|
558
544
|
agg_method
|
|
@@ -631,7 +617,6 @@ def _shrratio_boundary_wtd_avg_autoroot( # noqa: PLR0914
|
|
|
631
617
|
|
|
632
618
|
|
|
633
619
|
"""
|
|
634
|
-
|
|
635
620
|
_delta_star, _r_val = (mpf(f"{_v}") for _v in (_delta_star, _r_val))
|
|
636
621
|
_s_mid = mp.fdiv(_delta_star, 1 + _delta_star)
|
|
637
622
|
|