Coverage for src/ifunnel/models/CVaRtargets.py: 100%
42 statements
« prev ^ index » next coverage.py v7.9.2, created at 2025-07-12 09:14 +0000
« prev ^ index » next coverage.py v7.9.2, created at 2025-07-12 09:14 +0000
1import numpy as np
2import pandas as pd
3from loguru import logger
5from .ScenarioGeneration import ScenarioGenerator
8# Primal CVaR formula
9def CVaR(alpha: float, p: np.array, q: np.array) -> tuple[float, float]:
10 """
11 Computes CVaR using primal formula.
12 NOTE: Inputs p and q should be numpy arrays.
13 """
14 # We need to be careful that math index starts from 1 but numpy starts from 0
15 # (matters in formulas like ceil(alpha * T))
16 # T = q.shape[0]
17 sort_idx = np.argsort(q)
18 sorted_q = q[sort_idx]
19 sorted_p = p[sort_idx]
21 # Starting index
22 i_alpha = np.sort(np.nonzero(np.cumsum(sorted_p) >= alpha)[0])[0]
24 # Weight of VaR component in CVaR
25 lambda_alpha = (np.sum(sorted_p[: (i_alpha + 1)]) - alpha) / (1 - alpha)
27 # CVaR
28 var = sorted_q[i_alpha]
29 cvar = lambda_alpha * sorted_q[i_alpha] + np.dot(sorted_p[(i_alpha + 1) :], sorted_q[(i_alpha + 1) :]) / (1 - alpha)
31 return var, cvar
34# FUNCTION RUNNING THE OPTIMIZATION
35# ----------------------------------------------------------------------
36def portfolio_risk_target(scenarios: pd.DataFrame, cvar_alpha: float) -> float:
37 # Fixed equal weight x
38 x = pd.Series(index=scenarios.columns, data=1 / scenarios.shape[1])
40 # Number of scenarios
41 scenario_n = scenarios.shape[0]
43 # Portfolio loss scenarios
44 losses = (-scenarios @ x).to_numpy()
46 # Probabilities
47 probs = np.ones(scenario_n) / scenario_n
49 # CVaR
50 _, portfolio_cvar = CVaR(1 - cvar_alpha, probs, losses)
52 return portfolio_cvar
55# ----------------------------------------------------------------------
56# Mathematical Optimization: TARGETS GENERATION
57# ----------------------------------------------------------------------
58def get_cvar_targets(
59 test_date: str,
60 benchmark: list,
61 budget: int,
62 cvar_alpha: float,
63 data: pd.DataFrame,
64 scgen: ScenarioGenerator,
65 n_simulations: int,
66) -> tuple[pd.DataFrame, pd.DataFrame]:
67 logger.info(f"🎯 Generating CVaR targets for {benchmark}")
69 # Define Benchmark
70 tickers = benchmark
71 # Get weekly return of our benchmark
72 whole_dataset_benchmark = data[tickers].copy()
74 # Get weekly data just for testing period
75 test_dataset_benchmark = whole_dataset_benchmark[whole_dataset_benchmark.index >= test_date]
77 # Number of weeks for testing
78 weeks_n = len(test_dataset_benchmark.index)
80 # Get scenarios
81 # The Monte Carlo Method
82 target_scenarios = scgen.bootstrapping(
83 data=whole_dataset_benchmark, # subsetMST or subsetCLUST
84 n_simulations=n_simulations,
85 n_test=weeks_n,
86 )
88 # Compute the optimal portfolio outperforming zero percentage return
89 # ----------------------------------------------------------------------
90 p_points = len(target_scenarios[:, 0, 0]) # number of periods
91 s_points = len(target_scenarios[0, :, 0]) # number of scenarios
93 # COMPUTE CVaR TARGETS
94 list_targets = []
95 for p in range(p_points):
96 # create data frame with scenarios for a given period p
97 scenario_df = pd.DataFrame(target_scenarios[p, :, :], columns=tickers, index=list(range(s_points)))
99 # run CVaR model to compute CVaR targets
100 cvar_target = portfolio_risk_target(scenarios=scenario_df, cvar_alpha=cvar_alpha)
101 # save the result
102 list_targets.append(cvar_target)
104 # Generate new column so that dtype is set right.
105 targets = pd.DataFrame(columns=["CVaR_Target"], data=list_targets)
107 # COMPUTE PORTFOLIO VALUE
108 list_portfolio_values = []
109 for w in test_dataset_benchmark.index:
110 budget_next = sum((budget / len(tickers)) * (1 + test_dataset_benchmark.loc[w, :]))
111 list_portfolio_values.append(budget_next)
112 budget = budget_next
114 # Generate dataframe so that dtype is set right.
115 portfolio_value = pd.DataFrame(
116 columns=["Benchmark_Value"],
117 index=test_dataset_benchmark.index,
118 data=list_portfolio_values,
119 )
121 return targets, portfolio_value