Skip to content

base module

Base functions and classes for portfolio optimization.


prepare_returns function

prepare_returns(
    returns,
    nan_to_zero=True,
    dropna_rows=True,
    dropna_cols=True,
    dropna_any=True
)

Prepare returns.


pypfopt_optimize function

pypfopt_optimize(
    target=None,
    target_is_convex=None,
    weights_sum_to_one=None,
    target_constraints=None,
    target_solver=None,
    target_initial_guess=None,
    objectives=None,
    constraints=None,
    sector_mapper=None,
    sector_lower=None,
    sector_upper=None,
    discrete_allocation=None,
    allocation_method=None,
    silence_warnings=None,
    ignore_opt_errors=None,
    ignore_errors=None,
    **kwargs
)

Get allocation using PyPortfolioOpt.

First, it resolves the optimizer using resolve_pypfopt_optimizer(). Depending upon which arguments it takes, it may further resolve expected returns, covariance matrix, etc. Then, it adds objectives and constraints to the optimizer instance, calls the target metric, extracts the weights, and finally, converts the weights to an integer allocation (if requested).

To specify the optimizer, use optimizer (see resolve_pypfopt_optimizer()). To specify the expected returns, use expected_returns (see resolve_pypfopt_expected_returns()). To specify the covariance matrix, use cov_matrix (see resolve_pypfopt_cov_matrix()). All other keyword arguments in **kwargs are used by resolve_pypfopt_func_call().

Each objective can be a function, an attribute of pypfopt.objective_functions, or an iterable of such.

Each constraint can be a function or an interable of such.

The target can be an attribute of the optimizer, or a stand-alone function. If target_is_convex is True, the function is added as a convex function. Otherwise, the function is added as a non-convex function. The keyword arguments weights_sum_to_one and those starting with target are passed pypfopt.base_optimizer.BaseConvexOptimizer.convex_objective and pypfopt.base_optimizer.BaseConvexOptimizer.nonconvex_objective respectively.

Set ignore_opt_errors to True to ignore any target optimization errors. Set ignore_errors to True to ignore any errors, even those caused by the user.

If discrete_allocation is True, resolves pypfopt.discrete_allocation.DiscreteAllocation and calls allocation_method as an attribute of the allocation object.

Any function is resolved using resolve_pypfopt_func_call().

For defaults, see pypfopt under pfopt.

Usage

  • Using mean historical returns, Ledoit-Wolf covariance matrix with constant variance, and efficient frontier:
>>> from vectorbtpro import *

>>> data = vbt.YFData.pull(["MSFT", "AMZN", "KO", "MA"])

100%

>>> vbt.pypfopt_optimize(prices=data.get("Close"))
{'MSFT': 0.13324, 'AMZN': 0.10016, 'KO': 0.03229, 'MA': 0.73431}
  • EMA historical returns and sample covariance:
>>> vbt.pypfopt_optimize(
...     prices=data.get("Close"),
...     expected_returns="ema_historical_return",
...     cov_matrix="sample_cov"
... )
{'MSFT': 0.08984, 'AMZN': 0.0, 'KO': 0.91016, 'MA': 0.0}
  • EMA historical returns, efficient Conditional Value at Risk, and other parameters automatically passed to their respective functions. Optimized towards lowest CVaR:
>>> vbt.pypfopt_optimize(
...     prices=data.get("Close"),
...     expected_returns="ema_historical_return",
...     optimizer="efficient_cvar",
...     beta=0.9,
...     weight_bounds=(-1, 1),
...     target="min_cvar"
... )
{'MSFT': 0.14779, 'AMZN': 0.07224, 'KO': 0.77552, 'MA': 0.00445}
  • Adding custom objectives:
>>> vbt.pypfopt_optimize(
...     prices=data.get("Close"),
...     objectives=["L2_reg"],
...     gamma=0.1,
...     target="min_volatility"
... )
{'MSFT': 0.22228, 'AMZN': 0.15685, 'KO': 0.28712, 'MA': 0.33375}
  • Adding custom constraints:
>>> vbt.pypfopt_optimize(
...     prices=data.get("Close"),
...     constraints=[lambda w: w[data.symbols.index("MSFT")] <= 0.1]
... )
{'MSFT': 0.1, 'AMZN': 0.10676, 'KO': 0.04341, 'MA': 0.74982}
  • Optimizing towards a custom convex objective (to add a non-convex objective, set target_is_convex to False):
>>> import cvxpy as cp

>>> def logarithmic_barrier_objective(w, cov_matrix, k=0.1):
...     log_sum = cp.sum(cp.log(w))
...     var = cp.quad_form(w, cov_matrix)
...     return var - k * log_sum

>>> pypfopt_optimize(
...     prices=data.get("Close"),
...     target=logarithmic_barrier_objective
... )
{'MSFT': 0.24595, 'AMZN': 0.23047, 'KO': 0.25862, 'MA': 0.26496}

resolve_asset_classes function

resolve_asset_classes(
    asset_classes,
    columns,
    col_indices=None
)

Resolve asset classes for Riskfolio-Lib.

Supports the following formats:

  • None: Takes columns where the bottom-most level is assumed to be assets
  • Index: Each level in the index must be a different asset class set
  • Nested dict: Each sub-dict must be a different asset class set
  • Sequence of strings or ints: Matches them against level names in the columns. If the columns have a single level, or some level names were not found, uses the sequence directly as one class asset set named 'Class'.
  • Sequence of dicts: Each dict becomes a row in the new DataFrame
  • DataFrame where the first column is the asset list and the next columns are the different asset’s classes sets (this is the target format accepted by Riskfolio-Lib). See an example here.

