import%20marimo%0A%0A__generated_with%20%3D%20%220.17.6%22%0Aapp%20%3D%20marimo.App(width%3D%22medium%22)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%20Example%3A%20Using%20Loman%20to%20Value%20a%20Portfolio%0A%0A%20%20%20%20In%20this%20example%2C%20we'll%20look%20at%20valuing%20a%20simple%20portfolio%20composed%20of%20equities%20and%20futures.%20In%20additional%2C%20we'll%20calculate%20an%20intraday%20P%26L%2C%20and%20some%20simple%20exposure%20measures.%0A%0A%20%20%20%20The%20main%20challenge%20of%20valuing%20and%20risk-managing%20a%20multi-asset%20portfolio%20is%20that%20each%20asset%20type%20must%20be%20treated%20differently.%20Position%2C%20market%20and%20static%20data%20for%20each%20asset%20type%20must%20typically%20be%20sourced%20from%20separate%20systems%20and%20then%20integrated%20into%20a%20single%20holistic%20view.%20Loman%20excels%20at%20this%20sort%20of%20task%2C%20making%20it%20easy%20to%20add%20new%20nodes%20as%20new%20sources%20of%20data%20become%20available%2C%20or%20as%20new%20calculations%20are%20required.%0A%0A%20%20%20%20To%20simplify%20exposition%2C%20we%20have%20deliberately%20limited%20the%20number%20of%20asset%20types%20we%20treat%2C%20and%20the%20number%20of%20measures%20we%20calculate.%20However%2C%20it%20should%20be%20easy%20to%20see%20that%20the%20techniques%20applied%20here%20can%20be%20extended%20naturally%20to%20more%20complex%20portfolios.%0A%0A%20%20%20%20To%20make%20sure%20that%20this%20example%20is%20as%20widely%20accessible%20as%20possible%2C%20we%20have%20used%20publicly%20available%20data%20sources.%20For%20actual%20investing%20use%2C%20we%20strongly%20recommend%20investigating%20paid%20services%20to%20access%20higher%20quality%20data%20(and%20which%20typically%20requires%20less%20processing%20to%20use).%0A%0A%20%20%20%20This%20example%20makes%20heavy%20use%20of%20Pandas%20dataframes.%20If%20you%20haven't%20come%20across%20Pandas%20before%2C%20definitely%20take%20a%20look%3A%20http%3A%2F%2Fpandas.pydata.org%2F.%20Dataframes%20are%20an%20in-memory%20table%20object%2C%20and%20have%20gained%20popularity%20for%20many%20data-processing%20tasks.%0A%0A%20%20%20%20%23%23%20Raw%20Holdings%20Data%0A%0A%20%20%20%20Ok%2C%20let's%20get%20going!%20We%20start%2C%20as%20usual%2C%20by%20creating%20a%20Loman%20Computation%20object%2C%20which%20we%20shall%20populate%20with%20data%20and%20calculations%3A%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_()%3A%0A%20%20%20%20import%20loman%0A%0A%20%20%20%20comp%20%3D%20loman.Computation()%0A%20%20%20%20return%20comp%2C%20loman%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20The%20first%20thing%20we%20shall%20need%20is%20holdings%20data.%20For%20this%20example%2C%20we%20assume%20that%20holdings%20data%20is%20provided%20in%20a%20CSV%20format%2C%20and%20insert%20that%20CSV%20data%20into%20a%20node%20in%20the%20computation%20called%20**holdings**%20using%20the%20%60add_node%60%20method.%0A%0A%20%20%20%20The%20portfolio%20itself%20consist%20of%205%20equities%20from%20an%20arbitrary%20%5BCNN%20Money%20article%5D(http%3A%2F%2Fmoney.cnn.com%2F2016%2F12%2F28%2Finvesting%2Fstocks-to-buy-2017%2F)%2C%20together%20with%20a%20short%20S%26P%20E-mini%20future%20to%20approximately%20hedge%20overall%20market%20exposure.%20(Our%20choice%20of%20article%20should%20not%20be%20construed%20as%20a%20recommendation%20for%20or%20against%20the%20content%20of%20that%20article%2C%20or%20the%20site%20as%20a%20whole).%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(comp)%3A%0A%20%20%20%20holdings%20%3D%20%22%22%22Type%2CSymbol%2CQty%2CCostBasis%0A%20%20%20%20Equity%2CAVGO%2C126%2C22680%0A%20%20%20%20Equity%2CEVHC%2C349%2C22685%0A%20%20%20%20Equity%2CSTT%2C287%2C22673%0A%20%20%20%20Equity%2CDAL%2C454%2C22700%0A%20%20%20%20Equity%2CDY%2C283%2C22640%0A%20%20%20%20Future%2CESM7%2C-1%2C0%0A%20%20%20%20Cash%2CUSD%2C2000%2C%0A%20%20%20%20%22%22%22%0A%20%20%20%20comp.add_node(%22holdings%22%2C%20value%3Dholdings)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%20Initial%20Processing%20of%20Holdings%20Data%0A%0A%20%20%20%20We%20need%20to%20convert%20this%20CSV%20text%20data%20into%20a%20usable%20form.%0A%0A%20%20%20%20The%20%60%40loman.node%60%20decorator%20allows%20us%20to%20insert%20a%20new%20node%20into%20the%20computation%2C%20using%20the%20name%20of%20the%20function%20as%20the%20name%20of%20the%20node.%20Any%20parameters%20of%20the%20function%20that%20are%20named%20the%20same%20as%20nodes%20in%20the%20computation%20will%20take%20the%20values%20of%20those%20nodes%20when%20the%20function%20is%20called.%0A%0A%20%20%20%20Here%20we%20use%20the%20%60%40loman.node%60%20decorator%20to%20create%20a%20new%20node%2C%20called%20**df_holdings**%2C%20which%20will%20be%20calculated%20from%20the%20node%20**holdings**%20that%20we%20defined%20above.%20The%20function%20simply%20reads%20the%20CSV%20data%20into%20a%20Pandas%20dataframe.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(comp%2C%20loman)%3A%0A%20%20%20%20from%20io%20import%20StringIO%0A%0A%20%20%20%20import%20pandas%20as%20pd%0A%0A%20%20%20%20%40loman.node(comp)%0A%20%20%20%20def%20df_holdings(holdings)%3A%0A%20%20%20%20%20%20%20%20f%20%3D%20StringIO(holdings)%0A%20%20%20%20%20%20%20%20df%20%3D%20pd.read_csv(f)%0A%20%20%20%20%20%20%20%20return%20df%0A%20%20%20%20return%20(pd%2C)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20We%20can%20easily%20see%20the%20structure%20of%20a%20computation%2C%20although%20at%20this%20stage%20it%20is%20pretty%20simple.%20There%20is%20a%20node%20**holdings**%2C%20in%20dark%20green%20because%20we%20set%20its%20value%2C%20and%20so%20it%20is%20up-to-date.%20And%20there%20is%20a%20second%20node%20**df_holdings**%2C%20which%20depends%20on%20**holdings**%2C%20as%20shown%20by%20the%20arrow.%20The%20lighter%20green%20color%20tells%20us%20the%20**df_holdings**%20is%20computable%2C%20but%20not%20currently%20up-to-date%2C%20which%20we%20expect%2C%20as%20we%20have%20not%20told%20Loman%20to%20calculate%20it%20yet.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(comp)%3A%0A%20%20%20%20comp%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20So%20we%20can%20go%20ahead%20and%20calculate%20**df_holdings**%20and%20check%20that%20it%20looks%20correct.%20Computation%20objects%20have%20an%20attribute-based%20accessor%2C%20%60v%60%2C%20which%20allows%20us%20to%20see%20the%20value%20of%20any%20node.%20It%20also%20works%20great%20with%20the%20auto-complete%20in%20interactive%20environments%2C%20such%20as%20IPython%20Notebook.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(comp)%3A%0A%20%20%20%20comp.compute(%22df_holdings%22)%0A%20%20%20%20comp.v.df_holdings%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20A%20quick%20sidenote%3A%20It%20is%20the%20nature%20of%20these%20examples%20that%20I%20present%20the%20computations%20in%20their%20complete%20state%2C%20with%20no%20mention%20of%20the%20struggle%20that%20it%20took%20to%20get%20them%20there.%20That's%20a%20shame%2C%20because%20one%20of%20the%20really%20great%20things%20about%20working%20with%20Loman%20is%20that%20you%20can%20iterate%20on%20a%20single%20node%20-%20even%20one%20in%20the%20middle%20of%20a%20calculation%20-%20without%20having%20to%20recalculate%20any%20of%20the%20upstream%20or%20downstream%20nodes%20if%20you%20don't%20want%20to.%20In%20this%20case%2C%20it%20doesn't%20make%20a%20huge%20amount%20of%20difference%2C%20but%20when%20dealing%20with%20large%20data%20sets%20and%20time-consuming%20calculations%2C%20it%20can%20be%20a%20boon%20not%20to%20have%20to%20wait%20a%20couple%20of%20minutes%20to%20see%20the%20results%20of%20your%20latest%20update%20to%20an%20intermediate%20calculation.%0A%0A%20%20%20%20%23%23%20Splitting%20the%20Holdings%20by%20Asset%20Type%0A%0A%20%20%20%20Back%20to%20the%20main%20plot.%20We%20will%20need%20to%20source%20different%20market%20data%20for%20each%20type%20of%20asset%20that%20we%20hold%2C%20and%20perform%20different%20calculations%20to%20boot.%20To%20this%20end%2C%20we%20split%20**df_holdings**%20into%20three%20separate%20DataFrames%2C%20one%20for%20each%20asset%20type%2C%20in%20nodes%20**df_holdings_equity**%2C%20**df_holdings_future**%20and%20**df_holdings_cash**.%0A%0A%20%20%20%20We%20use%20a%20single%20function%20for%20calculating%20each%20of%20the%20nodes%2C%20and%20provide%20different%20values%20to%20the%20%60type%60%20parameter.%20As%20before%2C%20the%20Loman%20knows%20that%20the%20df_holdings%20argument%20will%20be%20populated%20from%20the%20**df_holdings**%20node.%20We%20use%20the%20%60kwds%60%20parameter%20to%20%60add_node%60%20to%20tell%20Loman%20that%20the%20parameter%20%60type%60%20should%20be%20a%20constant%2C%20rather%20than%20taken%20from%20a%20node.%20Thus%20the%20**df_holdings_equity**%20node%20will%20be%20calculated%20using%20the%20**df_holdings**%20node%2C%20and%20the%20constant%20%60'Equity'%60%2C%20and%20so%20on.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(comp%2C%20loman)%3A%0A%20%20%20%20def%20filter_df(df_holdings%2C%20type)%3A%0A%20%20%20%20%20%20%20%20return%20df_holdings%5Bdf_holdings.Type%20%3D%3D%20type%5D%0A%0A%20%20%20%20comp.add_node(%22df_holdings_equity%22%2C%20filter_df%2C%20kwds%3D%7B%22type%22%3A%20loman.C(%22Equity%22)%7D)%0A%20%20%20%20comp.add_node(%22df_holdings_future%22%2C%20filter_df%2C%20kwds%3D%7B%22type%22%3A%20loman.C(%22Future%22)%7D)%0A%20%20%20%20comp.add_node(%22df_holdings_cash%22%2C%20filter_df%2C%20kwds%3D%7B%22type%22%3A%20loman.C(%22Cash%22)%7D)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20As%20before%2C%20we%20can%20compute%20nodes%20and%20inspect%20the%20results%20to%20make%20sure%20they%20are%20as%20we%20expect%3A%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(comp)%3A%0A%20%20%20%20comp.compute_all()%0A%20%20%20%20comp.v.df_holdings_equity%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(comp)%3A%0A%20%20%20%20comp.v.df_holdings_future%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(comp)%3A%0A%20%20%20%20comp.v.df_holdings_cash%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%20Gathering%20Market%20Data%0A%0A%20%20%20%20To%20do%20a%20portfolio%20valuation%2C%20and%20calculate%20exposure%20metrics%2C%20we%20will%20need%20market%20data.%20The%20market%20data%20we%20need%20will%20be%20different%20for%20each%20type%20of%20asset.%20We%20start%20first%20with%20the%20equity%20portfolio.%0A%0A%20%20%20%20%23%23%23%20Equity%20Prices%20from%20Yahoo%0A%0A%20%20%20%20We%20add%20node%20**df_equity_yql_data_yahoo**%2C%20and%20we%20populate%20it%20by%20applying%20the%20function%20%60get_yahoo_equity_data%60%20to%20each%20Symbol%20in%20**df_holdings_equity**%2C%20the%20DataFrame%20with%20our%20equity%20holdings%2C%20in%20turn.%20The%20yahoo_finance%20library%20returns%20a%20dictionary%2C%20for%20each%20symbol%2C%20so%20we%20turn%20each%20of%20those%20into%20a%20row%2C%20and%20**df_equity_yql_data_yahoo**%20will%20contain%20a%20DataFrame.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(comp%2C%20loman%2C%20pd)%3A%0A%20%20%20%20%23%20Offline%20deterministic%20equity%20data%20(replacing%20yahoo_finance)%0A%20%20%20%20PRICES_MAP%20%3D%20%7B%0A%20%20%20%20%20%20%20%20%22AVGO%22%3A%20%7B%22Symbol%22%3A%20%22AVGO%22%2C%20%22PreviousClose%22%3A%20180.0%2C%20%22LastTradePriceOnly%22%3A%20181.5%7D%2C%0A%20%20%20%20%20%20%20%20%22EVHC%22%3A%20%7B%22Symbol%22%3A%20%22EVHC%22%2C%20%22PreviousClose%22%3A%2062.0%2C%20%22LastTradePriceOnly%22%3A%2061.0%7D%2C%0A%20%20%20%20%20%20%20%20%22STT%22%3A%20%7B%22Symbol%22%3A%20%22STT%22%2C%20%22PreviousClose%22%3A%2075.0%2C%20%22LastTradePriceOnly%22%3A%2076.2%7D%2C%0A%20%20%20%20%20%20%20%20%22DAL%22%3A%20%7B%22Symbol%22%3A%20%22DAL%22%2C%20%22PreviousClose%22%3A%2050.0%2C%20%22LastTradePriceOnly%22%3A%2049.3%7D%2C%0A%20%20%20%20%20%20%20%20%22DY%22%3A%20%7B%22Symbol%22%3A%20%22DY%22%2C%20%22PreviousClose%22%3A%2085.0%2C%20%22LastTradePriceOnly%22%3A%2086.0%7D%2C%0A%20%20%20%20%7D%0A%0A%20%20%20%20def%20get_yahoo_equity_data(symbol)%3A%0A%20%20%20%20%20%20%20%20d%20%3D%20PRICES_MAP.get(symbol%2C%20%7B%22Symbol%22%3A%20symbol%2C%20%22PreviousClose%22%3A%200.0%2C%20%22LastTradePriceOnly%22%3A%200.0%7D)%0A%20%20%20%20%20%20%20%20return%20pd.Series(d)%0A%0A%20%20%20%20%40loman.node(comp)%0A%20%20%20%20def%20df_equity_yql_data_yahoo(df_holdings_equity)%3A%0A%20%20%20%20%20%20%20%20return%20df_holdings_equity.Symbol.apply(get_yahoo_equity_data)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20As%20before%2C%20we%20can%20compute%20and%20inspect%20the%20node%20to%20see%20what%20it%20contains.%20Yahoo%20returns%20a%20large%20number%20of%20fields%2C%20and%20we%20capture%20them%20all.%20For%20now%2C%20we%20will%20only%20use%20a%20small%20number%20of%20those%20fields%2C%20but%20there%20is%20no%20extra%20cost%20to%20capture%20them%2C%20and%20they%20may%20be%20useful%20for%20other%20calculations%20that%20we%20would%20want%20to%20add%20in%20the%20future%20-%20for%20example%2C%20we%20may%20want%20to%20calculate%20sets%20of%20alerts%20as%20prices%20fall%20below%20certain%20moving%20averages%2C%20say.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(comp)%3A%0A%20%20%20%20comp.compute_all()%0A%20%20%20%20comp.v.df_equity_yql_data_yahoo%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20Scraping%20Equity%20Betas%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20We%20would%20also%20like%20betas%20for%20the%20equities%20in%20our%20portfolio.%20Unfortunately%2C%20Yahoo%20doesn't%20expose%20these%20through%20its%20YQL%20API%2C%20so%20we%20instead%20scrape%20the%20data%20directly%20from%20the%20page.%20Again%2C%20we'd%20like%20to%20strongly%20recommend%20using%20paid%20data%20sources%20to%20avoid%20this%20sort%20of%20hack.%20Nonetheless%2C%20this%20does%20demonstrate%20the%20power%20of%20the%20libraries%20available%20for%20Python%2C%20as%20well%20as%20showcasing%20Loman's%20ability%20to%20easily%20integrate%20multiple%20data%20sources.%0A%0A%20%20%20%20We%20use%20the%20same%20strategy%20of%20iterating%20over%20each%20Symbol%20in%20**df_holdings_equity**%2C%20this%20time%20applying%20the%20function%20%60scrape_yahoo_data%60%20to%20get%20a%20web%20page%20and%20read%20beta%20from%20it.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(comp%2C%20loman%2C%20pd)%3A%0A%20%20%20%20%23%20Offline%20deterministic%20betas%20(replacing%20scraping%20with%20bs4%2Frequests)%0A%20%20%20%20BETAS_MAP%20%3D%20%7B%0A%20%20%20%20%20%20%20%20%22AVGO%22%3A%201.10%2C%0A%20%20%20%20%20%20%20%20%22EVHC%22%3A%200.85%2C%0A%20%20%20%20%20%20%20%20%22STT%22%3A%201.05%2C%0A%20%20%20%20%20%20%20%20%22DAL%22%3A%201.20%2C%0A%20%20%20%20%20%20%20%20%22DY%22%3A%200.95%2C%0A%20%20%20%20%7D%0A%0A%20%20%20%20def%20scrape_yahoo_data(symbol)%3A%0A%20%20%20%20%20%20%20%20return%20pd.Series(%7B%22Symbol%22%3A%20symbol%2C%20%22Beta%22%3A%20float(BETAS_MAP.get(symbol%2C%201.0))%7D)%0A%0A%20%20%20%20%40loman.node(comp)%0A%20%20%20%20def%20df_equity_scraped_data_yahoo(df_holdings_equity)%3A%0A%20%20%20%20%20%20%20%20return%20df_holdings_equity.Symbol.apply(scrape_yahoo_data)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20And%20again%2C%20we%20quickly%20compute%20and%20inspect%20the%20result.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(comp)%3A%0A%20%20%20%20comp.compute_all()%0A%20%20%20%20comp.v.df_equity_scraped_data_yahoo%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20Futures%20Prices%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20The%20parsing%20of%20futures%20prices%20from%20scrapes%20of%20the%20CME's%20website%20is%20slightly%20more%20onerous%2C%20requiring%20the%20future%20code%20to%20be%20parsed%20from%20an%20attribute%20using%20a%20regex.%20To%20move%20the%20exposition%20along%2C%20we%20won't%20cover%20it%20in%20detail%2C%20but%20just%20show%20that%20the%20DataFrame%20of%20prices%20we%20obtain%20is%20sensible%3A%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(comp%2C%20loman%2C%20pd)%3A%0A%20%20%20%20import%20re%0A%0A%20%20%20%20import%20numpy%20as%20np%0A%0A%20%20%20%20id_regex%20%3D%20re.compile(%22quotesFuturesProductTable1_(%3FP%3CSymbol%3E.%7B4%7D)_(%3FP%3CField%3E.*)%22)%0A%0A%20%20%20%20def%20get_el_data(el)%3A%0A%20%20%20%20%20%20%20%20m%20%3D%20id_regex.match(el%5B%22id%22%5D)%0A%20%20%20%20%20%20%20%20d%20%3D%20m.groupdict()%0A%20%20%20%20%20%20%20%20d%5B%22Value%22%5D%20%3D%20el.text%0A%20%20%20%20%20%20%20%20return%20d%0A%0A%20%20%20%20def%20try_float(x)%3A%0A%20%20%20%20%20%20%20%20try%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20float(x)%0A%20%20%20%20%20%20%20%20except%20Exception%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20np.nan%0A%0A%20%20%20%20%40loman.node(comp)%0A%20%20%20%20def%20df_future_prices()%3A%0A%20%20%20%20%20%20%20%20%23%20Offline%20deterministic%20futures%20prices%20(replacing%20web%20scraping)%0A%20%20%20%20%20%20%20%20data%20%3D%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%22ESM7%22%3A%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22priorSettle%22%3A%202380.0%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22last%22%3A%202385.5%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22change%22%3A%205.5%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22high%22%3A%202390.0%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22low%22%3A%202370.0%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22open%22%3A%202380.0%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22volume%22%3A%20100000%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22ESU7%22%3A%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22priorSettle%22%3A%202400.0%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22last%22%3A%202402.0%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22change%22%3A%202.0%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22high%22%3A%202410.0%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22low%22%3A%202395.0%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22open%22%3A%202398.0%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22volume%22%3A%2095000%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22ESZ7%22%3A%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22priorSettle%22%3A%202420.0%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22last%22%3A%202418.0%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22change%22%3A%20-2.0%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22high%22%3A%202430.0%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22low%22%3A%202410.0%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22open%22%3A%202422.0%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22volume%22%3A%2090000%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22ESH8%22%3A%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22priorSettle%22%3A%202440.0%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22last%22%3A%202445.0%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22change%22%3A%205.0%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22high%22%3A%202450.0%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22low%22%3A%202435.0%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22open%22%3A%202441.0%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22volume%22%3A%2087000%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22ESM8%22%3A%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22priorSettle%22%3A%202460.0%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22last%22%3A%202455.0%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22change%22%3A%20-5.0%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22high%22%3A%202465.0%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22low%22%3A%202448.0%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22open%22%3A%202459.0%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22volume%22%3A%2082000%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%2C%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20df%20%3D%20pd.DataFrame.from_dict(data%2C%20orient%3D%22index%22)%0A%20%20%20%20%20%20%20%20return%20df%0A%20%20%20%20return%20(np%2C)%0A%0A%0A%40app.cell%0Adef%20_(comp)%3A%0A%20%20%20%20comp.compute_all()%0A%20%20%20%20comp.v.df_future_prices%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20We%20also%20need%20some%20static%20data%20for%20futures.%20In%20this%20case%20we%20need%20two%20things%3A%20First%2C%20the%20contract%20unit%20i.e.%20the%20dollar%20amount%20of%20each%20point%20move%20in%20the%20index.%20And%20secondly%2C%20the%20beta%2C%20which%20in%20this%20case%20(glossing%20over%20technicalities)%20is%201%2C%20since%20the%20underlying%20of%20the%20future%20is%20the%20index%20itself.%20In%20a%20real%20system%2C%20this%20data%20would%20be%20stored%20in%20a%20database%20or%20security%20master%20system%2C%20which%20we%20could%20of%20course%20access%20from%20Loman.%20To%20keep%20this%20example%20self-contained%2C%20here%20we%20hardcode%20our%20static%20data%2C%20and%20insert%20it%20into%20a%20node%20**df_future_static_data**.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(comp%2C%20pd)%3A%0A%20%20%20%20data%20%3D%20%5B%0A%20%20%20%20%20%20%20%20%5B%22ESM7%22%2C%2050%2C%20%22USD%22%2C%201.0%5D%2C%0A%20%20%20%20%20%20%20%20%5B%22ESU7%22%2C%2050%2C%20%22USD%22%2C%201.0%5D%2C%0A%20%20%20%20%20%20%20%20%5B%22ESZ7%22%2C%2050%2C%20%22USD%22%2C%201.0%5D%2C%0A%20%20%20%20%20%20%20%20%5B%22ESH8%22%2C%2050%2C%20%22USD%22%2C%201.0%5D%2C%0A%20%20%20%20%20%20%20%20%5B%22ESM8%22%2C%2050%2C%20%22USD%22%2C%201.0%5D%2C%0A%20%20%20%20%5D%0A%20%20%20%20df%20%3D%20pd.DataFrame(data%2C%20columns%3D%5B%22Symbol%22%2C%20%22UnitAmount%22%2C%20%22UnitCurrency%22%2C%20%22Beta%22%5D)%0A%20%20%20%20comp.add_node(%22df_future_static_data%22%2C%20value%3Ddf)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%20Calculating%20P%26Ls%0A%0A%20%20%20%20Finally%2C%20we%20come%20onto%20the%20interesting%20bit%20-%20using%20our%20holdings%20data%20and%20market%20data%20to%20calculate%20P%26Ls%20and%20exposure%20measures.%0A%0A%20%20%20%20We%20start%20with%20the%20equity%20portion%20of%20the%20portfolio.%20We'll%20add%20a%20new%20node%20**df_equity_pnl**%2C%20which%20will%20contain%20a%20DataFrame%20with%20a%20row%20for%20each%20holding%20we%20have%2C%20together%20with%20the%20market%20and%20other%20information%20that%20we%20want.%20This%20node%20will%20depend%20on%20the%20three%20relevant%20nodes%20**df_holdings_equity**%20(containing%20holdings%20data)%2C%20**df_equity_yql_data_yahoo**%20(containing%20prices)%20and%20**df_equity_scraped_data_yahoo**%20(containing%20betas)%2C%20and%20so%20these%20are%20the%20parameters%20of%20the%20function%20defining%20the%20node%20**df_equity_pnl**.%0A%0A%20%20%20%20In%20the%20first%20part%20of%20the%20computation%2C%20we%20use%20Pandas%20merge%20functionality%20to%20join%20the%20tables%2C%20based%20on%20looking%20up%20Symbol%20in%20each%20table.%20We%20also%20take%20the%20opportunity%20to%20limit%20the%20set%20of%20columns%20taken%20from%20**df_equity_yql_data_yahoo**%2C%20and%20to%20rename%20columns%20to%20our%20liking.%0A%0A%20%20%20%20In%20the%20second%20part%20of%20the%20computation%2C%20we%20calculate%20several%20measures.%20For%20example%2C%20intraday%20PNL%20is%20current%20value%20less%20value%20at%20the%20close.%20And%20we%20define%20an%20exposure%20metric%20called%20CurrentBetaAdjExposure%2C%20which%20is%20defined%20as%20Beta%20%24%5Ctimes%24%20Current%20Exposure%20(Current%20Exposure%20is%20just%20Current%20Value%20for%20equities).%0A%0A%20%20%20%20As%20always%2C%20we%20check%20the%20results%20as%20we%20go.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(comp%2C%20loman%2C%20pd)%3A%0A%20%20%20%20%40loman.node(comp)%0A%20%20%20%20def%20df_equity_pnl(df_holdings_equity%2C%20df_equity_yql_data_yahoo%2C%20df_equity_scraped_data_yahoo)%3A%0A%20%20%20%20%20%20%20%20%23%20Merge%20DataFrames%0A%20%20%20%20%20%20%20%20df%20%3D%20pd.merge(%0A%20%20%20%20%20%20%20%20%20%20%20%20df_holdings_equity%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20df_equity_yql_data_yahoo%5B%5B%22Symbol%22%2C%20%22PreviousClose%22%2C%20%22LastTradePriceOnly%22%5D%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20how%3D%22left%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20on%3D%22Symbol%22%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20df.rename(columns%3D%7B%22PreviousClose%22%3A%20%22Close%22%2C%20%22LastTradePriceOnly%22%3A%20%22Last%22%7D%2C%20inplace%3DTrue)%0A%20%20%20%20%20%20%20%20for%20col%20in%20%5B%22Close%22%2C%20%22Last%22%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20df%5Bcol%5D%20%3D%20df%5Bcol%5D.astype(float)%0A%20%20%20%20%20%20%20%20df%20%3D%20pd.merge(df%2C%20df_equity_scraped_data_yahoo%2C%20how%3D%22left%22%2C%20on%3D%22Symbol%22)%0A%0A%20%20%20%20%20%20%20%20%23%20Calculate%20Measures%0A%20%20%20%20%20%20%20%20df%5B%22CloseValue%22%5D%20%3D%20df.Qty%20*%20df.Close%0A%20%20%20%20%20%20%20%20df%5B%22CurrentValue%22%5D%20%3D%20df.Qty%20*%20df.Last%0A%20%20%20%20%20%20%20%20df%5B%22PNL_Day%22%5D%20%3D%20df.CurrentValue%20-%20df.CloseValue%0A%20%20%20%20%20%20%20%20df%5B%22PNL_ITD%22%5D%20%3D%20df.CurrentValue%20-%20df.CostBasis%0A%20%20%20%20%20%20%20%20df%5B%22CurrentExposure%22%5D%20%3D%20df.CurrentValue%0A%20%20%20%20%20%20%20%20df%5B%22CurrentBetaAdjExposure%22%5D%20%3D%20df.Beta%20*%20df.CurrentExposure%0A%20%20%20%20%20%20%20%20return%20df%0A%0A%20%20%20%20comp.compute_all()%0A%20%20%20%20comp.v.df_equity_pnl%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20The%20process%20is%20very%20similar%20for%20futures.%20**df_future_pnl**%20is%20created%20by%20merging%20the%20holdings%2C%20price%20and%20static%20data%20in%20**df_holdings_future**%2C%20**df_future_prices**%20and%20**df_future_static_data**%20respectively.%20We%20again%20calculate%20each%20of%20the%20P%26L%20and%20exposure%20measures%20that%20we%20would%20like%20to%20see%2C%20but%20note%20that%20the%20treatment%20for%20futures%20is%20different.%20For%20example%2C%20intraday%20P%26L%20is%20calculated%20as%20%24(%5Ctext%7BLast%20Price%7D%20-%20%5Ctext%7BClose%20Price%7D)%20%5Ctimes%20%5Ctext%7BQuantity%7D%20%5Ctimes%20%5Ctext%7BContract%20Unit%7D%24.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(comp%2C%20loman%2C%20pd)%3A%0A%20%20%20%20%40loman.node(comp)%0A%20%20%20%20def%20df_future_pnl(df_holdings_future%2C%20df_future_prices%2C%20df_future_static_data)%3A%0A%20%20%20%20%20%20%20%20%23%20Merge%20DataFrames%0A%20%20%20%20%20%20%20%20df%20%3D%20pd.merge(%0A%20%20%20%20%20%20%20%20%20%20%20%20df_holdings_future%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20df_future_prices%5B%5B%22priorSettle%22%2C%20%22last%22%5D%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20how%3D%22left%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20left_on%3D%22Symbol%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20right_index%3DTrue%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20df.rename(columns%3D%7B%22priorSettle%22%3A%20%22Close%22%2C%20%22last%22%3A%20%22Last%22%7D%2C%20inplace%3DTrue)%0A%20%20%20%20%20%20%20%20for%20col%20in%20%5B%22Close%22%2C%20%22Last%22%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20df%5Bcol%5D%20%3D%20df%5Bcol%5D.astype(float)%0A%20%20%20%20%20%20%20%20df%20%3D%20pd.merge(df%2C%20df_future_static_data%2C%20how%3D%22left%22%2C%20on%3D%22Symbol%22)%0A%0A%20%20%20%20%20%20%20%20%23%20Calculate%20Measures%0A%20%20%20%20%20%20%20%20df%5B%22PNL_Day%22%5D%20%3D%20(df.Last%20-%20df.Close)%20*%20df.Qty%20*%20df.UnitAmount%0A%20%20%20%20%20%20%20%20df%5B%22CloseValue%22%5D%20%3D%200%0A%20%20%20%20%20%20%20%20df%5B%22CurrentValue%22%5D%20%3D%20df.PNL_Day%0A%20%20%20%20%20%20%20%20df%5B%22PNL_ITD%22%5D%20%3D%20df.CurrentValue%20-%20df.CostBasis%0A%20%20%20%20%20%20%20%20df%5B%22CurrentExposure%22%5D%20%3D%20df.Last%20*%20df.Qty%20*%20df.UnitAmount%0A%20%20%20%20%20%20%20%20df%5B%22CurrentBetaAdjExposure%22%5D%20%3D%20df.Beta%20*%20df.CurrentExposure%0A%20%20%20%20%20%20%20%20return%20df%0A%0A%20%20%20%20comp.compute_all()%0A%20%20%20%20comp.v.df_future_pnl%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20The%20calculations%20for%20the%20cash%20component%20are%20more%20basic%2C%20as%20cash%20has%20no%20exposure%20by%20these%20measures%3A%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(comp%2C%20loman%2C%20np)%3A%0A%20%20%20%20%40loman.node(comp)%0A%20%20%20%20def%20df_cash_pnl(df_holdings_cash)%3A%0A%20%20%20%20%20%20%20%20df%20%3D%20df_holdings_cash.copy()%0A%20%20%20%20%20%20%20%20df%5B%22Close%22%5D%20%3D%20np.nan%0A%20%20%20%20%20%20%20%20df%5B%22Last%22%5D%20%3D%20np.nan%0A%20%20%20%20%20%20%20%20df%5B%22PNL_Day%22%5D%20%3D%20df%5B%22PNL_ITD%22%5D%20%3D%200.0%0A%20%20%20%20%20%20%20%20df%5B%22CloseValue%22%5D%20%3D%20df%5B%22CurrentValue%22%5D%20%3D%20df%5B%22CostBasis%22%5D%20%3D%20df.Qty%0A%20%20%20%20%20%20%20%20df%5B%22CurrentExposure%22%5D%20%3D%20df%5B%22CurrentBetaAdjExposure%22%5D%20%3D%200.0%0A%20%20%20%20%20%20%20%20df%5B%22Beta%22%5D%20%3D%200%0A%20%20%20%20%20%20%20%20return%20df%0A%0A%20%20%20%20comp.compute_all()%0A%20%20%20%20comp.v.df_cash_pnl%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20Finally%2C%20we%20add%20a%20node%20**df_pnl**%2C%20where%20we%20take%20all%20the%20rows%20from%20the%20equity%2C%20future%20and%20cash%20P%26L%20and%20exposure%20DataFrames%20in%20**df_equity_pnl**%2C%20**df_future_pnl**%20and%20**df_cash_pnl**%20respectively.%20This%20gives%20us%20a%20position-by-position%20P%26L%20report.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(comp%2C%20loman%2C%20pd)%3A%0A%20%20%20%20%40loman.node(comp)%0A%20%20%20%20def%20df_pnl(df_equity_pnl%2C%20df_future_pnl%2C%20df_cash_pnl)%3A%0A%20%20%20%20%20%20%20%20df%20%3D%20pd.concat(%5Bdf_equity_pnl%2C%20df_future_pnl%2C%20df_cash_pnl%5D)%0A%20%20%20%20%20%20%20%20df%20%3D%20df%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22Type%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22Symbol%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22Qty%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22Close%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22Last%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22CostBasis%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22CloseValue%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22CurrentValue%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22PNL_ITD%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22PNL_Day%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22Beta%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22CurrentExposure%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22CurrentBetaAdjExposure%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%5D%0A%20%20%20%20%20%20%20%20%5D%0A%20%20%20%20%20%20%20%20return%20df%0A%0A%20%20%20%20comp.compute_all()%0A%20%20%20%20comp.v.df_pnl%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20From%20there%2C%20various%20aggregations%20are%20possible.%20For%20example%2C%20we%20can%20easily%20see%20intraday%20P%26L%2C%20and%20inception-to-date%20P%26L%3A%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(comp)%3A%0A%20%20%20%20comp.v.df_pnl.PNL_Day.sum()%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(comp)%3A%0A%20%20%20%20comp.v.df_pnl.PNL_ITD.sum()%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20As%20well%20as%20seeing%20our%20exposure%2C%20and%20beta-adjusted%20exposure%20metrics%20across%20the%20whole%20portfolio.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(comp)%3A%0A%20%20%20%20comp.v.df_pnl%5B%5B%22CurrentExposure%22%2C%20%22CurrentBetaAdjExposure%22%5D%5D.sum()%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20We%20could%20even%20put%20the%20aggregated%20data%20itself%20into%20new%20nodes.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%20Final%20Thoughts%0A%0A%20%20%20%20The%20first%20thing%20to%20point%20out%20is%20that%20our%20computation%20is%20now%20both%20reasonably%20complex%2C%20but%20very%20logically%20laid%20out%3A%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(comp)%3A%0A%20%20%20%20comp.draw()%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20This%20is%20serves%20a%20practical%20purpose.%20Code%20typically%20spends%20the%20bulk%20of%20its%20life%20being%20maintained%2C%20so%20it%20is%20important%20to%20make%20that%20maintenance%20as%20easy%20and%20fast%20as%20possible.%20Returning%20to%20this%20after%20several%20months%2C%20it%20takes%20a%20time%20to%20reacquaint%20with%20even%20a%20modest%20amount%20of%20code%2C%20while%20the%20logical%20structure%20and%20visualization%20capabilities%20offered%20by%20Loman%20make%20it%20easy%20to%20see%20how%20a%20computation%20occurs.%0A%0A%20%20%20%20There%20are%20also%20benefits%20when%20we%20are%20first%20creating%20code.%20Because%20Loman%20keeps%20track%20of%20state%20for%20us%2C%20as%20we%20interactively%20add%20nodes%20(and%20possibily%20take%20several%20iterations%20to%20get%20them%20correct)%2C%20we%20do%20not%20have%20to%20re-run%20upstream%20dependencies.%20Contrast%20this%20with%20a%20writing%20a%20program%20by%20adding%20sub-routines%20or%20sections%20of%20code.%20Each%20re-run%20would%20necessitate%20fetching%20data%20from%20several%20external%20websites%2C%20and%20by%20the%20end%2C%20even%20in%20this%20small%20case%2C%20the%20process%20of%20trying%20out%20new%20iterations%20becomes%20quite%20onerous.%0A%0A%20%20%20%20Of%20course%20there%20are%20other%20possibilities.%20An%20interactive%20environment%20such%20as%20IPython%20Notebook%20requires%20us%20to%20manually%20keep%20track%20ourselves%20of%20what%20needs%20to%20be%20re-run%20each%20time.%20Or%20a%20program%20that%20serializes%20data%20from%20external%20websites%20to%20disk%20and%20then%20reads%20in%20the%20cached%20copy%20also%20imposes%20a%20substantial%20cognitive%20load%20keeping%20track%20of%20that%20cache.%0A%0A%20%20%20%20Our%20solution%20is%20extensible.%20It%20is%20easy%20to%20add%20new%20asset%20types%2C%20new%20measures%2C%20or%20new%20classifications%20to%20cover%20more%20complex%20portfolios%2C%20and%20create%20more%20detailed%20reports.%20Also%2C%20we%20can%20combine%20with%20other%20data%20sources%20to%20create%20entirely%20new%20reports%20-%20for%20example%2C%20it%20is%20easy%20to%20imagine%20using%20earnings%20announcement%20calendars%20to%20create%20a%20node%20with%20a%20list%20of%20equities%20reporting%20earnings%20in%20the%20next%20week%2C%20which%20could%20be%20used%20to%20provide%20alerts.%20Or%20we%20could%20progarm%20a%20node%20to%20show%20how%20near%20our%20portfolio%20is%20to%20risk%20or%20compliance%20limits.%0A%0A%20%20%20%20For%20real-time%20use%2C%20Loman's%20ability%20to%20track%20state%20allows%20us%20to%20update%20different%20inputs%20at%20different%20cadences.%20For%20example%2C%20we%20might%20update%20the%20equity%20and%20price%20nodes%20every%20tick%2C%20or%20every%20few%20seconds%2C%20but%20update%20semi-static%20data%20less%20frequently.%0A%0A%20%20%20%20We%20can%20also%20control%20output%20cadence%20by%20specifying%20which%20nodes%20to%20calculate%2C%20when.%20We%20only%20produce%20one%20set%20of%20results%20here%2C%20but%20consider%20if%20we%20had%20a%20valuation%20or%20risk%20model%20that%20we%20wanted%20to%20use%2C%20but%20which%20was%20slow%20to%20calculate.%20We%20could%20still%20calculate%20P%26L%20at%20a%20relatively%20high%20frequency%2C%20which%20calculating%20the%20more%20time-consuming%20model%20every%20few%20minutes%2C%20say.%0A%0A%20%20%20%20Finally%2C%20our%20solution%20is%20potentially%20usable%20in%20a%20variety%20of%20different%20contexts.%20We%20might%20have%20a%20batch%20system%20using%20this%20computation%20graph%2C%20to%20produce%20daily%2C%20or%20periodic%20intraday%20valuations%20centrally%2C%20while%20the%20investment%20team%20managing%20a%20particular%20portfolio%20might%20appreciate%20the%20ability%20to%20run%20the%20same%20computation%20graph%20on%20their%20portfolios%20and%20model%20portfolios%20whenever%20they%20desire.%0A%0A%20%20%20%20That%20brings%20us%20to%20the%20end%20of%20this%20example.%20We%20looked%20at%20how%20to%20use%20Loman%20to%20create%20a%20basic%20valuation%20system%20for%20a%20limited%20portfolio.%20In%20doing%20so%2C%20we%20explored%20several%20tangible%20benefits%20that%20we%20hope%20make%20life%20easier%20for%20quant%20practitioners.%20If%20anything%20is%20unclear%20in%20this%20example%2C%20feel%20free%20to%20contact%20the%20authors%20through%20the%20%5BLoman%20Forum%20on%20Google%20Groups%5D(https%3A%2F%2Fgroups.google.com%2Fforum%2F%23!forum%2Floman-python).%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_()%3A%0A%20%20%20%20import%20marimo%20as%20mo%0A%20%20%20%20return%20(mo%2C)%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20app.run()%0A
8e64616e9f7f377ab298ee0af4b0c2d3b38d18d049f051f342cdf6afe64d0a1e