Okama: Investment Portfolio Analysis & Optimization

repository·master·Indexed 43 days ago

https://github.com/mbk-dev/okama

A Python library for quantitative finance, featuring Markowitz Mean-Variance Analysis, Monte Carlo simulations, and multi-period rebalancing. Okama provides tools for portfolio optimization via Efficient Frontiers, Discounted Cash Flow (DCF) modeling, and risk metrics (VaR, CVaR). It includes built-in access to historical market data for stocks, ETFs, mutual funds, currencies, and macroeconomic indicators for the USA, UK, EU, Russia, and Israel.

Tokens
35.7K
Snippets
106
Records
150
Agent score
55%

What's inside Okama

  1. Overview of Okama features

    master

    Okama is a Python library for investment portfolio analysis and optimization using quantitative finance concepts.

    Key Capabilities:

    • Optimization: Constrained Markowitz Mean-Variance Analysis (MVA) and multi-period Efficient Frontier optimization with rebalancing constraints.
    • Simulation & Risk: Monte Carlo simulations, DCF (Discounted Cash Flow) with contribution/withdrawal flows, and risk metrics (VaR, CVaR, semideviation, variance, drawdowns).
    • Forecasting: Models based on normal, lognormal, and Student's t distributions, including distribution fitting.
    • Analysis: Backtesting asset/index performance, tracking ETF performance against benchmarks, and dividend yield indicators.
    • Visualization: Matplotlib-based plots for Efficient Frontier, Transition Maps, and risk/return performance.
  2. Overview of the local_name Label Support Implementation Plan

    master

    The local_name implementation plan aims to expose the local_name API (the native-language security name) on the Asset object. This allows AssetList, Portfolio, and EfficientFrontier to use native names as labels in charts and tables.

    Key Architectural Components:

    • Asset.local_name: A nullable field that stores the native-language security name.
    • Asset.info: Stores the raw payload associated with the asset.
    • ListMaker.local_names: A parallel map built alongside standard names that includes a fallback mechanism (using the standard name if local_name is unavailable).
    • _asset_labels(mode): A consistent resolver used by all plotting and reporting sites to determine which label to display.
    • Legacy Compatibility: Existing parameters like tickers, ticker_names, and labels_are_tickers are mapped to the new label-mode vocabulary to ensure backward compatibility.
  3. Validation rules for FinPlan and FinPlanStage

    master

    When constructing financial plans, okama enforces several constraints to ensure simulation integrity:

    FinPlanStage Constraints:

    • period must be an integer and must be at least 1.
    • distribution must be one of ('norm', 'lognorm', 't').
    • cashflow_parameters must be a CashFlow instance whose parent is the same as the stage's portfolio.

    FinPlan Constraints:

    • stages cannot be empty (must contain at least one stage).
    • All portfolios within the provided stages must have the same currency.
    • initial_investment must be greater than 0.
  4. Correct cash flow recursion for periods with extra flows

    master

    When using time_series to inject extra cash flows into a simulation, Okama applies a uniform recursion for every month in the period:

    balance = balance * (1 + r) + cash_flow

    This ensures that the first month of a period containing an extra cash flow still earns its return, and that the internal balance tracking correctly includes both the return and the extra flow. This is critical for sizing subsequent percentage-based withdrawals correctly.

  5. How tracking error methods (rms vs std) differ

    master

    Okama provides two ways to calculate ex-post tracking error, which differ in how they treat the mean of the return differences:

    1. RMS (method="rms"): This is the uncentered root-mean-square of the differences. It does not subtract the mean of the differences from each observation. Consequently, if there is a systematic lag or bias between the asset and the benchmark (a non-zero tracking difference), it is captured in the result.

    2. STD (method="std"): This is the centered sample standard deviation of the differences (using Bessel's correction, ddof=1). It measures the pure volatility of the deviations from the benchmark, effectively ignoring the systematic tracking difference (the mean deviation).

    Both methods are annualized for monthly time series by multiplying by sqrt(12).

  6. Understand the Monte Carlo Cash-Flow Engine Architecture

    master

    The Monte Carlo (MC) cash-flow engine in Okama is designed to provide vectorized performance for financial simulations. It consists of a shared core simulation method, _simulate_paths_mc(ror, params, discount_rate), which returns both wealth and cash_flow data.

    This core is accessed via two thin public wrappers:

    1. monte_carlo_cash_flow: Returns the cash flow time series.
    2. monte_carlo_irr: Returns the Internal Rate of Return (IRR) calculations.

    Both methods utilize existing DCF-level caches (PortfolioDCF._monte_carlo_wealth_fv and ._monte_carlo_cash_flow_fv) to avoid redundant computations. These caches are automatically cleared when updating cash flow strategies or Monte Carlo parameters.

  7. Understand stage boundaries and the 'Golden Invariant' in DCF simulations

    master

    In okama, a multi-stage financial plan is constructed by chaining pure engine simulations over slices of a return matrix.

    The Golden Invariant

    For monthly cash flows, a plan drawn from two consecutive slices of the same return matrix (using initial_balance to connect them) should be mathematically equivalent to a single continuous run over the full matrix.

    Exceptions and Semantic Choices

    • Periodic Cash Flows: If a stage boundary falls inside a calendar year (e.g., a yearly withdrawal strategy where the stage ends in June), the cash flow for that year is pro-rated across the boundary. The two halves will sum to the full period's amount, but the raw trajectories will differ slightly from a single continuous run.
    • CWD (Cut Withdrawals If Drawdown): A new stage implies a new portfolio context. For CutWithdrawalsIfDrawdown, the drawdown peak resets at a stage boundary. A deep drawdown in Stage 1 does not reduce the withdrawal amount in Stage 2 if Stage 2 starts with flat returns.
    • VDS (Vanguard Dynamic Spending): Floor and ceiling constraints for VDS do not bind on a stage's first period; they are evaluated based on the state at the start of the stage.
  8. Available financial and macroeconomic data

    master

    Okama provides access to free historical market data and macroeconomic indicators via its API.

    End-of-day historical data includes:

    • Stocks and ETFs for major world markets
    • Mutual funds
    • Commodities
    • Currencies
    • Stock indexes

    Macroeconomic indicators (available for USA, UK, EU, Russia, and Israel):

    • Inflation
    • Central bank rates
    • CAPE10 (Shiller P/E) ratios

    Other data:

    • Real estate prices
    • Top bank rates
  9. Understand the performance design of `PortfolioDCF.find_the_largest_withdrawals_size`

    master

    The method PortfolioDCF.find_the_largest_withdrawals_size is designed to find the maximum withdrawal amount (or percentage) that satisfies a specific financial goal (e.g., maintain_balance_pv, maintain_balance_fv, or survival_period) at a given Monte Carlo percentile.

    To optimize performance, the system uses a tiered dispatch mechanism based on the _withdrawal_structure class attribute of the provided cash flow strategy:

    1. Linear Strategies (IndexationStrategy, CWD): Uses structural shortcut solvers that require zero or minimal search iterations by exploiting the linear relationship between withdrawal amount and wealth.
    2. Multiplicative Strategies (PercentageStrategy without extra time_series cash flows): Uses a fast re-evaluation solver based on vectorized cumulative products.
    3. Generic Path (All other strategies, including VanguardDynamicSpendingStrategy or PercentageStrategy with extra cash flows): Uses Brent root-finding on a signed residual, powered by a vectorized Monte Carlo wealth engine.

    This architecture ensures that even complex, non-linear strategies benefit from the vectorized engine, while simpler strategies can bypass expensive simulations entirely.

  10. How VanguardDynamicSpending (VDS) withdrawal limits work

    master

    The VanguardDynamicSpending strategy supports floor_ceiling limits to bound withdrawals.

    When floor_ceiling is set (e.g., (0.0, 0.0) to force constant withdrawals), the withdrawal size is calculated based on the previous period's withdrawal amount, adjusted by indexation and adjust_floor_ceiling.

    Important: In recent versions, these limits are correctly applied within wealth-index calculations, ensuring that the last_withdrawal state is tracked across periods so that floor/ceiling bounds remain active.

    vds = ok.VanguardDynamicSpending(
        pf_single,
        initial_investment=10_000,
        percentage=-0.10,
        floor_ceiling=(0.0, 0.0),
        adjust_floor_ceiling=False,
        indexation=0.0,
    )
  11. How vectorized negative-value masking works

    master

    In Monte Carlo simulations, a path might hit a zero or negative balance (a 'void'). Okama provides a vectorized way to handle this using zero_wealth_after_first_void.

    Instead of processing each path individually, this function scans the entire wealth DataFrame. For every column (path), it identifies the first non-positive value and replaces it, and every subsequent value in that column, with 0.0. This ensures that once a portfolio is 'voided', it stays at zero for the remainder of the simulation horizon.

    # Internal logic used by monte_carlo_wealth(include_negative_values=False)
    from okama.portfolios import dcf_calculations
    
    # wealth is a pd.DataFrame of Monte Carlo paths
    masked_wealth = dcf_calculations.zero_wealth_after_first_void(wealth)