Note

If asset_classes is neither None nor a DataFrame, the bottom-most level in columns gets renamed to 'Assets' and becomes the first column of the new DataFrame.


resolve_assets_constraints function

resolve_assets_constraints(
    constraints
)

Resolve asset constraints for Riskfolio-Lib.

Apart from the target format, also accepts a sequence of dicts such that each dict becomes a row in a new DataFrame. Dicts don't have to specify all column names, the function will autofill any missing elements/columns.


resolve_assets_views function

resolve_assets_views(
    views
)

Resolve asset views for Riskfolio-Lib.

Apart from the target format, also accepts a sequence of dicts such that each dict becomes a row in a new DataFrame. Dicts don't have to specify all column names, the function will autofill any missing elements/columns.


resolve_factors_constraints function

resolve_factors_constraints(
    constraints
)

Resolve factors constraints for Riskfolio-Lib.

Apart from the target format, also accepts a sequence of dicts such that each dict becomes a row in a new DataFrame. Dicts don't have to specify all column names, the function will autofill any missing elements/columns.


resolve_factors_views function

resolve_factors_views(
    views
)

Resolve factors views for Riskfolio-Lib.

Apart from the target format, also accepts a sequence of dicts such that each dict becomes a row in a new DataFrame. Dicts don't have to specify all column names, the function will autofill any missing elements/columns.


resolve_hrp_constraints function

resolve_hrp_constraints(
    constraints
)

Resolve HRP constraints for Riskfolio-Lib.

Apart from the target format, also accepts a sequence of dicts such that each dict becomes a row in a new DataFrame. Dicts don't have to specify all column names, the function will autofill any missing elements/columns.


resolve_pypfopt_cov_matrix function

resolve_pypfopt_cov_matrix(
    cov_matrix='ledoit_wolf',
    **kwargs
)

Resolve the covariance matrix.

cov_matrix can be an array, an attribute of pypfopt.risk_models, a function, or one of the following options:

  • 'sample_cov': pypfopt.risk_models.sample_cov
  • 'semicovariance' or 'semivariance': pypfopt.risk_models.semicovariance
  • 'exp_cov': pypfopt.risk_models.exp_cov
  • 'ledoit_wolf' or 'ledoit_wolf_constant_variance': pypfopt.risk_models.CovarianceShrinkage.ledoit_wolf with 'constant_variance' as shrinkage factor
  • 'ledoit_wolf_single_factor': pypfopt.risk_models.CovarianceShrinkage.ledoit_wolf with 'single_factor' as shrinkage factor
  • 'ledoit_wolf_constant_correlation': pypfopt.risk_models.CovarianceShrinkage.ledoit_wolf with 'constant_correlation' as shrinkage factor
  • 'oracle_approximating': pypfopt.risk_models.CovarianceShrinkage.ledoit_wolf with 'oracle_approximating' as shrinkage factor

Any function is resolved using resolve_pypfopt_func_call().


resolve_pypfopt_expected_returns function

resolve_pypfopt_expected_returns(
    expected_returns='mean_historical_return',
    **kwargs
)

Resolve the expected returns.

expected_returns can be an array, an attribute of pypfopt.expected_returns, a function, or one of the following options:

  • 'mean_historical_return': pypfopt.expected_returns.mean_historical_return
  • 'ema_historical_return': pypfopt.expected_returns.ema_historical_return
  • 'capm_return': pypfopt.expected_returns.capm_return
  • 'bl_returns': pypfopt.black_litterman.BlackLittermanModel.bl_returns

Any function is resolved using resolve_pypfopt_func_call().


resolve_pypfopt_func_call function

resolve_pypfopt_func_call(
    pypfopt_func,
    **kwargs
)

Resolve arguments using resolve_pypfopt_func_kwargs() and call the function with that arguments.


resolve_pypfopt_func_kwargs function

resolve_pypfopt_func_kwargs(
    pypfopt_func,
    cache=None,
    var_kwarg_names=None,
    used_arg_names=None,
    **kwargs
)

Resolve keyword arguments passed to any optimization function with the layout of PyPortfolioOpt.

Parses the signature of pypfopt_func, and for each accepted argument, looks for an argument with the same name in kwargs. If not found, tries to resolve that argument using other arguments or by calling other optimization functions.

Argument frequency gets resolved with (global) freq and year_freq using ReturnsAccessor.get_ann_factor().

Any argument in kwargs can be wrapped using pfopt_func_dict to define the argument per function rather than globally.

Note

When providing custom functions, make sure that the arguments they accept are visible in the signature (that is, no variable arguments) and have the same naming as in PyPortfolioOpt.

Functions market_implied_prior_returns and BlackLittermanModel.bl_weights take risk_aversion, which is different from arguments with the same name in other functions. To set it, pass delta.


resolve_pypfopt_optimizer function

resolve_pypfopt_optimizer(
    optimizer='efficient_frontier',
    **kwargs
)

Resolve the optimizer.

