%23%20%2F%2F%2F%20script%0A%23%20requires-python%20%3D%20%22%3E%3D3.11%22%0A%23%20dependencies%20%3D%20%5B%0A%23%20%20%20%20%20%22marimo%3D%3D0.20.4%22%2C%0A%23%20%20%20%20%20%22basanos%22%2C%0A%23%20%20%20%20%20%22numpy%3E%3D2.0.0%22%2C%0A%23%20%20%20%20%20%22plotly%3E%3D6.0.0%22%2C%0A%23%20%5D%0A%23%20%5Btool.uv.sources%5D%0A%23%20basanos%20%3D%20%7B%20path%20%3D%20%22..%2F..%2F..%22%2C%20editable%20%3D%20true%20%7D%0A%23%20%2F%2F%2F%0A%0Aimport%20marimo%0A%0A__generated_with%20%3D%20%220.20.4%22%0Aapp%20%3D%20marimo.App(width%3D%22medium%22)%0A%0Awith%20app.setup%3A%0A%20%20%20%20import%20marimo%20as%20mo%0A%20%20%20%20import%20numpy%20as%20np%0A%20%20%20%20import%20plotly.graph_objects%20as%20go%0A%20%20%20%20from%20plotly.subplots%20import%20make_subplots%0A%0A%20%20%20%20from%20basanos.math%20import%20FactorModel%0A%0A%0A%40app.cell%0Adef%20cell_01()%3A%0A%20%20%20%20%22%22%22Render%20the%20factor%20model%20guide%20introduction.%22%22%22%0A%20%20%20%20mo.md(%0A%20%20%20%20%20%20%20%20r%22%22%22%0A%20%20%20%20%20%20%20%20%23%20%F0%9F%A7%AE%20Basanos%20%E2%80%94%20Factor%20Risk%20Model%20Guide%0A%0A%20%20%20%20%20%20%20%20The%20**%60FactorModel%60**%20class%20encapsulates%20a%20*factor%20risk%20model*%20decomposition%0A%20%20%20%20%20%20%20%20that%20expresses%20an%20asset%20covariance%20matrix%20as%20a%20low-rank%20systematic%20component%0A%20%20%20%20%20%20%20%20plus%20a%20diagonal%20idiosyncratic%20term%3A%0A%0A%20%20%20%20%20%20%20%20%24%24%5Cbm%7B%5CSigma%7D%20%3D%20%5Cmathbf%7BB%7D%5Cmathbf%7BF%7D%5Cmathbf%7BB%7D%5E%5Ctop%20%2B%20%5Cmathbf%7BD%7D%24%24%0A%0A%20%20%20%20%20%20%20%20where%0A%0A%20%20%20%20%20%20%20%20-%20%24%5Cmathbf%7BB%7D%20%5Cin%20%5Cmathbb%7BR%7D%5E%7Bn%20%5Ctimes%20k%7D%24%20%E2%80%94%20**factor%20loading%20matrix**%3A%20each%20column%0A%20%20%20%20%20%20%20%20%20%20gives%20the%20sensitivity%20of%20all%20%24n%24%20assets%20to%20one%20latent%20factor.%0A%20%20%20%20%20%20%20%20-%20%24%5Cmathbf%7BF%7D%20%5Cin%20%5Cmathbb%7BR%7D%5E%7Bk%20%5Ctimes%20k%7D%24%20%E2%80%94%20**factor%20covariance%20matrix**%20(positive%0A%20%20%20%20%20%20%20%20%20%20definite)%3A%20how%20the%20%24k%24%20factors%20co-vary.%0A%20%20%20%20%20%20%20%20-%20%24%5Cmathbf%7BD%7D%20%3D%20%5Coperatorname%7Bdiag%7D(d_1%2C%20%5Cdots%2C%20d_n)%24%2C%20%24d_i%20%3E%200%24%20%E2%80%94%20**idiosyncratic%0A%20%20%20%20%20%20%20%20%20%20variance**%3A%20asset-specific%20risk%20*unexplained*%20by%20the%20common%20factors.%0A%0A%20%20%20%20%20%20%20%20The%20key%20assumption%20is%20%24k%20%5Cll%20n%24%3A%20dominant%20risk%20sources%20are%20captured%20by%20a%20handful%0A%20%20%20%20%20%20%20%20of%20factors%2C%20making%20the%20model%20both%20interpretable%20and%20computationally%20efficient.%0A%0A%20%20%20%20%20%20%20%20%23%23%20What%20this%20notebook%20covers%0A%0A%20%20%20%20%20%20%20%201.%20%F0%9F%8F%97%EF%B8%8F%20**Direct%20construction**%20%E2%80%94%20build%20a%20%60FactorModel%60%20from%20explicit%20arrays%0A%20%20%20%20%20%20%20%202.%20%F0%9F%93%90%20**Properties**%20%E2%80%94%20%60n_assets%60%2C%20%60n_factors%60%2C%20and%20reconstructed%20%60covariance%60%0A%20%20%20%20%20%20%20%203.%20%F0%9F%93%88%20**Fitting%20from%20returns**%20%E2%80%94%20%60FactorModel.from_returns()%60%20via%20truncated%20SVD%0A%20%20%20%20%20%20%20%204.%20%F0%9F%8E%9A%EF%B8%8F%20**Interactive%20k%20selection**%20%E2%80%94%20visualise%20the%20singular%20value%20spectrum%20to%20pick%20%24k%24%0A%20%20%20%20%20%20%20%205.%20%F0%9F%8E%A8%20**Factor%20structure**%20%E2%80%94%20heatmap%20of%20factor%20loadings%20and%20idiosyncratic%20variances%0A%20%20%20%20%20%20%20%206.%20%F0%9F%94%8D%20**Covariance%20approximation**%20%E2%80%94%20how%20well%20the%20rank-*k*%20model%20captures%20correlations%0A%20%20%20%20%20%20%20%207.%20%E2%9A%A1%20**Woodbury%20solve**%20%E2%80%94%20efficient%20%24%5Cbm%7B%5CSigma%7D%5E%7B-1%7D%5Cmathbf%7Bb%7D%24%20without%20forming%20%24%5Cbm%7B%5CSigma%7D%24%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20cell_02()%3A%0A%20%20%20%20%22%22%22Render%20separator.%22%22%22%0A%20%20%20%20mo.md(r%22%22%22---%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20cell_03()%3A%0A%20%20%20%20%22%22%22Render%20the%20direct%20construction%20section.%22%22%22%0A%20%20%20%20mo.md(%0A%20%20%20%20%20%20%20%20r%22%22%22%0A%20%20%20%20%20%20%20%20%23%23%20%F0%9F%8F%97%EF%B8%8F%20Direct%20Construction%0A%0A%20%20%20%20%20%20%20%20A%20%60FactorModel%60%20is%20a%20**frozen%20dataclass**%20%E2%80%94%20once%20created%20its%20fields%20cannot%20be%0A%20%20%20%20%20%20%20%20mutated.%20%20Pass%20the%20three%20arrays%20explicitly%20to%20the%20constructor.%20%20The%0A%20%20%20%20%20%20%20%20%60__post_init__%60%20validator%20checks%20shape%20consistency%20and%20strict%20positivity%20of%0A%20%20%20%20%20%20%20%20the%20idiosyncratic%20variances.%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20cell_04()%3A%0A%20%20%20%20%22%22%22Demonstrate%20direct%20FactorModel%20construction.%22%22%22%0A%20%20%20%20%23%203%20assets%2C%202%20factors%20%E2%80%94%20minimal%20illustrative%20example%0A%20%20%20%20_loadings%20%3D%20np.array(%0A%20%20%20%20%20%20%20%20%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20%5B0.8%2C%200.2%5D%2C%20%20%23%20asset%201%3A%20mostly%20factor%201%0A%20%20%20%20%20%20%20%20%20%20%20%20%5B0.3%2C%200.7%5D%2C%20%20%23%20asset%202%3A%20mixed%0A%20%20%20%20%20%20%20%20%20%20%20%20%5B0.1%2C%200.9%5D%2C%20%20%23%20asset%203%3A%20mostly%20factor%202%0A%20%20%20%20%20%20%20%20%5D%0A%20%20%20%20)%0A%20%20%20%20_factor_cov%20%3D%20np.array(%5B%5B0.04%2C%200.01%5D%2C%20%5B0.01%2C%200.03%5D%5D)%20%20%23%202x2%20factor%20covariance%0A%20%20%20%20_idio_var%20%3D%20np.array(%5B0.02%2C%200.015%2C%200.025%5D)%20%20%23%20idiosyncratic%20variances%0A%0A%20%20%20%20fm_manual%20%3D%20FactorModel(%0A%20%20%20%20%20%20%20%20factor_loadings%3D_loadings%2C%0A%20%20%20%20%20%20%20%20factor_covariance%3D_factor_cov%2C%0A%20%20%20%20%20%20%20%20idiosyncratic_var%3D_idio_var%2C%0A%20%20%20%20)%0A%0A%20%20%20%20mo.callout(%0A%20%20%20%20%20%20%20%20mo.md(%0A%20%20%20%20%20%20%20%20%20%20%20%20f%22%22%22%0A%20%20%20%20%20%20%20%20%20%20%20%20**Constructed%20%60FactorModel%60**%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%7C%20Property%20%7C%20Value%20%7C%0A%20%20%20%20%20%20%20%20%20%20%20%20%7C---%7C---%7C%0A%20%20%20%20%20%20%20%20%20%20%20%20%7C%20%60n_assets%60%20%7C%20%60%7Bfm_manual.n_assets%7D%60%20%7C%0A%20%20%20%20%20%20%20%20%20%20%20%20%7C%20%60n_factors%60%20%7C%20%60%7Bfm_manual.n_factors%7D%60%20%7C%0A%20%20%20%20%20%20%20%20%20%20%20%20%7C%20%60factor_loadings.shape%60%20%7C%20%60%7Bfm_manual.factor_loadings.shape%7D%60%20%7C%0A%20%20%20%20%20%20%20%20%20%20%20%20%7C%20%60factor_covariance.shape%60%20%7C%20%60%7Bfm_manual.factor_covariance.shape%7D%60%20%7C%0A%20%20%20%20%20%20%20%20%20%20%20%20%7C%20%60idiosyncratic_var.shape%60%20%7C%20%60%7Bfm_manual.idiosyncratic_var.shape%7D%60%20%7C%0A%20%20%20%20%20%20%20%20%20%20%20%20%7C%20%60covariance.shape%60%20%7C%20%60%7Bfm_manual.covariance.shape%7D%60%20%7C%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20The%20frozen%20dataclass%20guarantees%20that%20none%20of%20these%20fields%20can%20be%0A%20%20%20%20%20%20%20%20%20%20%20%20overwritten%20after%20construction%20%E2%80%94%20immutability%20is%20enforced%20by%20Python.%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20)%2C%0A%20%20%20%20%20%20%20%20kind%3D%22success%22%2C%0A%20%20%20%20)%0A%20%20%20%20return%20(fm_manual%2C)%0A%0A%0A%40app.cell%0Adef%20cell_05(fm_manual)%3A%0A%20%20%20%20%22%22%22Show%20the%20reconstructed%20full%20covariance%20matrix.%22%22%22%0A%20%20%20%20_cov%20%3D%20fm_manual.covariance%0A%20%20%20%20_fig%20%3D%20go.Figure(%0A%20%20%20%20%20%20%20%20go.Heatmap(%0A%20%20%20%20%20%20%20%20%20%20%20%20z%3D_cov%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20x%3D%5B%22Asset%201%22%2C%20%22Asset%202%22%2C%20%22Asset%203%22%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20y%3D%5B%22Asset%201%22%2C%20%22Asset%202%22%2C%20%22Asset%203%22%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20colorscale%3D%22RdBu_r%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20zmid%3D0%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20text%3D%5B%5Bf%22%7Bv%3A.4f%7D%22%20for%20v%20in%20row%5D%20for%20row%20in%20_cov%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20texttemplate%3D%22%25%7Btext%7D%22%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20)%0A%20%20%20%20_fig.update_layout(%0A%20%20%20%20%20%20%20%20title%3D%22Reconstructed%20covariance%20matrix%20%20%CE%A3%20%3D%20BFB%E1%B5%80%20%2B%20D%20%20(manual%20example)%22%2C%0A%20%20%20%20%20%20%20%20height%3D320%2C%0A%20%20%20%20)%0A%20%20%20%20mo.vstack(%0A%20%20%20%20%20%20%20%20%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20mo.md(%22%23%23%23%20Reconstructed%20Covariance%20Matrix%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20mo.ui.plotly(_fig)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20mo.md(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20r%22*Off-diagonal%20entries%20reflect%20systematic%20co-movement%20through%20shared%20factor%20loadings.%20%20%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20r%22Diagonal%20%3D%20systematic%20variance%20%2B%20idiosyncratic%20variance.*%22%0A%20%20%20%20%20%20%20%20%20%20%20%20)%2C%0A%20%20%20%20%20%20%20%20%5D%0A%20%20%20%20)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20cell_06()%3A%0A%20%20%20%20%22%22%22Render%20separator.%22%22%22%0A%20%20%20%20mo.md(r%22%22%22---%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20cell_07()%3A%0A%20%20%20%20%22%22%22Introduce%20the%20from_returns%20section.%22%22%22%0A%20%20%20%20mo.md(%0A%20%20%20%20%20%20%20%20r%22%22%22%0A%20%20%20%20%20%20%20%20%23%23%20%F0%9F%93%88%20Fitting%20from%20Returns%3A%20%60FactorModel.from_returns()%60%0A%0A%20%20%20%20%20%20%20%20In%20practice%20the%20factor%20model%20is%20*estimated*%20from%20a%20return%20matrix%0A%20%20%20%20%20%20%20%20%24%5Cmathbf%7BR%7D%20%5Cin%20%5Cmathbb%7BR%7D%5E%7BT%20%5Ctimes%20n%7D%24%20using%20the%20**Singular%20Value%20Decomposition**%3A%0A%0A%20%20%20%20%20%20%20%20%24%24%5Cmathbf%7BR%7D%20%3D%20%5Cmathbf%7BU%7D%5Cbm%7B%5CSigma%7D%5Cmathbf%7BV%7D%5E%5Ctop%24%24%0A%0A%20%20%20%20%20%20%20%20The%20top-%24k%24%20right%20singular%20vectors%20define%20the%20factor%20loadings%2C%0A%20%20%20%20%20%20%20%20and%20the%20top-%24k%24%20singular%20values%20define%20the%20factor%20covariance%3A%0A%0A%20%20%20%20%20%20%20%20%24%24%5Cmathbf%7BB%7D%20%3D%20%5Cmathbf%7BV%7D_k%2C%20%5Cquad%20%5Cmathbf%7BF%7D%20%3D%20%5Cbm%7B%5CSigma%7D_k%5E2%20%2F%20T%2C%20%5Cquad%0A%20%20%20%20%20%20%20%20%20%20%5Chat%7Bd%7D_i%20%3D%20%5Cmax%5C!%5Cbigl(%5Chat%7B%5Csigma%7D_i%5E2%20-%20(%5Cmathbf%7BB%7D%5Cmathbf%7BF%7D%5Cmathbf%7BB%7D%5E%5Ctop)_%7Bii%7D%2C%5C%2C%20%5Cvarepsilon%5Cbigr)%24%24%0A%0A%20%20%20%20%20%20%20%20When%20the%20return%20columns%20have%20**unit%20variance**%20(i.e.%20%24%5Chat%7B%5Csigma%7D_i%5E2%20%3D%201%24%2C%0A%20%20%20%20%20%20%20%20as%20produced%20by%20volatility%20adjustment)%2C%20this%20simplifies%20to%0A%20%20%20%20%20%20%20%20%24%5Chat%7Bd%7D_i%20%3D%20%5Cmax(1%20-%20(%5Cmathbf%7BB%7D%5Cmathbf%7BF%7D%5Cmathbf%7BB%7D%5E%5Ctop)_%7Bii%7D%2C%20%5Cvarepsilon)%24%0A%20%20%20%20%20%20%20%20and%20the%20reconstructed%20covariance%20also%20has%20unit%20diagonal.%20%20The%20example%20below%0A%20%20%20%20%20%20%20%20uses%20standardised%20returns%20to%20satisfy%20this%20assumption.%0A%0A%20%20%20%20%20%20%20%20Below%20we%20generate%20**synthetic%20correlated%20returns**%20for%208%20assets%20over%20200%20days%2C%0A%20%20%20%20%20%20%20%20then%20fit%20a%20%60FactorModel%60%20and%20inspect%20the%20result.%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20cell_08()%3A%0A%20%20%20%20%22%22%22Generate%20synthetic%20correlated%20returns%20for%208%20assets.%22%22%22%0A%20%20%20%20_rng%20%3D%20np.random.default_rng(42)%0A%20%20%20%20_n_assets%20%3D%208%0A%20%20%20%20_t_len%20%3D%20200%0A%0A%20%20%20%20%23%20Build%20a%20low-rank%20true%20covariance%3A%203%20underlying%20factors%20drive%20the%20assets%0A%20%20%20%20_true_loadings%20%3D%20_rng.standard_normal((_n_assets%2C%203))%0A%20%20%20%20_true_cov%20%3D%20_true_loadings%20%40%20_true_loadings.T%20%2B%20np.diag(np.ones(_n_assets)%20*%200.5)%0A%0A%20%20%20%20%23%20Cholesky-sample%20correlated%20returns%2C%20then%20standardise%20to%20unit%20variance%0A%20%20%20%20_chol%20%3D%20np.linalg.cholesky(_true_cov)%0A%20%20%20%20_raw%20%3D%20_rng.standard_normal((_t_len%2C%20_n_assets))%20%40%20_chol.T%0A%20%20%20%20returns%20%3D%20_raw%20%2F%20_raw.std(axis%3D0%2C%20keepdims%3DTrue)%20%20%23%20unit-variance%20columns%0A%0A%20%20%20%20_asset_names%20%3D%20%5Bf%22A%7Bi%20%2B%201%7D%22%20for%20i%20in%20range(_n_assets)%5D%0A%20%20%20%20mo.callout(%0A%20%20%20%20%20%20%20%20mo.md(%0A%20%20%20%20%20%20%20%20%20%20%20%20f%22%22%22%0A%20%20%20%20%20%20%20%20%20%20%20%20**Synthetic%20return%20matrix**%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20-%20Shape%3A%20%60%7Breturns.shape%7D%60%20%E2%80%94%20%7B_t_len%7D%20days%20%C3%97%20%7B_n_assets%7D%20assets%0A%20%20%20%20%20%20%20%20%20%20%20%20-%20Column%20means%20%E2%89%88%200%2C%20column%20stds%20%E2%89%88%201%20(standardised)%0A%20%20%20%20%20%20%20%20%20%20%20%20-%20True%20underlying%20structure%3A%20**3%20latent%20factors**%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20)%2C%0A%20%20%20%20%20%20%20%20kind%3D%22info%22%2C%0A%20%20%20%20)%0A%20%20%20%20return%20(returns%2C)%0A%0A%0A%40app.cell%0Adef%20cell_09()%3A%0A%20%20%20%20%22%22%22Render%20separator.%22%22%22%0A%20%20%20%20mo.md(r%22%22%22---%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20cell_10()%3A%0A%20%20%20%20%22%22%22Introduce%20the%20singular%20value%20spectrum%20section.%22%22%22%0A%20%20%20%20mo.md(%0A%20%20%20%20%20%20%20%20r%22%22%22%0A%20%20%20%20%20%20%20%20%23%23%20%F0%9F%8E%9A%EF%B8%8F%20Choosing%20*k*%3A%20The%20Singular%20Value%20Spectrum%0A%0A%20%20%20%20%20%20%20%20A%20useful%20diagnostic%20for%20selecting%20the%20number%20of%20factors%20%24k%24%20is%20the%0A%20%20%20%20%20%20%20%20**singular%20value%20spectrum**%20of%20the%20return%20matrix.%20%20Genuine%20risk%20factors%0A%20%20%20%20%20%20%20%20show%20up%20as%20large%20singular%20values%20well%20separated%20from%20the%20bulk%20of%20the%0A%20%20%20%20%20%20%20%20spectrum%20(noise%20floor).%0A%0A%20%20%20%20%20%20%20%20Use%20the%20slider%20below%20to%20choose%20%24k%24%20and%20observe%20how%20the%20cumulative%0A%20%20%20%20%20%20%20%20explained%20variance%20changes.%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20cell_11(returns)%3A%0A%20%20%20%20%22%22%22Compute%20singular%20values%20and%20show%20the%20spectrum.%22%22%22%0A%20%20%20%20_%2C%20sv%2C%20_%20%3D%20np.linalg.svd(returns%2C%20full_matrices%3DFalse)%0A%20%20%20%20_explained%20%3D%20(sv**2)%20%2F%20(sv**2).sum()%0A%20%20%20%20_cumulative%20%3D%20np.cumsum(_explained)%0A%0A%20%20%20%20_fig%20%3D%20make_subplots(%0A%20%20%20%20%20%20%20%20rows%3D1%2C%0A%20%20%20%20%20%20%20%20cols%3D2%2C%0A%20%20%20%20%20%20%20%20subplot_titles%3D%5B%22Singular%20Value%20Spectrum%22%2C%20%22Cumulative%20Explained%20Variance%22%5D%2C%0A%20%20%20%20)%0A%20%20%20%20_fig.add_trace(%0A%20%20%20%20%20%20%20%20go.Bar(%0A%20%20%20%20%20%20%20%20%20%20%20%20x%3Dlist(range(1%2C%20len(sv)%20%2B%201))%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20y%3Dsv.tolist()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20name%3D%22Singular%20value%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20marker_color%3D%22%232980b9%22%2C%0A%20%20%20%20%20%20%20%20)%2C%0A%20%20%20%20%20%20%20%20row%3D1%2C%0A%20%20%20%20%20%20%20%20col%3D1%2C%0A%20%20%20%20)%0A%20%20%20%20_fig.add_trace(%0A%20%20%20%20%20%20%20%20go.Scatter(%0A%20%20%20%20%20%20%20%20%20%20%20%20x%3Dlist(range(1%2C%20len(_cumulative)%20%2B%201))%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20y%3D(_cumulative%20*%20100).tolist()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20mode%3D%22lines%2Bmarkers%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20name%3D%22Cumulative%20variance%20(%25)%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20line%3D%7B%22color%22%3A%20%22%23e74c3c%22%2C%20%22width%22%3A%202%7D%2C%0A%20%20%20%20%20%20%20%20)%2C%0A%20%20%20%20%20%20%20%20row%3D1%2C%0A%20%20%20%20%20%20%20%20col%3D2%2C%0A%20%20%20%20)%0A%20%20%20%20_fig.add_hline(y%3D80%2C%20line_dash%3D%22dash%22%2C%20line_color%3D%22grey%22%2C%20annotation_text%3D%2280%20%25%22%2C%20row%3D1%2C%20col%3D2)%0A%20%20%20%20_fig.update_layout(%0A%20%20%20%20%20%20%20%20height%3D380%2C%0A%20%20%20%20%20%20%20%20showlegend%3DFalse%2C%0A%20%20%20%20%20%20%20%20xaxis_title%3D%22Factor%20index%22%2C%0A%20%20%20%20%20%20%20%20xaxis2_title%3D%22Number%20of%20factors%20k%22%2C%0A%20%20%20%20%20%20%20%20yaxis_title%3D%22Singular%20value%22%2C%0A%20%20%20%20%20%20%20%20yaxis2_title%3D%22Cumulative%20explained%20variance%20(%25)%22%2C%0A%20%20%20%20)%0A%20%20%20%20mo.ui.plotly(_fig)%0A%20%20%20%20return%20(sv%2C)%0A%0A%0A%40app.cell%0Adef%20cell_12()%3A%0A%20%20%20%20%22%22%22Create%20slider%20for%20k%20selection.%22%22%22%0A%20%20%20%20k_slider%20%3D%20mo.ui.slider(%0A%20%20%20%20%20%20%20%20start%3D1%2C%0A%20%20%20%20%20%20%20%20stop%3D8%2C%0A%20%20%20%20%20%20%20%20value%3D3%2C%0A%20%20%20%20%20%20%20%20step%3D1%2C%0A%20%20%20%20%20%20%20%20label%3D%22Number%20of%20factors%20k%3A%22%2C%0A%20%20%20%20%20%20%20%20show_value%3DTrue%2C%0A%20%20%20%20)%0A%20%20%20%20mo.vstack(%5Bk_slider%5D)%0A%20%20%20%20return%20(k_slider%2C)%0A%0A%0A%40app.cell%0Adef%20cell_13(k_slider%2C%20returns%2C%20sv)%3A%0A%20%20%20%20%22%22%22Fit%20FactorModel%20with%20chosen%20k%20and%20display%20summary.%22%22%22%0A%20%20%20%20fm%20%3D%20FactorModel.from_returns(returns%2C%20k%3Dk_slider.value)%0A%0A%20%20%20%20_explained_k%20%3D%20((sv%5B%3A%20k_slider.value%5D%20**%202).sum()%20%2F%20(sv**2).sum())%20*%20100%0A%0A%20%20%20%20mo.callout(%0A%20%20%20%20%20%20%20%20mo.md(%0A%20%20%20%20%20%20%20%20%20%20%20%20f%22%22%22%0A%20%20%20%20%20%20%20%20%20%20%20%20**Fitted%20%60FactorModel%60%20with%20k%20%3D%20%7Bk_slider.value%7D**%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%7C%20Property%20%7C%20Value%20%7C%0A%20%20%20%20%20%20%20%20%20%20%20%20%7C---%7C---%7C%0A%20%20%20%20%20%20%20%20%20%20%20%20%7C%20%60n_assets%60%20%7C%20%60%7Bfm.n_assets%7D%60%20%7C%0A%20%20%20%20%20%20%20%20%20%20%20%20%7C%20%60n_factors%60%20%7C%20%60%7Bfm.n_factors%7D%60%20%7C%0A%20%20%20%20%20%20%20%20%20%20%20%20%7C%20%60factor_loadings.shape%60%20%7C%20%60%7Bfm.factor_loadings.shape%7D%60%20%7C%0A%20%20%20%20%20%20%20%20%20%20%20%20%7C%20%60factor_covariance.shape%60%20%7C%20%60%7Bfm.factor_covariance.shape%7D%60%20%7C%0A%20%20%20%20%20%20%20%20%20%20%20%20%7C%20Explained%20variance%20%7C%20**%7B_explained_k%3A.1f%7D%20%25**%20%7C%0A%20%20%20%20%20%20%20%20%20%20%20%20%7C%20Mean%20idiosyncratic%20var%20%7C%20%60%7Bfm.idiosyncratic_var.mean()%3A.4f%7D%60%20%7C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20)%2C%0A%20%20%20%20%20%20%20%20kind%3D%22success%22%2C%0A%20%20%20%20)%0A%20%20%20%20return%20(fm%2C)%0A%0A%0A%40app.cell%0Adef%20cell_14()%3A%0A%20%20%20%20%22%22%22Render%20separator.%22%22%22%0A%20%20%20%20mo.md(r%22%22%22---%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20cell_15()%3A%0A%20%20%20%20%22%22%22Introduce%20the%20factor%20structure%20section.%22%22%22%0A%20%20%20%20mo.md(%0A%20%20%20%20%20%20%20%20r%22%22%22%0A%20%20%20%20%20%20%20%20%23%23%20%F0%9F%8E%A8%20Factor%20Structure%0A%0A%20%20%20%20%20%20%20%20The%20two%20charts%20below%20show%20the%20**factor%20loading%20matrix**%20%24%5Cmathbf%7BB%7D%24%20and%20the%0A%20%20%20%20%20%20%20%20**idiosyncratic%20variance**%20%24%5Cmathbf%7Bd%7D%24%20for%20the%20currently%20selected%20%24k%24.%0A%0A%20%20%20%20%20%20%20%20-%20**Factor%20loadings%20heatmap**%20%E2%80%94%20each%20cell%20%24(i%2C%20j)%24%20is%20the%20sensitivity%20of%0A%20%20%20%20%20%20%20%20%20%20asset%20%24i%24%20to%20factor%20%24j%24.%20%20Large%20absolute%20values%20indicate%20the%20asset%20is%0A%20%20%20%20%20%20%20%20%20%20strongly%20driven%20by%20that%20factor.%0A%20%20%20%20%20%20%20%20-%20**Idiosyncratic%20variance**%20%E2%80%94%20the%20residual%20per-asset%20variance%20not%20captured%0A%20%20%20%20%20%20%20%20%20%20by%20the%20%24k%24%20common%20factors.%20%20A%20near-zero%20bar%20means%20the%20asset%20is%20almost%0A%20%20%20%20%20%20%20%20%20%20entirely%20explained%20by%20the%20factor%20model.%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20cell_16(fm%2C%20k_slider)%3A%0A%20%20%20%20%22%22%22Plot%20factor%20loadings%20heatmap%20and%20idiosyncratic%20variance%20bar%20chart.%22%22%22%0A%20%20%20%20_asset_labels%20%3D%20%5Bf%22A%7Bi%20%2B%201%7D%22%20for%20i%20in%20range(fm.n_assets)%5D%0A%20%20%20%20_factor_labels%20%3D%20%5Bf%22F%7Bj%20%2B%201%7D%22%20for%20j%20in%20range(fm.n_factors)%5D%0A%0A%20%20%20%20_fig%20%3D%20make_subplots(%0A%20%20%20%20%20%20%20%20rows%3D1%2C%0A%20%20%20%20%20%20%20%20cols%3D2%2C%0A%20%20%20%20%20%20%20%20subplot_titles%3D%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20f%22Factor%20Loadings%20B%20%20(n%3D%7Bfm.n_assets%7D%2C%20k%3D%7Bk_slider.value%7D)%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22Idiosyncratic%20Variance%20d%22%2C%0A%20%20%20%20%20%20%20%20%5D%2C%0A%20%20%20%20%20%20%20%20column_widths%3D%5B0.6%2C%200.4%5D%2C%0A%20%20%20%20)%0A%0A%20%20%20%20%23%20Factor%20loadings%20heatmap%0A%20%20%20%20_fig.add_trace(%0A%20%20%20%20%20%20%20%20go.Heatmap(%0A%20%20%20%20%20%20%20%20%20%20%20%20z%3Dfm.factor_loadings.tolist()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20x%3D_factor_labels%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20y%3D_asset_labels%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20colorscale%3D%22RdBu_r%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20zmid%3D0%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20text%3D%5B%5Bf%22%7Bv%3A.3f%7D%22%20for%20v%20in%20row%5D%20for%20row%20in%20fm.factor_loadings%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20texttemplate%3D%22%25%7Btext%7D%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20showscale%3DTrue%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20name%3D%22Loadings%22%2C%0A%20%20%20%20%20%20%20%20)%2C%0A%20%20%20%20%20%20%20%20row%3D1%2C%0A%20%20%20%20%20%20%20%20col%3D1%2C%0A%20%20%20%20)%0A%0A%20%20%20%20%23%20Idiosyncratic%20variance%20bar%20chart%0A%20%20%20%20_fig.add_trace(%0A%20%20%20%20%20%20%20%20go.Bar(%0A%20%20%20%20%20%20%20%20%20%20%20%20x%3D_asset_labels%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20y%3Dfm.idiosyncratic_var.tolist()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20name%3D%22Idiosyncratic%20var%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20marker_color%3D%22%238e44ad%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20text%3D%5Bf%22%7Bv%3A.4f%7D%22%20for%20v%20in%20fm.idiosyncratic_var%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20textposition%3D%22outside%22%2C%0A%20%20%20%20%20%20%20%20)%2C%0A%20%20%20%20%20%20%20%20row%3D1%2C%0A%20%20%20%20%20%20%20%20col%3D2%2C%0A%20%20%20%20)%0A%0A%20%20%20%20_fig.update_layout(%0A%20%20%20%20%20%20%20%20height%3D420%2C%0A%20%20%20%20%20%20%20%20showlegend%3DFalse%2C%0A%20%20%20%20%20%20%20%20yaxis2_title%3D%22Variance%22%2C%0A%20%20%20%20%20%20%20%20xaxis2_title%3D%22Asset%22%2C%0A%20%20%20%20)%0A%20%20%20%20mo.ui.plotly(_fig)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20cell_17()%3A%0A%20%20%20%20%22%22%22Render%20separator.%22%22%22%0A%20%20%20%20mo.md(r%22%22%22---%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20cell_18()%3A%0A%20%20%20%20%22%22%22Introduce%20the%20covariance%20approximation%20section.%22%22%22%0A%20%20%20%20mo.md(%0A%20%20%20%20%20%20%20%20r%22%22%22%0A%20%20%20%20%20%20%20%20%23%23%20%F0%9F%94%8D%20Covariance%20Approximation%20Quality%0A%0A%20%20%20%20%20%20%20%20The%20factor%20model%20provides%20a%20**rank-%24k%24%20approximation**%20of%20the%20full%20sample%0A%20%20%20%20%20%20%20%20covariance%20matrix.%20%20The%20heatmaps%20below%20compare%3A%0A%0A%20%20%20%20%20%20%20%20-%20**Sample%20correlation**%20%24%5Chat%7BC%7D%24%20%E2%80%94%20empirical%20correlations%20from%20the%20return%20matrix.%0A%20%20%20%20%20%20%20%20-%20**Factor-model%20correlation**%20%24%5Chat%7BC%7D%5E%7B(k)%7D%24%20%E2%80%94%20correlations%20implied%20by%0A%20%20%20%20%20%20%20%20%20%20%24%5Cmathbf%7BB%7D%5Cmathbf%7BF%7D%5Cmathbf%7BB%7D%5E%5Ctop%20%2B%20%5Cmathbf%7BD%7D%24%20(normalised%20to%20unit%20diagonal).%0A%20%20%20%20%20%20%20%20-%20**Approximation%20error**%20%E2%80%94%20absolute%20difference%20%24%7C%5Chat%7BC%7D%20-%20%5Chat%7BC%7D%5E%7B(k)%7D%7C%24.%0A%0A%20%20%20%20%20%20%20%20A%20small%20%24k%24%20captures%20only%20the%20dominant%20systematic%20structure%3B%20the%20off-diagonal%0A%20%20%20%20%20%20%20%20entries%20of%20the%20error%20matrix%20show%20which%20asset%20pairs%20are%20under-modelled.%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20cell_19(fm%2C%20returns)%3A%0A%20%20%20%20%22%22%22Plot%20sample%20vs%20factor-model%20correlation%20and%20approximation%20error.%22%22%22%0A%20%20%20%20_sample_cov%20%3D%20np.cov(returns.T)%0A%20%20%20%20_sample_std%20%3D%20np.sqrt(np.diag(_sample_cov))%0A%20%20%20%20_sample_corr%20%3D%20_sample_cov%20%2F%20np.outer(_sample_std%2C%20_sample_std)%0A%0A%20%20%20%20%23%20Factor-model%20implied%20correlation%20(normalised%20to%20unit%20diagonal)%0A%20%20%20%20_fm_cov%20%3D%20fm.covariance%0A%20%20%20%20_fm_std%20%3D%20np.sqrt(np.diag(_fm_cov))%0A%20%20%20%20_fm_corr%20%3D%20_fm_cov%20%2F%20np.outer(_fm_std%2C%20_fm_std)%0A%0A%20%20%20%20_err%20%3D%20np.abs(_sample_corr%20-%20_fm_corr)%0A%20%20%20%20_asset_labels%20%3D%20%5Bf%22A%7Bi%20%2B%201%7D%22%20for%20i%20in%20range(fm.n_assets)%5D%0A%0A%20%20%20%20_fig%20%3D%20make_subplots(%0A%20%20%20%20%20%20%20%20rows%3D1%2C%0A%20%20%20%20%20%20%20%20cols%3D3%2C%0A%20%20%20%20%20%20%20%20subplot_titles%3D%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20%22Sample%20Correlation%20%20%C4%88%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20f%22Factor-Model%20Correlation%20%20%C4%88%E2%81%BD%E1%B5%8F%E2%81%BE%20%20(k%3D%7Bfm.n_factors%7D)%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22Absolute%20Error%20%20%7C%C4%88%20%E2%88%92%20%C4%88%E2%81%BD%E1%B5%8F%E2%81%BE%7C%22%2C%0A%20%20%20%20%20%20%20%20%5D%2C%0A%20%20%20%20)%0A%0A%20%20%20%20for%20_col_idx%2C%20(_mat%2C%20_scale%2C%20_title)%20in%20enumerate(%0A%20%20%20%20%20%20%20%20%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20(_sample_corr%2C%20%22RdBu_r%22%2C%20%22Sample%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20(_fm_corr%2C%20%22RdBu_r%22%2C%20%22Factor%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20(_err%2C%20%22Oranges%22%2C%20%22Error%22)%2C%0A%20%20%20%20%20%20%20%20%5D%2C%0A%20%20%20%20%20%20%20%20start%3D1%2C%0A%20%20%20%20)%3A%0A%20%20%20%20%20%20%20%20_fig.add_trace(%0A%20%20%20%20%20%20%20%20%20%20%20%20go.Heatmap(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20z%3D_mat.tolist()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20x%3D_asset_labels%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20y%3D_asset_labels%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20colorscale%3D_scale%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20zmid%3D0%20if%20_scale%20%3D%3D%20%22RdBu_r%22%20else%20None%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20zmin%3D-1%20if%20_scale%20%3D%3D%20%22RdBu_r%22%20else%200%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20zmax%3D1%20if%20_scale%20%3D%3D%20%22RdBu_r%22%20else%20None%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20showscale%3DTrue%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20name%3D_title%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20row%3D1%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20col%3D_col_idx%2C%0A%20%20%20%20%20%20%20%20)%0A%0A%20%20%20%20_mae_offdiag%20%3D%20float(_err%5Bnp.triu_indices_from(_err%2C%20k%3D1)%5D.mean())%0A%20%20%20%20_fig.update_layout(%0A%20%20%20%20%20%20%20%20height%3D380%2C%0A%20%20%20%20%20%20%20%20showlegend%3DFalse%2C%0A%20%20%20%20%20%20%20%20title%3Df%22Mean%20absolute%20off-diagonal%20error%3A%20%7B_mae_offdiag%3A.4f%7D%22%2C%0A%20%20%20%20)%0A%20%20%20%20mo.ui.plotly(_fig)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20cell_20()%3A%0A%20%20%20%20%22%22%22Render%20separator.%22%22%22%0A%20%20%20%20mo.md(r%22%22%22---%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20cell_21()%3A%0A%20%20%20%20%22%22%22Introduce%20the%20Woodbury%20solve%20section.%22%22%22%0A%20%20%20%20mo.md(%0A%20%20%20%20%20%20%20%20r%22%22%22%0A%20%20%20%20%20%20%20%20%23%23%20%E2%9A%A1%20Efficient%20Solve%20via%20the%20Woodbury%20Identity%0A%0A%20%20%20%20%20%20%20%20The%20%60FactorModel.solve(b)%60%20method%20solves%20the%20linear%20system%0A%20%20%20%20%20%20%20%20%24%5Cbm%7B%5CSigma%7D%5Cmathbf%7Bx%7D%20%3D%20%5Cmathbf%7Bb%7D%24%20**without%20forming%20the%20full%0A%20%20%20%20%20%20%20%20%24n%20%5Ctimes%20n%24%20matrix**%2C%20using%20the%20Sherman%E2%80%93Morrison%E2%80%93Woodbury%20formula%3A%0A%0A%20%20%20%20%20%20%20%20%24%24(%5Cmathbf%7BD%7D%20%2B%20%5Cmathbf%7BB%7D%5Cmathbf%7BF%7D%5Cmathbf%7BB%7D%5E%5Ctop)%5E%7B-1%7D%0A%20%20%20%20%20%20%20%20%20%20%3D%20%5Cmathbf%7BD%7D%5E%7B-1%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20-%20%5Cmathbf%7BD%7D%5E%7B-1%7D%5Cmathbf%7BB%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%5Cunderbrace%7B(%5Cmathbf%7BF%7D%5E%7B-1%7D%20%2B%20%5Cmathbf%7BB%7D%5E%5Ctop%5Cmathbf%7BD%7D%5E%7B-1%7D%5Cmathbf%7BB%7D)%7D_%7Bk%20%5Ctimes%20k%7D%7B%7D%5E%7B-1%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%5Cmathbf%7BB%7D%5E%5Ctop%5Cmathbf%7BD%7D%5E%7B-1%7D%24%24%0A%0A%20%20%20%20%20%20%20%20%7C%20Step%20%7C%20Cost%20%7C%0A%20%20%20%20%20%20%20%20%7C---%7C---%7C%0A%20%20%20%20%20%20%20%20%7C%20%24%5Cmathbf%7BD%7D%5E%7B-1%7D%24%20(diagonal%20inversion)%20%7C%20%24O(n)%24%20%7C%0A%20%20%20%20%20%20%20%20%7C%20Inner%20%24k%20%5Ctimes%20k%24%20system%20%7C%20%24O(k%5E3%20%2B%20kn)%24%20%7C%0A%20%20%20%20%20%20%20%20%7C%20Total%20%7C%20%24O(k%5E3%20%2B%20kn)%24%20%20vs.%20%20%24O(n%5E3)%24%20for%20direct%20inversion%20%7C%0A%0A%20%20%20%20%20%20%20%20For%20%24k%20%5Cll%20n%24%20this%20is%20a%20substantial%20saving%20%E2%80%94%20particularly%20relevant%20when%0A%20%20%20%20%20%20%20%20%60solve%60%20is%20called%20at%20every%20timestamp%20inside%20the%20optimizer%20loop.%0A%0A%20%20%20%20%20%20%20%20The%20cell%20below%20verifies%20that%20the%20Woodbury%20result%20matches%20direct%20inversion%0A%20%20%20%20%20%20%20%20numerically.%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20cell_22(fm)%3A%0A%20%20%20%20%22%22%22Verify%20Woodbury%20solve%20matches%20direct%20inversion.%22%22%22%0A%20%20%20%20_rng_solve%20%3D%20np.random.default_rng(99)%0A%20%20%20%20_b_vec%20%3D%20_rng_solve.standard_normal(fm.n_assets)%0A%0A%20%20%20%20%23%20Woodbury%20solve%0A%20%20%20%20_x_woodbury%20%3D%20fm.solve(_b_vec)%0A%0A%20%20%20%20%23%20Direct%20solve%20via%20full%20covariance%0A%20%20%20%20_x_direct%20%3D%20np.linalg.solve(fm.covariance%2C%20_b_vec)%0A%0A%20%20%20%20_max_err%20%3D%20float(np.abs(_x_woodbury%20-%20_x_direct).max())%0A%20%20%20%20_rel_err%20%3D%20float(np.abs(_x_woodbury%20-%20_x_direct).max()%20%2F%20(np.abs(_x_direct).max()%20%2B%201e-16))%0A%20%20%20%20_residual%20%3D%20float(np.abs(fm.covariance%20%40%20_x_woodbury%20-%20_b_vec).max())%0A%0A%20%20%20%20mo.callout(%0A%20%20%20%20%20%20%20%20mo.md(%0A%20%20%20%20%20%20%20%20%20%20%20%20f%22%22%22%0A%20%20%20%20%20%20%20%20%20%20%20%20**Woodbury%20solve%20vs%20direct%20inversion**%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%7C%20Check%20%7C%20Value%20%7C%0A%20%20%20%20%20%20%20%20%20%20%20%20%7C---%7C---%7C%0A%20%20%20%20%20%20%20%20%20%20%20%20%7C%20Max%20absolute%20error%20%7C%20%60%7B_max_err%3A.2e%7D%60%20%7C%0A%20%20%20%20%20%20%20%20%20%20%20%20%7C%20Max%20relative%20error%20%7C%20%60%7B_rel_err%3A.2e%7D%60%20%7C%0A%20%20%20%20%20%20%20%20%20%20%20%20%7C%20Residual%20%20max%20%E2%80%96%CE%A3x%20%E2%88%92%20b%E2%80%96%20%7C%20%60%7B_residual%3A.2e%7D%60%20%7C%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%E2%9C%85%20Both%20solutions%20agree%20to%20machine%20precision.%0A%20%20%20%20%20%20%20%20%20%20%20%20The%20Woodbury%20result%20satisfies%20%24%5C%5Cbm%7B%7B%5C%5CSigma%7D%7D%5C%5Cmathbf%7B%7Bx%7D%7D%20%3D%20%5C%5Cmathbf%7B%7Bb%7D%7D%24%20exactly%0A%20%20%20%20%20%20%20%20%20%20%20%20(up%20to%20floating-point%20rounding).%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20)%2C%0A%20%20%20%20%20%20%20%20kind%3D%22success%22%2C%0A%20%20%20%20)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20cell_23()%3A%0A%20%20%20%20%22%22%22Render%20separator.%22%22%22%0A%20%20%20%20mo.md(r%22%22%22---%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20cell_24()%3A%0A%20%20%20%20%22%22%22Render%20the%20conclusion.%22%22%22%0A%20%20%20%20mo.md(%0A%20%20%20%20%20%20%20%20r%22%22%22%0A%20%20%20%20%20%20%20%20%23%23%20%F0%9F%8E%89%20Conclusion%0A%0A%20%20%20%20%20%20%20%20This%20notebook%20demonstrated%20the%20full%20lifecycle%20of%20the%20%60FactorModel%60%20class%3A%0A%0A%20%20%20%20%20%20%20%20%E2%9C%85%20**Direct%20construction**%20%E2%80%94%20provide%20%24%5Cmathbf%7BB%7D%24%2C%20%24%5Cmathbf%7BF%7D%24%2C%20and%20%24%5Cmathbf%7Bd%7D%24%20explicitly%3B%0A%20%20%20%20%20%20%20%20the%20frozen%20dataclass%20validates%20shape%20and%20positivity%20on%20construction.%0A%0A%20%20%20%20%20%20%20%20%E2%9C%85%20**Fitting%20from%20returns**%20%E2%80%94%20%60FactorModel.from_returns(R%2C%20k)%60%20extracts%20the%20top-%24k%24%0A%20%20%20%20%20%20%20%20SVD%20components%20to%20produce%20a%20calibrated%20low-rank%20covariance%20model.%0A%0A%20%20%20%20%20%20%20%20%E2%9C%85%20**Singular%20value%20spectrum**%20%E2%80%94%20inspect%20explained%20variance%20to%20choose%20%24k%24%20objectively%3B%0A%20%20%20%20%20%20%20%20a%20sharp%20drop%20in%20singular%20values%20signals%20where%20the%20noise%20floor%20begins.%0A%0A%20%20%20%20%20%20%20%20%E2%9C%85%20**Factor%20structure**%20%E2%80%94%20the%20loading%20heatmap%20reveals%20which%20assets%20are%20dominated%20by%20which%0A%20%20%20%20%20%20%20%20factors%3B%20idiosyncratic%20variances%20show%20what%20the%20model%20leaves%20unexplained.%0A%0A%20%20%20%20%20%20%20%20%E2%9C%85%20**Covariance%20approximation**%20%E2%80%94%20a%20rank-%24k%24%20model%20recovers%20the%20dominant%20correlation%0A%20%20%20%20%20%20%20%20structure%20with%20a%20controllable%20approximation%20error.%0A%0A%20%20%20%20%20%20%20%20%E2%9C%85%20**Woodbury%20solve**%20%E2%80%94%20%60fm.solve(b)%60%20solves%20%24%5Cbm%7B%5CSigma%7D%5Cmathbf%7Bx%7D%20%3D%20%5Cmathbf%7Bb%7D%24%20in%0A%20%20%20%20%20%20%20%20%24O(k%5E3%20%2B%20kn)%24%20time%20instead%20of%20%24O(n%5E3)%24%2C%20with%20numerically%20identical%20results.%0A%0A%20%20%20%20%20%20%20%20%23%23%23%20Next%20steps%0A%0A%20%20%20%20%20%20%20%20-%20Use%20%60FactorModel%60%20inside%20%60BasanosConfig%60%20via%20the%20%60k%60%20parameter%20to%20enable%0A%20%20%20%20%20%20%20%20%20%20factor-model%20covariance%20estimation%20in%20the%20full%20optimizer%20pipeline.%0A%20%20%20%20%20%20%20%20-%20Compare%20%60covariance_mode%3D%22pca%22%60%20(factor-model%20based)%20with%20%60%22ewma_shrink%22%60%20using%0A%20%20%20%20%20%20%20%20%20%20%60BasanosEngine.sharpe_at_pca_components()%60.%0A%20%20%20%20%20%20%20%20-%20See%20the%20%5Bbasanos%20repository%5D(https%3A%2F%2Fgithub.com%2FJebel-Quant%2Fbasanos)%20for%20the%0A%20%20%20%20%20%20%20%20%20%20full%20API%20reference%20and%20the%20%60demo%60%20notebook%20for%20an%20end-to-end%20portfolio%20example.%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20)%0A%20%20%20%20return%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20app.run()%0A
5257a1db371ac2882a1aa42b8c00bfbe