API reference¶
The calculations behind the notebook, the docs and the app all live here.
Data¶
quantile_compass.fetch_data ¶
Fetch daily historical price data for the core portfolio risk factors and a few supplementary risk-factor series, and align them on their common overlapping date range.
Run directly to (re)populate data/raw/market_data.csv:
python -m quantile_compass.fetch_data
fetch_series ¶
Fetch max-available daily closing prices for one ticker.
Source code in src/quantile_compass/fetch_data.py
fetch_all ¶
Fetch every ticker in tickers and report each one's own availability.
Source code in src/quantile_compass/fetch_data.py
quantile_compass.data ¶
Load and clean the fetched market data.
load_market_data ¶
Load the raw fetched CSV, indexed by date.
clean_bad_ticks ¶
Detect and interpolate single-day "V-shaped" price spikes: a day whose
return exceeds threshold in magnitude and is (mostly) reversed by the
very next day's return in the opposite direction. Real market moves of
that size persist; a value that fully round-trips in one day is a bad
tick, not a crisis.
Returns a new DataFrame; flagged points are replaced by linear interpolation between their (good) neighbors.
Source code in src/quantile_compass/data.py
prepare_dataset ¶
prepare_dataset(path: str | Path = DEFAULT_DATA_PATH, start: str | None = DENSE_DATA_START) -> pd.DataFrame
Load, trim to the dense period, and clean the dataset - the standard entry point for the rest of the package.
Pass start=None to keep the full fetched history including its sparse
early section.
Source code in src/quantile_compass/data.py
Returns¶
quantile_compass.returns ¶
Log returns and the equity/forex decomposition of the portfolio.
PortfolioSpec
dataclass
¶
Weights and betas of the equity portfolio.
Attributes:
| Name | Type | Description |
|---|---|---|
w_us |
float
|
Share of the portfolio held in US equity. |
w_de |
float
|
Share held in German equity. |
beta_us |
float
|
Market beta of the US holding. |
beta_de |
float
|
Market beta of the German holding. |
Source code in src/quantile_compass/returns.py
compute_log_returns ¶
compute_log_returns(prices: DataFrame | Series, max_gap_days: int = DEFAULT_MAX_GAP_DAYS) -> pd.DataFrame | pd.Series
Continuously-compounded (log) returns.
Returns spanning more than max_gap_days calendar days are set to NaN:
when the data source is missing a stretch of days, the change across that
hole is not a one-day return and must not be treated as one.
Source code in src/quantile_compass/returns.py
decompose_portfolio_returns ¶
decompose_portfolio_returns(prices: DataFrame, spec: PortfolioSpec | None = None, max_gap_days: int = DEFAULT_MAX_GAP_DAYS) -> pd.DataFrame
Split the portfolio's ruble return into its equity and forex components.
The ruble value of a foreign holding is the local-currency price times the exchange rate, so in logs the return separates cleanly into an equity part and a currency part:
r_equity = w_de * beta_de * r_DAX + w_us * beta_us * r_NASDAQ
r_forex = w_de * r_EURRUB + w_us * r_USDRUB
r_total = r_equity + r_forex
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
prices
|
DataFrame
|
Price data containing NASDAQ100, DAX, USD_RUB, EUR_RUB columns. |
required |
spec
|
PortfolioSpec | None
|
Portfolio weights/betas; defaults to 60/40 US/German with betas 1.6/1.3. |
None
|
max_gap_days
|
int
|
Returns spanning wider gaps than this are dropped. |
DEFAULT_MAX_GAP_DAYS
|
Returns:
| Type | Description |
|---|---|
DataFrame
|
DataFrame with |
Source code in src/quantile_compass/returns.py
Volatility¶
quantile_compass.volatility ¶
EWMA volatility, covariance and correlation estimation (RiskMetrics style).
ewma_variance ¶
ewma_variance(returns: Series, lam: float = DEFAULT_LAMBDA, burn_in: int = DEFAULT_BURN_IN, mask_burn_in: bool = True) -> pd.Series
EWMA variance estimate.
.. math:: \hat\sigma_t^2 = (1-\lambda) r_{t-1}^2 + \lambda \hat\sigma_{t-1}^2
The recursion is seeded from the first burn_in observations rather than
the whole sample, so that from burn_in onward the estimate at time t
depends only on returns before t - i.e. it is usable as a genuine
one-day-ahead forecast. Seeding on the full sample (as textbook treatments
often do) leaks future information into every earlier estimate.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
returns
|
Series
|
Return series. |
required |
lam
|
float
|
Decay factor, strictly between 0 and 1. |
DEFAULT_LAMBDA
|
burn_in
|
int
|
Number of observations used to seed the recursion. |
DEFAULT_BURN_IN
|
mask_burn_in
|
bool
|
If True, the seeding window is returned as NaN, since those estimates are informed by their own window. |
True
|
Source code in src/quantile_compass/volatility.py
ewma_volatility ¶
ewma_volatility(returns: Series, lam: float = DEFAULT_LAMBDA, burn_in: int = DEFAULT_BURN_IN, mask_burn_in: bool = True) -> pd.Series
EWMA standard deviation - the square root of :func:ewma_variance.
Source code in src/quantile_compass/volatility.py
ewma_covariance ¶
ewma_covariance(a: Series, b: Series, lam: float = DEFAULT_LAMBDA, burn_in: int = DEFAULT_BURN_IN, mask_burn_in: bool = True) -> pd.Series
EWMA covariance between two contemporaneous return series.
.. math:: \hat\sigma_{ab,t} = (1-\lambda) r_{a,t-1} r_{b,t-1} + \lambda \hat\sigma_{ab,t-1}
Seeded the same way as :func:ewma_variance, for the same reason.
Source code in src/quantile_compass/volatility.py
ewma_correlation ¶
ewma_correlation(a: Series, b: Series, lam: float = DEFAULT_LAMBDA, burn_in: int = DEFAULT_BURN_IN, mask_burn_in: bool = True) -> pd.Series
EWMA correlation, i.e. covariance normalised by the two EWMA volatilities.
Source code in src/quantile_compass/volatility.py
annualize_volatility ¶
Scale a daily volatility to an annual one by the square root of time.
covariance_matrix ¶
covariance_matrix(equity: Series, forex: Series, lam: float = DEFAULT_LAMBDA, annualize: bool = True, trading_days: int = TRADING_DAYS, burn_in: int = DEFAULT_BURN_IN, asof: Timestamp | str | None = None) -> pd.DataFrame
Build the 2x2 equity/forex covariance matrix as of a given date (default: the final observation).
Returns a labelled DataFrame so it reads clearly in a notebook.
Source code in src/quantile_compass/volatility.py
Value-at-Risk¶
quantile_compass.var ¶
Value-at-Risk estimators: parametric, historical, and age-weighted historical.
parametric_var ¶
parametric_var(sensitivities: ndarray | list[float], cov_matrix: DataFrame | ndarray, alpha: float = DEFAULT_ALPHA, horizon_days: int = 1, trading_days: int = TRADING_DAYS, annualized_cov: bool = True) -> float
Normal parametric VaR for a linear portfolio of risk factors.
.. math:: VaR_{h,\alpha} = \Phi^{-1}(1-\alpha)\,\sqrt{\theta' \Omega_h \theta}
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
sensitivities
|
ndarray | list[float]
|
Exposure vector :math: |
required |
cov_matrix
|
DataFrame | ndarray
|
Risk-factor covariance matrix. |
required |
alpha
|
float
|
Significance level (0.01 = 99% confidence). |
DEFAULT_ALPHA
|
horizon_days
|
int
|
Risk horizon in trading days. |
1
|
trading_days
|
int
|
Trading days per year, used to de-annualize. |
TRADING_DAYS
|
annualized_cov
|
bool
|
Whether |
True
|
Returns:
| Type | Description |
|---|---|
float
|
VaR as a positive fraction of portfolio value (0.04 = a 4% loss). |
Source code in src/quantile_compass/var.py
historical_var ¶
Historical-simulation VaR: the empirical alpha-quantile of realised returns, sign-flipped so a loss is reported positive. Makes no distributional assumption.
Source code in src/quantile_compass/var.py
age_weighted_historical_var ¶
age_weighted_historical_var(returns: Series, alpha: float = DEFAULT_ALPHA, lam: float = DEFAULT_LAMBDA) -> float
Age-weighted ("hybrid") historical VaR after Boudoukh, Richardson and Whitelaw (1998).
Each observation gets an exponentially decaying probability weight, most
recent first (:math:\omega_T = 1-\lambda, :math:\omega_{i-1} = \lambda\omega_i).
Returns are then sorted and the quantile is read off the cumulative
weighted distribution, so recent market conditions dominate the tail
without discarding older history outright.
Source code in src/quantile_compass/var.py
scale_var_horizon ¶
Square-root-of-time scaling of a 1-day VaR (assumes i.i.d. returns).
Source code in src/quantile_compass/var.py
rolling_parametric_var ¶
rolling_parametric_var(equity: Series, forex: Series, alpha: float = DEFAULT_ALPHA, horizon_days: int = 1, lam: float = DEFAULT_LAMBDA, burn_in: int = DEFAULT_BURN_IN) -> pd.Series
Parametric VaR recomputed at every date from that date's EWMA covariance.
The combined portfolio's variance at time t is
.. math:: \sigma^2_t = \sigma^2_{E,t} + 2\sigma_{EF,t} + \sigma^2_{F,t}
(the quadratic form with exposures :math:\theta = [1, 1]), and the VaR is
:math:\Phi^{-1}(1-\alpha)\sqrt{h}\,\sigma_t.
Because the EWMA estimates carry no look-ahead, the value at t is what the model would have forecast on the morning of t - which is what makes the series usable for backtesting.
Returns:
| Type | Description |
|---|---|
Series
|
VaR as a positive fraction of portfolio value, NaN through the burn-in. |
Source code in src/quantile_compass/var.py
count_var_breaches ¶
Backtest a VaR series: how often did the realised loss exceed the forecast?
A well-calibrated 99% VaR should be breached on about 1% of days. Far fewer means the model is too conservative and ties up capital; far more means it understates the risk.
Returns:
| Type | Description |
|---|---|
dict[str, float]
|
Dict with the number of observations compared, the breach count, the |
dict[str, float]
|
realised breach rate and the rate the confidence level implies. |
Source code in src/quantile_compass/var.py
Plotting¶
quantile_compass.viz ¶
Reusable plotting helpers shared by the notebook, the docs and the app.
shade_crises ¶
Shade the crisis windows on a time-axis chart.
Source code in src/quantile_compass/viz.py
plot_price_levels ¶
Plot price series rebased to 100 at the start of the sample.
Source code in src/quantile_compass/viz.py
plot_volatility ¶
plot_volatility(vol_equity: Series, vol_forex: Series, ax: Axes | None = None, as_percent: bool = True) -> plt.Axes
Plot the two EWMA volatility series with crisis windows shaded.
Source code in src/quantile_compass/viz.py
plot_correlation ¶
Plot the EWMA equity/forex correlation through time.
Source code in src/quantile_compass/viz.py
plot_return_distribution ¶
Histogram of portfolio returns.
Source code in src/quantile_compass/viz.py
plot_var_comparison ¶
Bar chart comparing VaR estimates across methods.