optimizer can be an instance of pypfopt.base_optimizer.BaseOptimizer, an attribute of pypfopt, a subclass of pypfopt.base_optimizer.BaseOptimizer, or one of the following options:

  • 'efficient_frontier': pypfopt.efficient_frontier.EfficientFrontier
  • 'efficient_cdar': pypfopt.efficient_frontier.EfficientCDaR
  • 'efficient_cvar': pypfopt.efficient_frontier.EfficientCVaR
  • 'efficient_semivariance': pypfopt.efficient_frontier.EfficientSemivariance
  • 'black_litterman' or 'bl': pypfopt.black_litterman.BlackLittermanModel
  • 'hierarchical_portfolio', 'hrpopt', or 'hrp': pypfopt.hierarchical_portfolio.HRPOpt
  • 'cla': pypfopt.cla.CLA

Any function is resolved using resolve_pypfopt_func_call().


resolve_riskfolio_func_kwargs function

resolve_riskfolio_func_kwargs(
    riskfolio_func,
    unused_arg_names=None,
    func_kwargs=None,
    **kwargs
)

Select keyword arguments belonging to riskfolio_func.


riskfolio_optimize function

riskfolio_optimize(
    returns,
    nan_to_zero=None,
    dropna_rows=None,
    dropna_cols=None,
    dropna_any=None,
    factors=None,
    port=None,
    port_cls=None,
    opt_method=None,
    stats_methods=None,
    model=None,
    asset_classes=None,
    constraints_method=None,
    constraints=None,
    views_method=None,
    views=None,
    solvers=None,
    sol_params=None,
    freq=None,
    year_freq=None,
    pre_opt=None,
    pre_opt_kwargs=None,
    pre_opt_as_w=None,
    func_kwargs=None,
    silence_warnings=None,
    return_port=None,
    ignore_errors=None,
    **kwargs
)

Get allocation using Riskfolio-Lib.

Args

returns : array_like
A dataframe that contains the returns of the assets.
nan_to_zero : bool
Whether to convert NaN values to zero.
dropna_rows : bool

Whether to drop rows with all NaN/zero values.

Gets applied only if nan_to_zero is True or dropna_any is False.

dropna_cols : bool
Whether to drop columns with all NaN/zero values.
dropna_any : bool

Whether to drop any NaN values.

Gets applied only if nan_to_zero is False.

factors : array_like
A dataframe that contains the factors.
port : Portfolio or HCPortfolio
Already initialized portfolio.
port_cls : str or type

Portfolio class.

Supports the following values:

  • None: Uses Portfolio
  • 'hc' or 'hcportfolio' (case-insensitive): Uses HCPortfolio
  • Other string: Uses attribute of riskfolio
  • Class: Uses a custom class
opt_method : str or callable

Optimization method.

Supports the following values:

  • None or 'optimization': Uses port.optimization (where port is a portfolio instance)
  • 'wc' or 'wc_optimization': Uses port.wc_optimization
  • 'rp' or 'rp_optimization': Uses port.rp_optimization
  • 'rrp' or 'rrp_optimization': Uses port.rrp_optimization
  • 'owa' or 'owa_optimization': Uses port.owa_optimization
  • String: Uses attribute of port
  • Callable: Uses a custom optimization function
stats_methods : str or sequence of str

Sequence of stats methods to call before optimization.

If None, tries to automatically populate the sequence using opt_method and model. For example, calls port.assets_stats if model="Classic" is used. Also, if func_kwargs is not empty, adds all functions whose name ends with '_stats'.

model : str
The model used to optimize the portfolio.
asset_classes : any

Asset classes matrix.

See resolve_asset_classes() for possible formats.

constraints_method : str

Constraints method.

Supports the following values:

If None and the class Portfolio is used, will use factors constraints if factors_stats is used, otherwise assets constraints. If the class HCPortfolio is used, will use HRP constraints.

constraints : any

Constraints matrix.

See resolve_assets_constraints() for possible formats of assets constraints, resolve_factors_constraints() for possible formats of factors constraints, and resolve_hrp_constraints() for possible formats of HRP constraints.

views_method : str

Views method.

Supports the following values:

If None, will use factors views if blfactors_stats is used, otherwise assets views.

views : any

Views matrix.

See resolve_assets_views() for possible formats of assets views and resolve_factors_views() for possible formats of factors views.

solvers : list of str
Solvers.
sol_params : dict
Solver parameters.
freq : frequency_like

Frequency to be used to compute the annualization factor.

Make sure to provide it when using views.

year_freq : frequency_like

Year frequency to be used to compute the annualization factor.

Make sure to provide it when using views.

pre_opt : bool
Whether to pre-optimize the portfolio with pre_opt_kwargs.
pre_opt_kwargs : dict
Call riskfolio_optimize() with these keyword arguments and use the returned portfolio for further optimization.
pre_opt_as_w : bool
Whether to use the weights as w from the pre-optimization step.
func_kwargs : dict

Further keyword arguments by function.

Can be used to override any arguments from kwargs matched with the function, or to add more arguments. Will be wrapped with pfopt_func_dict and passed to select_pfopt_func_kwargs() when calling each Riskfolio-Lib's function.

silence_warnings : bool
Whether to silence all warnings.
return_port : bool
Whether to also return the portfolio.
ignore_errors : bool
Whether to ignore any errors, even those caused by the user.
**kwargs
Keyword arguments that will be passed to any Riskfolio-Lib's function that needs them (i.e., lists any of them in its signature).

For defaults, see riskfolio under pfopt.

Usage

  • Classic Mean Risk Optimization:
>>> from vectorbtpro import *

>>> data = vbt.YFData.pull(["MSFT", "AMZN", "KO", "MA"])
>>> returns = data.close.vbt.to_returns()

100%

