Propensity Score Matching (PSM)
Estimate the Average Treatment effect on the Treated (ATT) of treatment_ticker exceeding treatment_threshold on dependent_ticker’s return, via Propensity Score Matching (PSM) on covariate_tickers.
Also known as: PSM, nearest-neighbor propensity matching.
For more information about the method, see causal_inference_model.get_propensity_score_matching.
Derives a binary “treatment” indicator the same way get_logistic_regression derives its binary outcome: 1 if treatment_ticker’s return in a given period exceeds treatment_threshold (0.0, i.e. a positive return, by default), else 0. This lets PSM answer e.g. “on periods where treatment_ticker has an outsized/positive move, is dependent_ticker’s return different than it would otherwise be – comparing only periods that LOOK similar on covariate_tickers (to control for the possibility that treatment_ticker tends to move on the same periods/regimes that also independently affect dependent_ticker)?”
The formula is as follows:
\[\text{Dₜ} = 1 \;\text{if the treatment ticker's}\; \text{column} (\text{Return by default}) \text{is above}\; \text{treatment\_threshold} (0 \;\text{by default}),\;\; \text{else 0}\] \[e(\text{xₜ}) = 1 / (1 + \exp(- \text{xₜ'beta})),\;\; \text{a logit of Dₜ on the covariate tickers (plus a constant)}\] \[\text{Matching on}\; \text{ℓₜ} = \ln(e / (1 - e)) \text{:} \;\text{each treated period},\;\; \text{in order},\;\; \text{takes the nearest unmatched control period} (\text{one-to-one},\; \text{without replacement}) \text{if} | \text{ℓ\_treated} - \text{ℓ\_control} | \leq \text{caliper},\;\; \text{which defaults to}\; 0.2 \cdot \text{the standard deviation} (\text{ddof} = 1) \text{of}\; \text{ℓ}\] \[\text{ATT} = (1 / m) \cdot \operatorname{Σ}(y_{\text{treated}} - y_{\text{control}}) \text{over the}\; m \;\text{matched pairs}\] \[\text{SE} = \operatorname{std}(y_{\text{treated}} - y_{\text{control}},\; \text{ddof} = 1) / \sqrt{m},\;\; \text{with a Student-t distribution with m - 1 degrees of freedom}\]Propensity Score Matching (PSM) in Python or via the MCP
Install the Finance Toolkit with:
pip install financetoolkit -U
Then call get_propensity_score_matching as shown below.
from financetoolkit import Toolkit
toolkit = Toolkit(
["XOM", "USO"],
api_key="FINANCIAL_MODELING_PREP_KEY",
start_date="2021-01-01",
end_date="2025-12-31",
)
# XOM's return on days USO rises more than 2%, matched on the Benchmark's return
toolkit.econometrics.get_propensity_score_matching(
dependent_ticker="XOM",
treatment_ticker="USO",
covariate_tickers="Benchmark",
treatment_threshold=0.02,
)
Or, after setting up the Finance Toolkit MCP, ask:
On days the United States Oil Fund (USO) rose more than 2%, how much more did ExxonMobil return than on comparable days with the same S&P 500 move, from 2021 to 2025?
Which returns:
| Value | |
|---|---|
| ATT | 0.0163 |
| Std. Error | 0.0016 |
| t-Statistic | 9.9366 |
| P-Value | 0 |
| Matched Pairs | 181 |
| N Treated | 185 |
| N Control | 1069 |
Using Your Own Data
get_propensity_score_matching runs on any data, not only prices: ratios, economic indicators, yield curves or your own dataset. Pass a DataFrame, a Series or a dictionary of them to Econometrics as data, with the dates in the rows or the columns, and set changes to how its changes are calculated: "percentage", "log", "difference" for rates and ratios, or None when the data are changes already. For example, for the monthly unemployment rates of the United States and Germany, where each country is a column:
from financetoolkit import Economics, Econometrics
economics = Economics(start_date="2000-01-01")
unemployment = economics.get_unemployment_rate(
countries=["United States", "Germany"], period="monthly"
)
econometrics = Econometrics(data=unemployment, changes="difference")
Then call econometrics.get_propensity_score_matching with dependent_ticker, treatment_ticker and covariate_tickers, using the column names of the data as tickers.
Parameters
get_propensity_score_matching accepts the following parameters.
| Parameter | Type | Description | Example |
|---|---|---|---|
dependent_ticker | str | The outcome asset. | "AAPL" |
treatment_ticker | str | The asset whose return, once it exceeds treatment_threshold, defines the treatment indicator. | "MSFT" |
covariate_tickers | str | list[str] | The asset(s) used as covariates to estimate the propensity score – should include asset(s) believed to drive selection into “treatment”. | "Benchmark" |
treatment_threshold | float | The return threshold defining treatment. Defaults to 0.0. | 0.01 |
period | str | The data frequency (daily, weekly, monthly, quarterly, or yearly). Defaults to “daily”. | "weekly" |
column | str | The historical data column to use. Defaults to “Return”. | "Return" |
caliper | float | None | The maximum allowed logit-propensity-score matching distance. Defaults to None, which uses Austin’s (2011) rule of thumb – see causal_inference_model.get_propensity_score_matching. | 0.05 |
add_constant | bool | Whether to include an intercept in the propensity score model. Defaults to True. | False |
rounding | int | None | The number of decimals to round the results to. Defaults to None. | 2 |