%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%22jquantstats%22%2C%0A%23%20%20%20%20%20%22yfinance%3E%3D0.2.0%22%2C%0A%23%20%20%20%20%20%22polars%3E%3D1.0.0%22%2C%0A%23%20%20%20%20%20%22plotly%3E%3D6.0.0%22%2C%0A%23%20%20%20%20%20%22jinja2%3E%3D3.1.0%22%2C%0A%23%20%5D%0A%23%20%5Btool.uv.sources%5D%0A%23%20jquantstats%20%3D%20%7B%20path%20%3D%20%22..%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%20os%0A%20%20%20%20from%20datetime%20import%20date%2C%20timedelta%0A%20%20%20%20from%20pathlib%20import%20Path%0A%0A%20%20%20%20import%20marimo%20as%20mo%0A%20%20%20%20import%20polars%20as%20pl%0A%20%20%20%20import%20yfinance%20as%20yf%0A%0A%20%20%20%20from%20jquantstats%20import%20Portfolio%0A%0A%20%20%20%20SHARES%20%3D%20500%0A%20%20%20%20TICKERS%20%3D%20%5B%22META%22%2C%20%22AAPL%22%5D%0A%20%20%20%20END_DATE%20%3D%20date.today()%0A%20%20%20%20START_DATE%20%3D%20END_DATE%20-%20timedelta(days%3D5%20*%20365)%0A%20%20%20%20NOTEBOOK_DIR%20%3D%20Path(__file__).parent%0A%0A%0A%40app.cell%0Adef%20cell_intro()%3A%0A%20%20%20%20%22%22%22Render%20the%20introduction.%22%22%22%0A%20%20%20%20mo.md(%0A%20%20%20%20%20%20%20%20rf%22%22%22%0A%20%20%20%20%20%20%20%20%23%20%F0%9F%93%88%20yfinance%20Portfolio%20Demo%0A%0A%20%20%20%20%20%20%20%20This%20notebook%20loads%20**%7BTICKERS%5B0%5D%7D**%20and%20**%7BTICKERS%5B1%5D%7D**%20closing%20prices%20from%20Yahoo%20Finance%0A%20%20%20%20%20%20%20%20for%20the%20last%205%20years%2C%20allocates%20**%7BSHARES%3A%2C%7D%20shares**%20in%20each%2C%20and%20produces%20a%0A%20%20%20%20%20%20%20%20full%20%60jquantstats%60%20portfolio%20report.%0A%0A%20%20%20%20%20%20%20%20%7C%20Parameter%20%7C%20Value%20%7C%0A%20%20%20%20%20%20%20%20%7C-----------%7C-------%7C%0A%20%20%20%20%20%20%20%20%7C%20Tickers%20%7C%20%60%7B%22%2C%20%22.join(TICKERS)%7D%60%20%7C%0A%20%20%20%20%20%20%20%20%7C%20Shares%20per%20asset%20%7C%20%60%7BSHARES%3A%2C%7D%60%20%7C%0A%20%20%20%20%20%20%20%20%7C%20Start%20date%20%7C%20%60%7BSTART_DATE%7D%60%20%7C%0A%20%20%20%20%20%20%20%20%7C%20End%20date%20%7C%20%60%7BEND_DATE%7D%60%20%7C%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_load_prices()%3A%0A%20%20%20%20%22%22%22Download%20adjusted%20closing%20prices%20from%20Yahoo%20Finance.%22%22%22%0A%20%20%20%20raw%20%3D%20yf.download(%0A%20%20%20%20%20%20%20%20TICKERS%2C%0A%20%20%20%20%20%20%20%20start%3DSTART_DATE.isoformat()%2C%0A%20%20%20%20%20%20%20%20end%3DEND_DATE.isoformat()%2C%0A%20%20%20%20%20%20%20%20auto_adjust%3DTrue%2C%0A%20%20%20%20%20%20%20%20progress%3DFalse%2C%0A%20%20%20%20)%0A%20%20%20%20%23%20yfinance%20returns%20a%20MultiIndex%20DataFrame%3B%20pull%20the%20Close%20level%0A%20%20%20%20close%20%3D%20raw%5B%22Close%22%5D%5BTICKERS%5D.dropna()%0A%0A%20%20%20%20%23%20Build%20Polars%20DataFrame%20without%20pyarrow%20by%20passing%20a%20plain%20dict%0A%20%20%20%20prices%20%3D%20pl.DataFrame(%0A%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%22date%22%3A%20%5Bd.date()%20for%20d%20in%20close.index.to_pydatetime()%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20**%7Bticker%3A%20close%5Bticker%5D.tolist()%20for%20ticker%20in%20TICKERS%7D%2C%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20)%0A%0A%20%20%20%20mo.md(%0A%20%20%20%20%20%20%20%20f%22%22%22%0A%20%20%20%20%20%20%20%20**Prices%20loaded**%20%E2%9C%85%0A%0A%20%20%20%20%20%20%20%20-%20Tickers%3A%20%60%7BTICKERS%7D%60%0A%20%20%20%20%20%20%20%20-%20Rows%3A%20%60%7Bprices.height%3A%2C%7D%60%0A%20%20%20%20%20%20%20%20-%20Date%20range%3A%20%60%7Bprices%5B%22date%22%5D.min()%7D%60%20%E2%86%92%20%60%7Bprices%5B%22date%22%5D.max()%7D%60%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20)%0A%20%20%20%20return%20(prices%2C)%0A%0A%0A%40app.cell%0Adef%20cell_build_positions(prices)%3A%0A%20%20%20%20%22%22%22Build%20a%20constant%20share%20position%20of%20SHARES%20units%20per%20asset.%22%22%22%0A%20%20%20%20n%20%3D%20prices.height%0A%20%20%20%20positions%20%3D%20prices.select(%22date%22).with_columns(%5Bpl.lit(float(SHARES)).alias(ticker)%20for%20ticker%20in%20TICKERS%5D)%0A%20%20%20%20mo.md(%0A%20%20%20%20%20%20%20%20f%22%22%22%0A%20%20%20%20%20%20%20%20**Positions%20constructed**%20%E2%9C%85%0A%0A%20%20%20%20%20%20%20%20-%20%60%7BSHARES%3A%2C%7D%60%20shares%20held%20in%20each%20of%20%60%7BTICKERS%7D%60%20for%20every%20trading%20day%0A%20%20%20%20%20%20%20%20-%20Total%20rows%3A%20%60%7Bn%3A%2C%7D%60%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20)%0A%20%20%20%20return%20(positions%2C)%0A%0A%0A%40app.cell%0Adef%20cell_build_portfolio(positions%2C%20prices)%3A%0A%20%20%20%20%22%22%22Create%20the%20Portfolio%20from%20share%20positions.%22%22%22%0A%20%20%20%20%23%20AUM%20%3D%20initial%20market%20value%20of%20the%20positions%0A%20%20%20%20first_row%20%3D%20prices.filter(pl.col(%22date%22)%20%3D%3D%20prices%5B%22date%22%5D.min())%0A%20%20%20%20aum%20%3D%20sum(first_row%5Bticker%5D%5B0%5D%20*%20SHARES%20for%20ticker%20in%20TICKERS)%0A%0A%20%20%20%20portfolio%20%3D%20Portfolio.from_position(%0A%20%20%20%20%20%20%20%20prices%3Dprices%2C%0A%20%20%20%20%20%20%20%20position%3Dpositions%2C%0A%20%20%20%20%20%20%20%20aum%3Dfloat(aum)%2C%0A%20%20%20%20)%0A%20%20%20%20mo.md(%0A%20%20%20%20%20%20%20%20f%22%22%22%0A%20%20%20%20%20%20%20%20**Portfolio%20created**%20%E2%9C%85%0A%0A%20%20%20%20%20%20%20%20-%20Assets%3A%20%60%7Bportfolio.assets%7D%60%0A%20%20%20%20%20%20%20%20-%20Initial%20AUM%3A%20%60%24%7Baum%3A%2C.0f%7D%60%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20)%0A%20%20%20%20return%20(portfolio%2C)%0A%0A%0A%40app.cell%0Adef%20cell_snapshot(portfolio)%3A%0A%20%20%20%20%22%22%22Render%20the%20portfolio%20snapshot%20chart.%22%22%22%0A%20%20%20%20mo.md(%22%23%23%20%F0%9F%93%8A%20Portfolio%20Snapshot%22)%0A%20%20%20%20fig%20%3D%20portfolio.plots.snapshot()%0A%20%20%20%20return%20(fig%2C)%0A%0A%0A%40app.cell%0Adef%20cell_snapshot_show(fig)%3A%0A%20%20%20%20%22%22%22Display%20snapshot%20figure.%22%22%22%0A%20%20%20%20fig%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20cell_report(portfolio)%3A%0A%20%20%20%20%22%22%22Generate%20the%20full%20HTML%20portfolio%20report.%22%22%22%0A%20%20%20%20mo.md(%22%23%23%20%F0%9F%93%8B%20HTML%20Report%22)%0A%20%20%20%20html%20%3D%20portfolio.report.to_html(title%3D%22META%20%2B%20AAPL%20%E2%80%94%20500%20Shares%20Each%22)%0A%20%20%20%20mo.md(f%22Report%20generated%3A%20**%7Blen(html)%3A%2C%7D**%20characters.%22)%0A%20%20%20%20return%20(html%2C)%0A%0A%0A%40app.cell%0Adef%20cell_report_export(html)%3A%0A%20%20%20%20%22%22%22Save%20the%20report%20to%20NOTEBOOK_OUTPUT_FOLDER%20if%20set%2C%20otherwise%20skip.%22%22%22%0A%20%20%20%20_output_folder%20%3D%20os.environ.get(%22NOTEBOOK_OUTPUT_FOLDER%22)%0A%20%20%20%20if%20_output_folder%3A%0A%20%20%20%20%20%20%20%20_path%20%3D%20Path(_output_folder)%20%2F%20%22yfinance_report.html%22%0A%20%20%20%20%20%20%20%20_path.write_text(html)%0A%20%20%20%20%20%20%20%20mo.md(f%22%E2%9C%85%20Report%20saved%20to%20%60%7B_path%7D%60%22)%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20mo.md(%22%E2%84%B9%EF%B8%8F%20%60NOTEBOOK_OUTPUT_FOLDER%60%20is%20not%20set%20%E2%80%94%20artifact%20saving%20skipped%20(set%20automatically%20by%20%60rhiza_marimo%60).%22)%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
420700ebf8a4fac877eed3a1ce232cae