>>> vbt.riskfolio_optimize(
...     returns,
...     method_mu='hist', method_cov='hist', d=0.94,  # assets_stats
...     model='Classic', rm='MV', obj='Sharpe', hist=True, rf=0, l=0  # optimization
... )
{'MSFT': 0.26297126323056036,
 'AMZN': 0.13984467450137006,
 'KO': 0.35870315943426767,
 'MA': 0.238480902833802}
  • The same by splitting arguments:
>>> vbt.riskfolio_optimize(
...     returns,
...     func_kwargs=dict(
...         assets_stats=dict(method_mu='hist', method_cov='hist', d=0.94),
...         optimization=dict(model='Classic', rm='MV', obj='Sharpe', hist=True, rf=0, l=0)
...     )
... )
{'MSFT': 0.26297126323056036,
 'AMZN': 0.13984467450137006,
 'KO': 0.35870315943426767,
 'MA': 0.238480902833802}
  • Asset constraints:
>>> vbt.riskfolio_optimize(
...     returns,
...     constraints=[
...         {
...             "Type": "Assets",
...             "Position": "MSFT",
...             "Sign": "<=",
...             "Weight": 0.01
...         }
...     ]
... )
{'MSFT': 0.009999990814976588,
 'AMZN': 0.19788481506569947,
 'KO': 0.4553600308839969,
 'MA': 0.336755163235327}
  • Asset class constraints:
>>> vbt.riskfolio_optimize(
...     returns,
...     asset_classes=["C1", "C1", "C2", "C2"],
...     constraints=[
...         {
...             "Type": "Classes",
...             "Set": "Class",
...             "Position": "C1",
...             "Sign": "<=",
...             "Weight": 0.1
...         }
...     ]
... )
{'MSFT': 0.03501297245802569,
 'AMZN': 0.06498702655063979,
 'KO': 0.4756624658301967,
 'MA': 0.4243375351611379}
  • Hierarchical Risk Parity (HRP) Portfolio Optimization:
>>> vbt.riskfolio_optimize(
...     returns,
...     port_cls="HCPortfolio",
...     model='HRP',
...     codependence='pearson',
...     rm='MV',
...     rf=0,
...     linkage='single',
...     max_k=10,
...     leaf_order=True
... )
{'MSFT': 0.19091632057853536,
 'AMZN': 0.11069893826556164,
 'KO': 0.28589872132122485,
 'MA': 0.41248601983467814}

select_pfopt_func_kwargs function

select_pfopt_func_kwargs(
    pypfopt_func,
    kwargs=None
)

Select keyword arguments belonging to pypfopt_func.


PortfolioOptimizer class

PortfolioOptimizer(
    wrapper,
    alloc_records,
    allocations,
    **kwargs
)

Class that exposes methods for generating allocations.

Superclasses

Inherited members


alloc_records property

Allocation ranges of type AllocRanges or points of type AllocPoints.


allocations property

Calls PortfolioOptimizer.get_allocations() with default arguments.


column_stack class method

PortfolioOptimizer.column_stack(
    *objs,
    wrapper_kwargs=None,
    **kwargs
)

Stack multiple PortfolioOptimizer instances along columns.

Uses ArrayWrapper.column_stack() to stack the wrappers.


fill_allocations method

PortfolioOptimizer.fill_allocations(
    dropna=None,
    fill_value=nan,
    wrap_kwargs=None,
    squeeze_groups=True
)

Fill an empty DataFrame with allocations.

Set dropna to 'all' to remove all NaN rows, or to 'head' to remove any rows coming before the first allocation.


filled_allocations property

Calls PortfolioOptimizer.fill_allocations() with default arguments.


from_allocate_func class method

PortfolioOptimizer.from_allocate_func(
    wrapper,
    allocate_func,
    *args,
    every=None,
    normalize_every=False,
    at_time=None,
    start=None,
    end=None,
    exact_start=False,
    on=None,
    add_delta=None,
    kind=None,
    indexer_method='bfill',
    indexer_tolerance=None,
    skip_not_found=True,
    index_points=None,
    rescale_to=None,
    parameterizer_cls=None,
    param_search_kwargs=None,
    name_tuple_to_str=None,
    group_configs=None,
    pre_group_func=None,
    jitted_loop=False,
    jitted=None,
    chunked=None,
    template_context=None,
    group_execute_kwargs=None,
    execute_kwargs=None,
    random_subset=None,
    clean_index_kwargs=None,
    wrapper_kwargs=None,
    **kwargs
)

Generate allocations from an allocation function.

Generates date points and allocates at those points.

Similar to PortfolioOptimizer.from_optimize_func(), but generates points using ArrayWrapper.get_index_points() and makes each point available as index_point in the context.

If jitted_loop is True, see allocate_meta_nb().

Also, in contrast to PortfolioOptimizer.from_optimize_func(), creates records of type AllocPoints.

Usage

  • Allocate uniformly every day:
>>> from vectorbtpro import *

>>> data = vbt.YFData.pull(
...     ["MSFT", "AMZN", "AAPL"],
...     start="2010-01-01",
...     end="2020-01-01"
... )
>>> close = data.get("Close")

>>> def uniform_allocate_func(n_cols):
...     return np.full(n_cols, 1 / n_cols)

