import%20marimo%0A%0A__generated_with%20%3D%20%220.17.7%22%0Aapp%20%3D%20marimo.App(width%3D%22medium%22%2C%20auto_download%3D%5B%22html%22%5D)%0A%0A%0A%40app.cell%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%20Quantile%20regression%20as%20a%20sanity%20check%20on%20asset%20valuation%0A%0A%20%20%20%20%2B%20*Author%3A%20%5BYegor%20Tkachenko%5D(https%3A%2F%2Fyegortkachenko.com%2F)*%0A%20%20%20%20%2B%20*Date%3A%20Nov%2018%2C%202025*%0A%0A%20%20%20%20**Quantile%20regression**%20is%20a%20statistical%20model%20that%20predicts%20a%20level%20of%20the%20output%20variable%20such%20that%20a%20chosen%20proportion%20%24%5Ctau%24%20of%20data%20points%20fall%20below%20this%20level%20%E2%80%93%20and%20the%20remaining%20%24(1-%5Ctau)%24%20fall%20above%20it%20%E2%80%93%20conditional%20on%20some%20covariates.%0A%0A%20%20%20%20Conveniently%2C%20quantile%20regression%20*of%20price%20on%20time*%20can%20be%20used%20as%20a%20sanity%20check%20on%20whether%20a%20given%20asset%20is%20over-%20or%20under-valued.%0A%0A%20%20%20%20More%20precisely%3A%0A%0A%20%20%20%20%2B%20For%20an%20arbitrary%20asset%2C%20let%20%24p_t%24%20denote%20the%20price%20at%20time%20%24t%24.%20For%20a%20chosen%20quantile%20%240%20%3C%20%5Ctau%20%3C%201%24%2C%20quantile%20regression%20of%20price%20%24p_t%24%20on%20time%20%24t%24%20estimates%20a%20function%20%24%5Chat%20p_%5Ctau(t)%24%20such%20that%2C%20for%20each%20time%20%24t%24%2C%0A%20%20%20%20%20%20%24%24P%5Cbig(p_t%20%5Cleq%20%5Chat%20p_%5Ctau(t)%5Cmid%20t%5Cbig)%5Capprox%20%5Ctau.%24%24%0A%20%20%20%20%2B%20In%20words%2C%20roughly%20a%20fraction%20%24%5Ctau%24%20of%20observed%20prices%20will%20lie%20below%20the%20estimated%20curve%20%24%5Chat%20p_%5Ctau(t)%24%2C%20assuming%20the%20model%20is%20correctly%20specified.%0A%20%20%20%20%2B%20For%20example%3A%0A%20%20%20%20%20%20%20%20%2B%20%24%5Ctau%3D0.5%24%20yields%20a%20median%20price%20path%20over%20time%20(in%20contrast%20to%20the%20mean%20from%20classical%20least-squares%20regression).%0A%20%20%20%20%20%20%20%20%2B%20%24%5Ctau%3D0.1%24%20gives%20the%2010th%20percentile%20of%20the%20price%20distribution%20conditional%20on%20time%20%E2%80%93%20a%20conservative%20lower%20bound%20estimate%20of%20the%20asset's%20value%20at%20each%20%24t%24.%0A%20%20%20%20%20%20%20%20%2B%20%24%5Ctau%3D0.9%24%20gives%20the%2090th%20percentile%20conditional%20on%20time%20%E2%80%93%20a%20level%20at%20which%20the%20asset%20could%20be%20considered%20historically%20expensive%20at%20time%20%24t%24.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20Quantile%20regression%20can%20be%20formulated%20as%20a%20convex%20optimization%20problem%20and%20solved%20using%20the%20%5BCVXPY%5D(https%3A%2F%2Fwww.cvxpy.org%2F)%20Python%20library.%20It%20is%20closely%20related%20to%20linear%20regression%2C%20but%20instead%20of%20minimizing%20squared%20errors%20we%20minimize%20a%20tilted%20absolute-loss%20function%20that%20puts%20different%20weights%20on%20positive%20vs.%5C%20negative%20residuals.%0A%0A%20%20%20%20Here%20is%20the%20setup%3A%0A%0A%20%20%20%20%2B%20Let%20%24y%20%5Cin%20%5Cmathbb%7BR%7D%5En%24%20be%20a%20length-%24n%24%20vector%20of%20prices%2C%20and%20let%20%24X%5Cin%20%5Cmathbb%7BR%7D%5E%7Bn%20%5Ctimes%20k%7D%24%20be%20an%20%24n%20%5Ctimes%20k%24%20matrix%20of%20covariates.%20In%20our%20case%2C%20we%20will%20use%20%24k%20%3D%202%24%2C%20with%3A%0A%20%20%20%20%20%20%20%20%2B%20a%20column%20of%20ones%20(intercept)%20as%20the%20first%20column%2C%20and%0A%20%20%20%20%20%20%20%20%2B%20a%20time%20index%20%24t%20%3D%201%2C%5Cdots%2Cn%24%20as%20the%20second%20column.%0A%0A%20%20%20%20%2B%20Let%20%24%5Ctau%20%5Cin%20(0%2C1)%24%20be%20the%20chosen%20quantile.%0A%0A%20%20%20%20%2B%20Let%20%24%5Cbeta%20%5Cin%20%5Cmathbb%7BR%7D%5Ek%24%20be%20the%20parameter%20vector.%20The%20fitted%20conditional%20quantile%20is%20%24X%20%5Cbeta%24.%0A%0A%20%20%20%20We%20estimate%20%24%5Cbeta%24%20by%20minimizing%20the%20*pinball%20loss*%20plus%20an%20%24%5Cell_1%24-regularization%20term%3A%0A%0A%20%20%20%20%24%24%5Cmin_%7B%5Cbeta%7D%5Cquad%20%5Cfrac%7B1%7D%7Bn%7D%20%5Csum_%7Bi%3D1%7D%5En%20%5Cbig%5B%5Ctau%5Cmax(y_i%20-%20x_i%5E%5Ctop%5Cbeta%2C%200)%20%2B%20(1-%5Ctau)%5Cmax(x_i%5E%5Ctop%5Cbeta%20-%20y_i%2C%200)%5Cbig%5D%20%2B%20%5Clambda%20%5Csum_%7Bj%3D1%7D%5Ek%20%7C%5Cbeta_j%7C.%24%24%0A%0A%20%20%20%20Here%2C%20%24x_i%24%20is%20%24i%24th%20row%20of%20%24X%24%2C%20and%20%24%5Clambda%20%3E%200%24%20controls%20the%20strength%20of%20%24%5Cell_1%24%20regularization.%0A%0A%20%20%20%20The%20intuition%20behind%20the%20pinball%20loss%20is%20straightforward.%20Suppose%20we%20set%20%24%5Ctau%20%3D%200.1%24.%20We%20want%20the%20regression%20line%20to%20hug%20the%20bottom%20of%20the%20time-series%20distribution%20so%20that%20only%20about%2010%25%20of%20observations%20fall%20below%20the%20line.%20This%20means%20we%20heavily%20penalize%20negative%20residuals%20%24y%20-%20X%5Cbeta%24%20(actual%20price%20below%20the%20fitted%20line)%20relative%20to%20positive%20ones%20(actual%20price%20above%20the%20line).%20With%20appropriate%20weights%20%241-%5Ctau%24%20and%20%24%5Ctau%24%20on%20these%20two%20types%20of%20residuals%2C%20the%20minimizer%20of%20this%20loss%20converges%20to%20the%20desired%20conditional%20%24%5Ctau%24-quantile%20(see%2C%20for%20example%2C%20%5Bthese%20notes%5D(https%3A%2F%2Fjosephsalmon.eu%2Fenseignement%2FUW%2FSTAT593%2FQuantileRegression.pdf)).%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%20numpy%20as%20np%0A%20%20%20%20import%20pandas%20as%20pd%0A%20%20%20%20import%20cvxpy%20as%20cp%0A%20%20%20%20import%20matplotlib.pyplot%20as%20plt%0A%20%20%20%20from%20datetime%20import%20timedelta%0A%20%20%20%20import%20marimo%20as%20mo%0A%20%20%20%20return%20cp%2C%20mo%2C%20np%2C%20pd%2C%20plt%0A%0A%0A%40app.cell%0Adef%20_(pd)%3A%0A%20%20%20%20%23%20download%20data%20from%20stooq.com%20for%20specified%20asset%0A%20%20%20%20def%20get_data(asset_name%2C%20start_date)%3A%20%20%0A%20%20%20%20%20%20%20%20url%20%3D%20f%22https%3A%2F%2Fstooq.com%2Fq%2Fd%2Fl%2F%3Fs%3D%7Basset_name%7D%26i%3Dd%22%0A%20%20%20%20%20%20%20%20data%20%3D%20pd.read_csv(url)%0A%20%20%20%20%20%20%20%20data%5B%22Date%22%5D%20%3D%20pd.to_datetime(data%5B%22Date%22%5D)%0A%20%20%20%20%20%20%20%20data%20%3D%20data%5Bdata%5B%22Date%22%5D%20%3E%3D%20pd.to_datetime(start_date)%5D%0A%20%20%20%20%20%20%20%20data%20%3D%20data%5B%5B%22Date%22%2C%20%22Close%22%5D%5D.dropna().reset_index(drop%3DTrue)%0A%20%20%20%20%20%20%20%20data%5B%22t%22%5D%20%3D%20(data%5B%22Date%22%5D%20-%20data%5B%22Date%22%5D.min()).dt.days.astype(%22float%22)%0A%20%20%20%20%20%20%20%20return%20data%0A%20%20%20%20return%20(get_data%2C)%0A%0A%0A%40app.cell%0Adef%20_(cp)%3A%0A%20%20%20%20%23%20estimate%20quantile%20regression%2C%20conditional%20on%20time%20index%0A%20%20%20%20%23%20via%20pinball%20loss%2C%20using%20convex%20optimization%20with%20cvxpy%0A%20%20%20%20def%20quantile_trend_coefs(tau%2C%20X%2C%20y%2C%20lambda_reg%3D0.001)%3A%0A%20%20%20%20%20%20%20%20%23%20tau%20%3A%20target%20quantile%20in%20(0%2C%201)%0A%20%20%20%20%20%20%20%20%23%20X%20%3A%20input%20matrix%20(n%2C%20k)%0A%20%20%20%20%20%20%20%20%23%20y%20%3A%20predicted%20vector%20(n%2C)%0A%20%20%20%20%20%20%20%20%23%20lambda_reg%20%3A%20L1%20regularization%20strength%0A%20%20%20%20%20%20%20%20%23%20beta%20%3A%20estimated%20coefficient%20vector%20(k%2C)%0A%0A%20%20%20%20%20%20%20%20beta%20%3D%20cp.Variable(X.shape%5B1%5D)%0A%20%20%20%20%20%20%20%20resid%20%3D%20y%20-%20X%20%40%20beta%0A%20%20%20%20%20%20%20%20pinball_loss%20%3D%20cp.mean(%0A%20%20%20%20%20%20%20%20%20%20%20%20tau%20*%20cp.pos(resid)%20%2B%20(1%20-%20tau)%20*%20cp.pos(-resid)%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20objective%20%3D%20cp.Minimize(pinball_loss%20%2B%20lambda_reg%20*%20cp.sum(cp.abs(beta)))%0A%20%20%20%20%20%20%20%20prob%20%3D%20cp.Problem(objective)%0A%20%20%20%20%20%20%20%20prob.solve()%0A%20%20%20%20%20%20%20%20return%20beta.value%0A%20%20%20%20return%20(quantile_trend_coefs%2C)%0A%0A%0A%40app.cell%0Adef%20_(get_data%2C%20mo%2C%20np%2C%20plt%2C%20quantile_trend_coefs)%3A%0A%20%20%20%20def%20report(asset_name%3D%22eth.v%22%2C%20start_date%3D%222022-01-01%22)%3A%0A%0A%20%20%20%20%20%20%20%20%23%20download%20data%0A%20%20%20%20%20%20%20%20data%20%3D%20get_data(asset_name%2C%20start_date)%0A%0A%20%20%20%20%20%20%20%20%23%20prepare%20data%0A%20%20%20%20%20%20%20%20y%20%3D%20data%5B%22Close%22%5D.values%20%20%23%20close%20price%0A%20%20%20%20%20%20%20%20t%20%3D%20data%5B%22t%22%5D.values%0A%20%20%20%20%20%20%20%20X%20%3D%20np.column_stack(%5Bnp.ones(len(t))%2C%20t%5D)%0A%20%20%20%20%20%20%20%20dates%20%3D%20data%5B%22Date%22%5D%0A%0A%20%20%20%20%20%20%20%20%23%20estimate%20conditional%20quantiles%0A%20%20%20%20%20%20%20%20quantiles%20%3D%20%5B0.1%2C%200.25%2C%200.75%2C%200.9%5D%0A%20%20%20%20%20%20%20%20betas%20%3D%20%7Btau%3A%20quantile_trend_coefs(tau%2C%20X%2C%20y)%20for%20tau%20in%20quantiles%7D%0A%20%20%20%20%20%20%20%20trends%20%3D%20%7Btau%3A%20X%20%40%20betas%5Btau%5D%20for%20tau%20in%20quantiles%7D%0A%0A%20%20%20%20%20%20%20%20%23%20plot%0A%20%20%20%20%20%20%20%20plt.figure(figsize%3D(10%2C%205))%0A%20%20%20%20%20%20%20%20plt.plot(dates%2C%20y%2C%20label%3D%22Close%20Daily%20Price%22%2C%20c%3D%22grey%22%2C%20linewidth%3D1)%0A%20%20%20%20%20%20%20%20plt.plot(dates%2C%20trends%5B0.1%5D%2C%20linewidth%3D2%2C%20label%3D%2210th%20Percentile%22%2C%20c%3D%22blue%22)%0A%20%20%20%20%20%20%20%20plt.plot(dates%2C%20trends%5B0.25%5D%2C%20linewidth%3D2%2C%20label%3D%2225th%20Percentile%22%2C%20c%3D%22lightblue%22)%0A%20%20%20%20%20%20%20%20plt.plot(dates%2C%20trends%5B0.75%5D%2C%20linewidth%3D2%2C%20label%3D%2275th%20Percentile%22%2C%20c%3D%22pink%22)%0A%20%20%20%20%20%20%20%20plt.plot(dates%2C%20trends%5B0.9%5D%2C%20linewidth%3D2%2C%20label%3D%2290th%20Percentile%22%2C%20c%3D%22red%22)%0A%20%20%20%20%20%20%20%20plt.xlabel(%22Date%22)%0A%20%20%20%20%20%20%20%20plt.ylabel(%22Price%20(USD)%22)%0A%20%20%20%20%20%20%20%20plt.title(f%22%7Basset_name.upper()%7D%20Price%20and%20Quantile%20Regression%20Trend%22)%0A%20%20%20%20%20%20%20%20plt.legend()%0A%20%20%20%20%20%20%20%20plt.tight_layout()%0A%0A%20%20%20%20%20%20%20%20fig%20%3D%20plt.gca()%0A%0A%20%20%20%20%20%20%20%20with%20mo.capture_stdout()%20as%20output%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20summary%20stats%20at%20the%20most%20recent%20date%0A%20%20%20%20%20%20%20%20%20%20%20%20current_date%20%3D%20dates.iloc%5B-1%5D.date()%0A%20%20%20%20%20%20%20%20%20%20%20%20current_price%20%3D%20y%5B-1%5D%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20print(f%22%2B%20Date%3A%20%7Bcurrent_date%7D%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20print(f%22%2B%20Asset%3A%20%7Basset_name%7D%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20print(f%22%2B%20Current%3A%20%5Cn%20%20%20%20%20%2B%20Price%3A%20%7Bcurrent_price%3A%2C.2f%7D%20USD%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20print(f%22%20%20%20%20%2B%2010th%20percentile%3A%20%7Btrends%5B0.1%5D%5B-1%5D%3A%2C.2f%7D%20USD%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20print(f%22%20%20%20%20%2B%2025th%20percentile%3A%20%7Btrends%5B0.25%5D%5B-1%5D%3A%2C.2f%7D%20USD%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20print(f%22%20%20%20%20%2B%2075th%20percentile%3A%20%7Btrends%5B0.75%5D%5B-1%5D%3A%2C.2f%7D%20USD%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20print(f%22%20%20%20%20%2B%2090th%20percentile%3A%20%7Btrends%5B0.9%5D%5B-1%5D%3A%2C.2f%7D%20USD%22)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20simple%20linear-trend-based%20'expected'%20annualized%20return%0A%20%20%20%20%20%20%20%20%20%20%20%20avg_slope%20%3D%200.5%20*%20(betas%5B0.1%5D%5B1%5D%20%2B%20betas%5B0.9%5D%5B1%5D)%0A%20%20%20%20%20%20%20%20%20%20%20%20approx_annual_return%20%3D%20365%20*%20avg_slope%20%2F%20current_price%0A%20%20%20%20%20%20%20%20%20%20%20%20print(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22%2B%20Approx.%20annualized%20return%20from%20current%20price%20%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20f%22(based%20on%2010th%20and%2090th%20percentile%20trends)%3A%20%7B100%20*%20approx_annual_return%3A%2C.2f%7D%25%22%0A%20%20%20%20%20%20%20%20%20%20%20%20)%0A%0A%20%20%20%20%20%20%20%20return%20fig%2C%20mo.md(output.getvalue())%0A%20%20%20%20return%20(report%2C)%0A%0A%0A%40app.cell%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20Below%2C%20you%20can%20explore%20different%20quantile%20regressors%20for%20several%20assets%20conditional%20on%20time.%20The%20default%20example%20is%20Ethereum%20(ETH)%20price%20in%20USD.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(mo)%3A%0A%20%20%20%20dropdown%20%3D%20mo.ui.dropdown(%0A%20%20%20%20%20%20%20%20options%3D%5B%22eth.v%22%2C%20%22btc.v%22%2C%20%22voo.us%22%2C%20%22coke.us%22%2C%20%22aapl.us%22%2C%20%22nvda.us%22%2C%20%22idev.us%22%5D%2C%20%0A%20%20%20%20%20%20%20%20value%3D%22eth.v%22%2C%20%0A%20%20%20%20%20%20%20%20label%3D%22Choose%20the%20asset%3A%20%22%0A%20%20%20%20)%0A%20%20%20%20dropdown%0A%20%20%20%20return%20(dropdown%2C)%0A%0A%0A%40app.cell%0Adef%20_(dropdown%2C%20report)%3A%0A%20%20%20%20report(asset_name%3Ddropdown.value%2C%20start_date%3D%222022-01-01%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%20Discussion%20and%20limitations%0A%0A%20%20%20%20The%20position%20of%20the%20actual%20price%20relative%20to%20the%20fitted%20quantile%20curves%20provides%20a%20rough%20indication%20of%20whether%20the%20asset%20is%20currently%20cheap%20or%20expensive%20relative%20to%20its%20historical%20behavior%20and%20trend.%0A%0A%20%20%20%20However%2C%20it%20is%20risky%20to%20take%20this%20method%20at%20face%20value%20over%20very%20long%20horizons.%20A%20linear%20trend%20in%20time%20is%20a%20crude%20approximation%3A%20in%20many%20markets%2C%20prices%20may%20grow%20roughly%20exponentially%20or%20experience%20structural%20breaks%2C%20regime%20shifts%2C%20or%20changes%20in%20volatility.%0A%0A%20%20%20%20One%20way%20to%20think%20about%20these%20quantile%20bounds%20is%20that%20they%20roughly%20circumscribe%20the%20space%20of%20price%20oscillations%20around%20a%20slowly%20moving%20linear%20trend.%20If%20that%20trend%20changes%20because%20of%20a%20fundamental%20shift%20(e.g.%2C%20new%20information%2C%20structural%20changes%20in%20the%20market)%2C%20extrapolations%20based%20on%20the%20historical%20linear%20trend%20%E2%80%93%20and%20the%20corresponding%20quantile%20bands%20%E2%80%93%20may%20no%20longer%20be%20valid.%0A%20%20%20%20%22%22%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
570613d608fefe835059b8cd88dbad74