>>> pfo = vbt.PortfolioOptimizer.from_allocate_func(
...     close.vbt.wrapper,
...     uniform_allocate_func,
...     close.shape[1]
... )
>>> pfo.allocations
symbol                         MSFT      AMZN      AAPL
Date
2010-01-04 00:00:00-05:00  0.333333  0.333333  0.333333
2010-01-05 00:00:00-05:00  0.333333  0.333333  0.333333
2010-01-06 00:00:00-05:00  0.333333  0.333333  0.333333
2010-01-07 00:00:00-05:00  0.333333  0.333333  0.333333
2010-01-08 00:00:00-05:00  0.333333  0.333333  0.333333
...                             ...       ...       ...
2019-12-24 00:00:00-05:00  0.333333  0.333333  0.333333
2019-12-26 00:00:00-05:00  0.333333  0.333333  0.333333
2019-12-27 00:00:00-05:00  0.333333  0.333333  0.333333
2019-12-30 00:00:00-05:00  0.333333  0.333333  0.333333
2019-12-31 00:00:00-05:00  0.333333  0.333333  0.333333

[2516 rows x 3 columns]
  • Allocate randomly every first date of the year:
>>> def random_allocate_func(n_cols):
...     weights = np.random.uniform(size=n_cols)
...     return weights / weights.sum()

>>> pfo = vbt.PortfolioOptimizer.from_allocate_func(
...     close.vbt.wrapper,
...     random_allocate_func,
...     close.shape[1],
...     every="AS-JAN"
... )
>>> pfo.allocations
symbol                         MSFT      AMZN      AAPL
Date
2011-01-03 00:00:00+00:00  0.160335  0.122434  0.717231
2012-01-03 00:00:00+00:00  0.071386  0.469564  0.459051
2013-01-02 00:00:00+00:00  0.125853  0.168480  0.705668
2014-01-02 00:00:00+00:00  0.391565  0.169205  0.439231
2015-01-02 00:00:00+00:00  0.115075  0.602844  0.282081
2016-01-04 00:00:00+00:00  0.244070  0.046547  0.709383
2017-01-03 00:00:00+00:00  0.316065  0.335000  0.348935
2018-01-02 00:00:00+00:00  0.422142  0.252154  0.325704
2019-01-02 00:00:00+00:00  0.368748  0.195147  0.436106
  • Specify index points manually:
>>> pfo = vbt.PortfolioOptimizer.from_allocate_func(
...     close.vbt.wrapper,
...     random_allocate_func,
...     close.shape[1],
...     index_points=[0, 30, 60]
... )
>>> pfo.allocations
symbol                         MSFT      AMZN      AAPL
Date
2010-01-04 00:00:00+00:00  0.257878  0.308287  0.433835
2010-02-17 00:00:00+00:00  0.090927  0.471980  0.437094
2010-03-31 00:00:00+00:00  0.395855  0.148516  0.455629
  • Specify allocations manually:
>>> def manual_allocate_func(weights):
...     return weights

>>> pfo = vbt.PortfolioOptimizer.from_allocate_func(
...     close.vbt.wrapper,
...     manual_allocate_func,
...     vbt.RepEval("weights[i]", context=dict(weights=[
...         [1, 0, 0],
...         [0, 1, 0],
...         [0, 0, 1]
...     ])),
...     index_points=[0, 30, 60]
... )
>>> pfo.allocations
symbol                     MSFT  AMZN  AAPL
Date
2010-01-04 00:00:00+00:00     1     0     0
2010-02-17 00:00:00+00:00     0     1     0
2010-03-31 00:00:00+00:00     0     0     1
  • Use Numba-compiled loop:
>>> @njit
... def random_allocate_func_nb(i, idx, n_cols):
...     weights = np.random.uniform(0, 1, n_cols)
...     return weights / weights.sum()

>>> pfo = vbt.PortfolioOptimizer.from_allocate_func(
...     close.vbt.wrapper,
...     random_allocate_func_nb,
...     close.shape[1],
...     index_points=[0, 30, 60],
...     jitted_loop=True
... )
>>> pfo.allocations
symbol                         MSFT      AMZN      AAPL
Date
2010-01-04 00:00:00+00:00  0.231925  0.351085  0.416990
2010-02-17 00:00:00+00:00  0.163050  0.070292  0.766658
2010-03-31 00:00:00+00:00  0.497465  0.500215  0.002319

Hint

There is no big reason of using the Numba-compiled loop, apart from when having to rebalance many thousands of times. Usually, using a regular Python loop and a Numba-compiled allocation function should suffice.


from_allocations class method

PortfolioOptimizer.from_allocations(
    wrapper,
    allocations,
    **kwargs
)

Pick allocations from a (flexible) array.

Uses PortfolioOptimizer.from_allocate_func().

If allocations is a DataFrame, uses its index as labels. If it's a Series or dict, uses it as a single allocation without index, which by default gets assigned to each index. If it's neither one of the above nor a NumPy array, tries to convert it into a NumPy array.

If allocations is a NumPy array, uses pick_idx_allocate_func_nb() and a Numba-compiled loop. Otherwise, uses a regular Python function to pick each allocation (which can be a dict, Series, etc.). Selection of elements is done in a flexible manner, meaning a single element will be applied to all rows, while one-dimensional arrays will be also applied to all rows but also broadcast across columns (as opposed to rows).


from_filled_allocations class method

PortfolioOptimizer.from_filled_allocations(
    allocations,
    valid_only=True,
    nonzero_only=True,
    unique_only=True,
    wrapper=None,
    **kwargs
)

Pick allocations from an already filled array.

Uses PortfolioOptimizer.from_allocate_func().

Uses pick_point_allocate_func_nb() and a Numba-compiled loop.

Extracts allocation points using get_alloc_points_nb().


from_initial class method

PortfolioOptimizer.from_initial(
    wrapper,
    allocations,
    **kwargs
)

Allocate once at the first index.

Uses PortfolioOptimizer.from_allocations() with on=0.


from_optimize_func class method

PortfolioOptimizer.from_optimize_func(
    wrapper,
    optimize_func,
    *args,
    every=None,
    normalize_every=False,
    split_every=True,
    start_time=None,
    end_time=None,
    lookback_period=None,
    start=None,
    end=None,
    exact_start=False,
    fixed_start=False,
    closed_start=True,
    closed_end=False,
    add_start_delta=None,
    add_end_delta=None,
    kind=None,
    skip_not_found=True,
    index_ranges=None,
    index_loc=None,
    rescale_to=None,
    alloc_wait=1,
    parameterizer_cls=None,
    param_search_kwargs=None,
    name_tuple_to_str=None,
    group_configs=None,
    pre_group_func=None,
    splitter_cls=None,
    eval_id=None,
    jitted_loop=False,
    jitted=None,
    chunked=None,
    template_context=None,
    group_execute_kwargs=None,
    execute_kwargs=None,
    random_subset=None,
    clean_index_kwargs=None,
    wrapper_kwargs=None,
    **kwargs
)

Generate allocations from an optimization function.

Generates date ranges, performs optimization on the subset of data that belongs to each date range, and allocates at the end of each range.

This is a parameterized method that allows testing multiple combinations on most arguments. First, it checks whether any of the arguments is wrapped with Param and combines their values. It then combines them over group_configs, if provided. Before execution, it additionally processes the group config using pre_group_func.

It then resolves the date ranges, either using the ready-to-use index_ranges or by passing all the arguments ranging from every to jitted to ArrayWrapper.get_index_ranges(). The optimization function optimize_func is then called on each date range by first substituting any templates found in *args and **kwargs. To forward any reserved arguments such as jitted to the optimization function, specify their names in forward_args and forward_kwargs.

Note

Make sure to use vectorbt's own templates to select the current date range (available as index_slice in the context mapping) from each array.

If jitted_loop is True, see optimize_meta_nb(). Otherwise, must take template-substituted *args and **kwargs, and return an array or dictionary with asset allocations (also empty).

Note

When jitted_loop is True and in case of multiple groups, use templates to substitute by the current group index (available as group_idx in the context mapping).

All allocations of all groups are stacked into one big 2-dim array where columns are assets and rows are allocations. Furthermore, date ranges are used to fill a record array of type AllocRanges that acts as an indexer for allocations. For example, the field col stores the group index corresponding to each allocation. Since this record array does not hold any information on assets themselves, it has its own wrapper that holds groups instead of columns, while the wrapper of the PortfolioOptimizer instance contains regular columns grouped by groups.

Usage

  • Allocate once:
>>> from vectorbtpro import *

>>> data = vbt.YFData.pull(
...     ["MSFT", "AMZN", "AAPL"],
...     start="2010-01-01",
...     end="2020-01-01"
... )
>>> close = data.get("Close")

>>> def optimize_func(df):
...     sharpe = df.mean() / df.std()
...     return sharpe / sharpe.sum()

>>> df_arg = vbt.RepEval("close.iloc[index_slice]", context=dict(close=close))
>>> pfo = vbt.PortfolioOptimizer.from_optimize_func(
...     close.vbt.wrapper,
...     optimize_func,
...     df_arg,
...     end="2015-01-01"
... )
>>> pfo.allocations
symbol                                     MSFT      AMZN      AAPL
alloc_group Date
group       2015-01-02 00:00:00+00:00  0.402459  0.309351  0.288191
  • Allocate every first date of the year:
>>> pfo = vbt.PortfolioOptimizer.from_optimize_func(
...     close.vbt.wrapper,
...     optimize_func,
...     df_arg,
...     every="AS-JAN"
... )
>>> pfo.allocations
symbol                                     MSFT      AMZN      AAPL
alloc_group Date
group       2011-01-03 00:00:00+00:00  0.480693  0.257317  0.261990
            2012-01-03 00:00:00+00:00  0.489893  0.215381  0.294727
            2013-01-02 00:00:00+00:00  0.540165  0.228755  0.231080
            2014-01-02 00:00:00+00:00  0.339649  0.273996  0.386354
            2015-01-02 00:00:00+00:00  0.350406  0.418638  0.230956
            2016-01-04 00:00:00+00:00  0.332212  0.141090  0.526698
            2017-01-03 00:00:00+00:00  0.390852  0.225379  0.383769
            2018-01-02 00:00:00+00:00  0.337711  0.317683  0.344606
            2019-01-02 00:00:00+00:00  0.411852  0.282680  0.305468
  • Specify index ranges manually:
>>> pfo = vbt.PortfolioOptimizer.from_optimize_func(
...     close.vbt.wrapper,
...     optimize_func,
...     df_arg,
...     index_ranges=[
...         (0, 30),
...         (30, 60),
...         (60, 90)
...     ]
... )
>>> pfo.allocations
symbol                                     MSFT      AMZN      AAPL
alloc_group Date
group       2010-02-16 00:00:00+00:00  0.340641  0.285897  0.373462
            2010-03-30 00:00:00+00:00  0.596392  0.206317  0.197291
            2010-05-12 00:00:00+00:00  0.437481  0.283160  0.279358
  • Test multiple combinations of one argument:
>>> pfo = vbt.PortfolioOptimizer.from_optimize_func(
...     close.vbt.wrapper,
...     optimize_func,
...     df_arg,
...     every="AS-JAN",
...     start="2015-01-01",
...     lookback_period=vbt.Param(["3MS", "6MS"])
... )
>>> pfo.allocations
symbol                                         MSFT      AMZN      AAPL
lookback_period Date
3MS             2016-01-04 00:00:00+00:00  0.282725  0.234970  0.482305
                2017-01-03 00:00:00+00:00  0.318100  0.269355  0.412545
                2018-01-02 00:00:00+00:00  0.387499  0.236432  0.376068
                2019-01-02 00:00:00+00:00  0.575464  0.254808  0.169728
6MS             2016-01-04 00:00:00+00:00  0.265035  0.198619  0.536346
                2017-01-03 00:00:00+00:00  0.314144  0.409020  0.276836
                2018-01-02 00:00:00+00:00  0.322741  0.282639  0.394621
                2019-01-02 00:00:00+00:00  0.565691  0.234760  0.199549
  • Test multiple cross-argument combinations:
>>> pfo = vbt.PortfolioOptimizer.from_optimize_func(
...     close.vbt.wrapper,
...     optimize_func,
...     df_arg,
...     every="AS-JAN",
...     group_configs=[
...         dict(start="2015-01-01"),
...         dict(start="2019-06-01", every="MS"),
...         dict(end="2014-01-01")
...     ]
... )
>>> pfo.allocations
symbol                                      MSFT      AMZN      AAPL
group_config Date
0            2016-01-04 00:00:00+00:00  0.332212  0.141090  0.526698
             2017-01-03 00:00:00+00:00  0.390852  0.225379  0.383769
             2018-01-02 00:00:00+00:00  0.337711  0.317683  0.344606
             2019-01-02 00:00:00+00:00  0.411852  0.282680  0.305468
1            2019-07-01 00:00:00+00:00  0.351461  0.327334  0.321205
             2019-08-01 00:00:00+00:00  0.418411  0.249799  0.331790
             2019-09-03 00:00:00+00:00  0.400439  0.374044  0.225517
             2019-10-01 00:00:00+00:00  0.509387  0.250497  0.240117
             2019-11-01 00:00:00+00:00  0.349983  0.469181  0.180835
             2019-12-02 00:00:00+00:00  0.260437  0.380563  0.359000
2            2012-01-03 00:00:00+00:00  0.489892  0.215381  0.294727
             2013-01-02 00:00:00+00:00  0.540165  0.228755  0.231080
             2014-01-02 00:00:00+00:00  0.339649  0.273997  0.386354
  • Use Numba-compiled loop:
>>> @njit
... def optimize_func_nb(i, from_idx, to_idx, close):
...     mean = vbt.nb.nanmean_nb(close[from_idx:to_idx])
...     std = vbt.nb.nanstd_nb(close[from_idx:to_idx])
...     sharpe = mean / std
...     return sharpe / np.sum(sharpe)

>>> pfo = vbt.PortfolioOptimizer.from_optimize_func(
...     close.vbt.wrapper,
...     optimize_func_nb,
...     np.asarray(close),
...     index_ranges=[
...         (0, 30),
...         (30, 60),
...         (60, 90)
...     ],
...     jitted_loop=True
... )
>>> pfo.allocations
symbol                         MSFT      AMZN      AAPL
Date
2010-02-17 00:00:00+00:00  0.336384  0.289598  0.374017
2010-03-31 00:00:00+00:00  0.599417  0.207158  0.193425
2010-05-13 00:00:00+00:00  0.434084  0.281246  0.284670

Hint

There is no big reason of using the Numba-compiled loop, apart from when having to rebalance many thousands of times. Usually, using a regular Python loop and a Numba-compiled optimization function suffice.


from_pypfopt class method

PortfolioOptimizer.from_pypfopt(
    wrapper=None,
    **kwargs
)

PortfolioOptimizer.from_optimize_func() applied on pypfopt_optimize().

If a wrapper is not provided, parses the wrapper from prices or returns, if provided.


from_random class method

PortfolioOptimizer.from_random(
    wrapper,
    direction='longonly',
    n=None,
    seed=None,
    **kwargs
)

Generate random allocations.

Uses PortfolioOptimizer.from_allocate_func().

Uses random_allocate_func_nb() and a Numba-compiled loop.


from_riskfolio class method

PortfolioOptimizer.from_riskfolio(
    returns,
    wrapper=None,
    **kwargs
)

PortfolioOptimizer.from_optimize_func() applied on Riskfolio-Lib.


from_uniform class method

PortfolioOptimizer.from_uniform(
    wrapper,
    **kwargs
)

Generate uniform allocations.

Uses PortfolioOptimizer.from_allocate_func().


from_universal_algo class method

PortfolioOptimizer.from_universal_algo(
    algo,
    S=None,
    n_jobs=1,
    log_progress=False,
    valid_only=True,
    nonzero_only=True,
    unique_only=True,
    wrapper=None,
    **kwargs
)

Generate allocations using Universal Portfolios.

S can be any price, while algo must be either an attribute of the package, subclass of universal.algo.Algo, instance of universal.algo.Algo, or instance of universal.result.AlgoResult.

Extracts allocation points using get_alloc_points_nb().


get_allocations method

PortfolioOptimizer.get_allocations(
    squeeze_groups=True
)

Get a DataFrame with allocation groups concatenated along the index axis.


indexing_func method

PortfolioOptimizer.indexing_func(
    *args,
    wrapper_meta=None,
    alloc_wrapper_meta=None,
    alloc_records_meta=None,
    **kwargs
)

Perform indexing on PortfolioOptimizer.


mean_allocation property

Get the mean allocation per column.


metrics class variable

Metrics supported by PortfolioOptimizer.

HybridConfig(
    start_index=dict(
        title='Start Index',
        calc_func=<function PortfolioOptimizer.<lambda> at 0x1647842c0>,
        agg_func=None,
        tags='wrapper'
    ),
    end_index=dict(
        title='End Index',
        calc_func=<function PortfolioOptimizer.<lambda> at 0x164784360>,
        agg_func=None,
        tags='wrapper'
    ),
    total_duration=dict(
        title='Total Duration',
        calc_func=<function PortfolioOptimizer.<lambda> at 0x164784400>,
        apply_to_timedelta=True,
        agg_func=None,
        tags='wrapper'
    ),
    total_records=dict(
        title='Total Records',
        calc_func='alloc_records.count',
        tags='alloc_records'
    ),
    coverage=dict(
        title='Coverage',
        calc_func='alloc_records.get_coverage',
        overlapping=False,
        check_alloc_ranges=True,
        tags=[
            'alloc_ranges',
            'coverage'
        ]
    ),
    overlap_coverage=dict(
        title='Overlap Coverage',
        calc_func='alloc_records.get_coverage',
        overlapping=True,
        check_alloc_ranges=True,
        tags=[
            'alloc_ranges',
            'coverage'
        ]
    ),
    mean_allocation=dict(
        title='Mean Allocation',
        calc_func='mean_allocation',
        post_calc_func=<function PortfolioOptimizer.<lambda> at 0x1647844a0>,
        tags='allocations'
    )
)

Returns PortfolioOptimizer._metrics, which gets (hybrid-) copied upon creation of each instance. Thus, changing this config won't affect the class.

To change metrics, you can either change the config in-place, override this property, or overwrite the instance variable PortfolioOptimizer._metrics.


plot method

PortfolioOptimizer.plot(
    column=None,
    dropna='head',
    line_shape='hv',
    plot_rb_dates=None,
    trace_kwargs=None,
    add_shape_kwargs=None,
    add_trace_kwargs=None,
    fig=None,
    **layout_kwargs
)

Plot allocations.

Args

column : str
Name of the allocation group to plot.
dropna : int
See PortfolioOptimizer.fill_allocations().
line_shape : str
Line shape.
plot_rb_dates : bool

Whether to plot rebalancing dates.

Defaults to True if there are no more than 20 rebalancing dates.

trace_kwargs : dict
Keyword arguments passed to plotly.graph_objects.Scatter.
add_shape_kwargs : dict
Keyword arguments passed to fig.add_shape for rebalancing dates.
add_trace_kwargs : dict
Keyword arguments passed to add_trace.
fig : Figure or FigureWidget
Figure to add traces to.
**layout_kwargs
Keyword arguments for layout.

Usage

>>> from vectorbtpro import *

>>> pfo = vbt.PortfolioOptimizer.from_random(
...     vbt.ArrayWrapper(
...         index=pd.date_range("2020-01-01", "2021-01-01"),
...         columns=["MSFT", "AMZN", "AAPL"],
...         ndim=2
...     ),
...     every="MS",
...     seed=40
... )
>>> pfo.plot().show()


plots_defaults property

Defaults for PlotsBuilderMixin.plots().

Merges PlotsBuilderMixin.plots_defaults and plots from pfopt.


resample method

PortfolioOptimizer.resample(
    *args,
    **kwargs
)

Perform resampling on PortfolioOptimizer.


row_stack class method

PortfolioOptimizer.row_stack(
    *objs,
    wrapper_kwargs=None,
    **kwargs
)

Stack multiple PortfolioOptimizer instances along rows.

Uses ArrayWrapper.row_stack() to stack the wrappers.


run_allocation_group class method

PortfolioOptimizer.run_allocation_group(
    wrapper,
    group_configs,
    group_index,
    group_idx,
    pre_group_func=None
)

Run an allocation group.


run_optimization_group class method

PortfolioOptimizer.run_optimization_group(
    wrapper,
    group_configs,
    group_index,
    group_idx,
    pre_group_func=None,
    silence_warnings=False
)

Run an optimization group.


simulate method

PortfolioOptimizer.simulate(
    close,
    **kwargs
)

Run Portfolio.from_optimizer() on this instance.


stats_defaults property

Defaults for StatsBuilderMixin.stats().

Merges StatsBuilderMixin.stats_defaults and stats from pfopt.


subplots class variable

Subplots supported by PortfolioOptimizer.

HybridConfig(
    alloc_ranges=dict(
        title='Allocation Ranges',
        plot_func='alloc_records.plot',
        check_alloc_ranges=True,
        tags='alloc_ranges'
    ),
    plot=dict(
        title='Allocations',
        plot_func='plot',
        tags='allocations'
    )
)

Returns PortfolioOptimizer._subplots, which gets (hybrid-) copied upon creation of each instance. Thus, changing this config won't affect the class.

To change subplots, you can either change the config in-place, override this property, or overwrite the instance variable PortfolioOptimizer._subplots.


pfopt_func_dict class

pfopt_func_dict(
    *args,
    **kwargs
)

Dict that contains optimization functions as keys.

Keys can be functions themselves, their names, or _def for the default value.

Superclasses

Inherited members