<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:googleplay="http://www.google.com/schemas/play-podcasts/1.0"><channel><title><![CDATA[Systematically Biased: Portfolio Lab]]></title><description><![CDATA[Experiments and notes on portfolio construction, asset allocation, optimization, and strategy backtesting.]]></description><link>https://systematicallybiased.substack.com/s/portfolio-lab</link><image><url>https://substackcdn.com/image/fetch/$s_!Sj04!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa3423260-de01-4124-8282-b7ae4f2153ff_757x757.png</url><title>Systematically Biased: Portfolio Lab</title><link>https://systematicallybiased.substack.com/s/portfolio-lab</link></image><generator>Substack</generator><lastBuildDate>Thu, 04 Jun 2026 20:15:56 GMT</lastBuildDate><atom:link href="https://systematicallybiased.substack.com/feed" rel="self" type="application/rss+xml"/><copyright><![CDATA[Systematically Biased]]></copyright><language><![CDATA[en]]></language><webMaster><![CDATA[systematicallybiased@substack.com]]></webMaster><itunes:owner><itunes:email><![CDATA[systematicallybiased@substack.com]]></itunes:email><itunes:name><![CDATA[Systematically Biased]]></itunes:name></itunes:owner><itunes:author><![CDATA[Systematically Biased]]></itunes:author><googleplay:owner><![CDATA[systematicallybiased@substack.com]]></googleplay:owner><googleplay:email><![CDATA[systematicallybiased@substack.com]]></googleplay:email><googleplay:author><![CDATA[Systematically Biased]]></googleplay:author><itunes:block><![CDATA[Yes]]></itunes:block><item><title><![CDATA[Backtesting Mean-Variance Alternatives]]></title><description><![CDATA[A practical comparison of mean-variance alternatives across a diversified ETF universe]]></description><link>https://systematicallybiased.substack.com/p/backtesting-mean-variance-alternatives</link><guid isPermaLink="false">https://systematicallybiased.substack.com/p/backtesting-mean-variance-alternatives</guid><dc:creator><![CDATA[Systematically Biased]]></dc:creator><pubDate>Thu, 28 May 2026 17:22:17 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!pBMe!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b921877-6ea6-4df7-a155-658598e6acae_3162x2822.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In a <a href="https://systematicallybiased.substack.com/p/beyond-mean-variance-optimization?r=6w89b">previous post</a>, I discussed several alternatives to mean-variance optimization (MVO), which are summarized below:</p><div id="datawrapper-iframe" class="datawrapper-wrap outer" data-attrs="{&quot;url&quot;:&quot;https://datawrapper.dwcdn.net/AgQsv/1/&quot;,&quot;thumbnail_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f93b9aa5-4ffd-45f7-bc7c-3844ff80d3ac_1220x918.png&quot;,&quot;thumbnail_url_full&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c60b11e4-be71-4eb5-b221-fc4ae77516ae_1220x988.png&quot;,&quot;height&quot;:501,&quot;title&quot;:&quot;Summary of Alternatives to MVO&quot;,&quot;description&quot;:&quot;&quot;}" data-component-name="DatawrapperToDOM"><iframe id="iframe-datawrapper" class="datawrapper-iframe" src="https://datawrapper.dwcdn.net/AgQsv/1/" width="730" height="501" frameborder="0" scrolling="no"></iframe><script type="text/javascript">!function(){"use strict";window.addEventListener("message",(function(e){if(void 0!==e.data["datawrapper-height"]){var t=document.querySelectorAll("iframe");for(var a in e.data["datawrapper-height"])for(var r=0;r<t.length;r++){if(t[r].contentWindow===e.source)t[r].style.height=e.data["datawrapper-height"][a]+"px"}}}))}();</script></div><p>In this post, I&#8217;m going to put them all to the (back)test. To keep things simple but also practical, I&#8217;ll use a common universe of ETFs representing different asset classes and regions, and I&#8217;ll make some choices about how to group them into buckets to avoid undue concentrations.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://systematicallybiased.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">This Substack is reader-supported. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><div><hr></div><h1>Asset Universe and Data</h1><p>I start with the following universe of ETFs. Although there are arguably some better (i.e. cheaper) ETFs for at least some of the asset classes, I chose these to maximize the length of the backtest period. </p><div id="datawrapper-iframe" class="datawrapper-wrap outer" data-attrs="{&quot;url&quot;:&quot;https://datawrapper.dwcdn.net/FVqaL/1/&quot;,&quot;thumbnail_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f2040ef9-e404-4a2b-8d80-df8c699c08e4_1220x674.png&quot;,&quot;thumbnail_url_full&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/19278a10-d149-4ea8-af2e-a4298b8cacd2_1220x674.png&quot;,&quot;height&quot;:333,&quot;title&quot;:&quot;Created with Datawrapper&quot;,&quot;description&quot;:&quot;&quot;}" data-component-name="DatawrapperToDOM"><iframe id="iframe-datawrapper" class="datawrapper-iframe" src="https://datawrapper.dwcdn.net/FVqaL/1/" width="730" height="333" frameborder="0" scrolling="no"></iframe><script type="text/javascript">!function(){"use strict";window.addEventListener("message",(function(e){if(void 0!==e.data["datawrapper-height"]){var t=document.querySelectorAll("iframe");for(var a in e.data["datawrapper-height"])for(var r=0;r<t.length;r++){if(t[r].contentWindow===e.source)t[r].style.height=e.data["datawrapper-height"][a]+"px"}}}))}();</script></div><p>The ETFs belong to different asset classes/regions To allow me to control overall exposures, I assign them to an upper level &#8220;allocation bucket&#8221;. I would note that both my definitions of  asset class and allocations buckets involve a degree of subjectivity. For example, I could have put REITs, Commodities, and Gold into one big bucket and called it &#8220;Alternatives&#8221;, but I preferred to split real estate from commodities and gold. </p><p>The ETFs all have different inception dates. To extend the backtest as much as possible, I first download total return series for the ETFs as well as their corresponding indices from Bloomberg. Then, for each ETF, if the ETF data starts after the index data, I backfill the series using the index returns. This gives me synthetic series starting in 2000 for almost all ETFs, the exceptions being VNQI (start date: 02/01/2001) and BNDX (start date: 04/01/2013). </p><p>After completing this step, I end up with the following series:</p><div id="datawrapper-iframe" class="datawrapper-wrap outer" data-attrs="{&quot;url&quot;:&quot;https://datawrapper.dwcdn.net/YDB4T/1/&quot;,&quot;thumbnail_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/19edc797-d045-4417-abf9-fa726e4cc84e_1220x878.png&quot;,&quot;thumbnail_url_full&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/46a7e264-2c55-4568-bb15-1dcba46a373b_1220x878.png&quot;,&quot;height&quot;:435,&quot;title&quot;:&quot;Created with Datawrapper&quot;,&quot;description&quot;:&quot;&quot;}" data-component-name="DatawrapperToDOM"><iframe id="iframe-datawrapper" class="datawrapper-iframe" src="https://datawrapper.dwcdn.net/YDB4T/1/" width="730" height="435" frameborder="0" scrolling="no"></iframe><script type="text/javascript">!function(){"use strict";window.addEventListener("message",(function(e){if(void 0!==e.data["datawrapper-height"]){var t=document.querySelectorAll("iframe");for(var a in e.data["datawrapper-height"])for(var r=0;r<t.length;r++){if(t[r].contentWindow===e.source)t[r].style.height=e.data["datawrapper-height"][a]+"px"}}}))}();</script></div><div><hr></div><h1>Setting Up the Backtest</h1><p>I use 3 years of daily data to estimate inputs for all methods, so the backtests start in 2003. On each rebalance date, I require an ETF to have available data over the last 3 years. Portfolios are rebalanced at the end of each month, and held for one month. </p><p>To avoid extreme allocations to individual assets/buckets, I constrain the optimizer for most optimizations to avoid solutions that drift too far from a diversified multi-asset allocation. At the allocation bucket level, I use the following constraints:</p><div id="datawrapper-iframe" class="datawrapper-wrap outer" data-attrs="{&quot;url&quot;:&quot;https://datawrapper.dwcdn.net/SiQRc/1/&quot;,&quot;thumbnail_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/cd7e6276-702a-49bf-899a-c91a12d4cbdc_1220x472.png&quot;,&quot;thumbnail_url_full&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/72a968a4-1440-4944-bc7c-4428751b4bf8_1220x542.png&quot;,&quot;height&quot;:265,&quot;title&quot;:&quot;Bucket Constraints&quot;,&quot;description&quot;:&quot;&quot;}" data-component-name="DatawrapperToDOM"><iframe id="iframe-datawrapper" class="datawrapper-iframe" src="https://datawrapper.dwcdn.net/SiQRc/1/" width="730" height="265" frameborder="0" scrolling="no"></iframe><script type="text/javascript">!function(){"use strict";window.addEventListener("message",(function(e){if(void 0!==e.data["datawrapper-height"]){var t=document.querySelectorAll("iframe");for(var a in e.data["datawrapper-height"])for(var r=0;r<t.length;r++){if(t[r].contentWindow===e.source)t[r].style.height=e.data["datawrapper-height"][a]+"px"}}}))}();</script></div><p>At the individual ETF level, I use the following constraints: </p><div id="datawrapper-iframe" class="datawrapper-wrap outer" data-attrs="{&quot;url&quot;:&quot;https://datawrapper.dwcdn.net/7YJO8/1/&quot;,&quot;thumbnail_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f777d164-1d7e-4d33-9cc8-d0e7935f05a8_1220x842.png&quot;,&quot;thumbnail_url_full&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/12e6d953-0488-4495-9979-b4c7c42c6ac2_1220x912.png&quot;,&quot;height&quot;:453,&quot;title&quot;:&quot;ETF Constraints&quot;,&quot;description&quot;:&quot;&quot;}" data-component-name="DatawrapperToDOM"><iframe id="iframe-datawrapper" class="datawrapper-iframe" src="https://datawrapper.dwcdn.net/7YJO8/1/" width="730" height="453" frameborder="0" scrolling="no"></iframe><script type="text/javascript">!function(){"use strict";window.addEventListener("message",(function(e){if(void 0!==e.data["datawrapper-height"]){var t=document.querySelectorAll("iframe");for(var a in e.data["datawrapper-height"])for(var r=0;r<t.length;r++){if(t[r].contentWindow===e.source)t[r].style.height=e.data["datawrapper-height"][a]+"px"}}}))}();</script></div><p>In my view, these constraints embed a relative flexible asset allocation policy, while also preventing overly concentrated portfolios. </p><p>I used simple historical estimates for expected returns and Ledoit-Wolf shrinkage estimator for the covariance matrix.<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-1" href="#footnote-1" target="_self">1</a> All optimizations were done using the <code>skfolio</code> python package. I clean-up weights to remove very small weights, and use some reasonable failsafes in case optimizations fails. </p><div><hr></div><h1>The Models</h1><p>I consider the following allocation models. With the exception of the risk budget portfolio, all other optimizations are done using the constraints discussed previously. Also, it should be noted that both the Global 60/40 Benchmark and the 1/<em>N</em> portfolio are feasible under the set of constraints. </p><h3>1. Global 60/40 Benchmark: </h3><p>A simple strategic allocation: 40% U.S. equities, 20% international equities, and 40% bonds, with a global tilt through EFA, EEM, and BNDX. The allocations within equity (SPY, EFA, EEM) and fixed income (AGG, BNDX) represent loosely the breakdown of the market cap of the markets represented by the ETFs.<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-2" href="#footnote-2" target="_self">2</a>  </p><div id="datawrapper-iframe" class="datawrapper-wrap outer" data-attrs="{&quot;url&quot;:&quot;https://datawrapper.dwcdn.net/DFqNn/1/&quot;,&quot;thumbnail_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/1380cab0-a157-4976-b5bf-7e1bb4c9d297_1220x546.png&quot;,&quot;thumbnail_url_full&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/71e70a17-ff4e-4948-a34c-d53967ed89a4_1220x616.png&quot;,&quot;height&quot;:303,&quot;title&quot;:&quot;Global 60/40 Allocations&quot;,&quot;description&quot;:&quot;&quot;}" data-component-name="DatawrapperToDOM"><iframe id="iframe-datawrapper" class="datawrapper-iframe" src="https://datawrapper.dwcdn.net/DFqNn/1/" width="730" height="303" frameborder="0" scrolling="no"></iframe><script type="text/javascript">!function(){"use strict";window.addEventListener("message",(function(e){if(void 0!==e.data["datawrapper-height"]){var t=document.querySelectorAll("iframe");for(var a in e.data["datawrapper-height"])for(var r=0;r<t.length;r++){if(t[r].contentWindow===e.source)t[r].style.height=e.data["datawrapper-height"][a]+"px"}}}))}();</script></div><h3><strong>2. 1/</strong><em><strong>N</strong></em><strong> Portfolio</strong></h3><p>This is an equally weighted portfolio of all available assets on each month. Note that applying 1/<em>N</em> across these ETFs/asset classes is not an innocuous or view-free allocation decision. It implies the following allocations: 33.33% to equity, 22.2% to bonds, 22.22% to REITs, and 22.22% to Commodities/Gold (11.11% each). </p><h3><strong>3. Minimum Variance Portfolio (MVP)</strong></h3><p>This portfolio chooses weights to minimize overall portfolio volatility available within the constraints.</p><h3><strong>4. Mean-Variance Optimization with Target Volatility (MVO </strong><em><strong>&#963;</strong></em><strong>=10%)</strong> </h3><p>This portfolio chooses the highest-return it can find while targeting a fixed volatility level of 10%.</p><h3>5. Mean-Semivariance Portfolio (Mean-SV)</h3><p>This portfolio optimizes expected return subject to a target semivariance equal to the realized semi-variance of the Global 60/40 portfolio. The semi-variance is calculated relative to a target return of zero. </p><h3>6. Mean-CVaR Portfolio (Mean-CVaR)</h3><p>This portfolio optimizes with respect to tail risk rather than volatility. CVaR measures the average loss in the worst part of the return distribution, so this approach is designed to be more sensitive to extreme downside events. I use a confidence level &#946;=0.95 and a target CVaR equal to the CVaR of the Global 60/40 portfolio.</p><h3>7. Maximum Diversification Portfolio (MDP)</h3><p>This portfolio maximizes the diversification ratio, favoring assets that contribute distinct risk exposures rather than moving closely together. </p><h3>8. Risk Budget Portfolio (RB)</h3><p>This portfolio allocates portfolio risk, rather than capital, across assets. Note that the risk budget approach is sensitive to the choice of the assets. For this reason, I decided to implement risk budgets across the allocation buckets: </p><ul><li><p>Equities: 40% (split equally between SPY, EFA, and EEM)</p></li><li><p>Fixed Income: 40% (split equally between AGG and BNDX)</p></li><li><p>Real Estate: 10% (split equally between IYR and VNQI)</p></li><li><p>Commodities/Gold: 10% (split equally between DBC and GLD)</p></li></ul><p>This results in ETF-level risk budgets that vary between 5% to 20%. Note that a risk parity solution at the ETF level would allocate 11.1% (1/9) to each ETF, which would result in a different risk budget at the bucket level.<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-3" href="#footnote-3" target="_self">3</a>  It should also be noted that for the RB portfolio, the asset-level and bucket constraints on portfolio weights do not apply.</p><div><hr></div><h1>Results</h1><p>The backtests cover the period from February 2003 to April 2026. The table below shows summary statistics of all the models. </p><div id="datawrapper-iframe" class="datawrapper-wrap outer" data-attrs="{&quot;url&quot;:&quot;https://datawrapper.dwcdn.net/ZzxSR/1/&quot;,&quot;thumbnail_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f117cdb5-b91b-41cd-9bb9-1ff7db4f5a09_1220x932.png&quot;,&quot;thumbnail_url_full&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/78721704-2b95-47f0-a95b-28b9c4713715_1220x1002.png&quot;,&quot;height&quot;:523,&quot;title&quot;:&quot;Return Statistics&quot;,&quot;description&quot;:&quot;&quot;}" data-component-name="DatawrapperToDOM"><iframe id="iframe-datawrapper" class="datawrapper-iframe" src="https://datawrapper.dwcdn.net/ZzxSR/1/" width="730" height="523" frameborder="0" scrolling="no"></iframe><script type="text/javascript">!function(){"use strict";window.addEventListener("message",(function(e){if(void 0!==e.data["datawrapper-height"]){var t=document.querySelectorAll("iframe");for(var a in e.data["datawrapper-height"])for(var r=0;r<t.length;r++){if(t[r].contentWindow===e.source)t[r].style.height=e.data["datawrapper-height"][a]+"px"}}}))}();</script></div><p>Before looking at the numbers, it&#8217;s important to note a few things: </p><ul><li><p>Since the strategies have different levels of risk, comparison of most statistics is not appropriate. In special, comparing returns and maximum drawdowns would be misleading. </p></li><li><p>The alternative allocation models I considered have different objectives. Some of the metrics are directly related to certain objectives; others are not. For example, none of the methods directly relate to maximum drawdown. While we can certainly analyze the realized maximum drawdowns, we can&#8217;t really make any conclusions about the methods in general based on this.  </p></li><li><p>In the case of the risk-based models (MVP, MDP, and RB) in particular, it&#8217;s important in my view to look at statistics related to what these methods try to achieve.  For example, within the methods that implement the constraints, MVP achieves the lowest volatility, which suggests the objective of this model is attained.<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-4" href="#footnote-4" target="_self">4</a>  Likewise, when looking at MDP, we should probably look at other metrics that directly relate to the diversification ratio. For RB, the primary objective is to attain the desired risk budget, which the method does. </p></li></ul><p>In order to make the strategies easier to compare, the table below shows the same statistics, but with the volatility of all strategies scaled to 10%. The best performing strategies in terms of risk-adjusted ratio metrics (Sharpe and Sortino ratio) are the mean-risk optimizations, which all produce very similar results.  </p><div id="datawrapper-iframe" class="datawrapper-wrap outer" data-attrs="{&quot;url&quot;:&quot;https://datawrapper.dwcdn.net/aZgU0/2/&quot;,&quot;thumbnail_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/19530575-caf6-419a-a051-bc5e4fd539f9_1220x932.png&quot;,&quot;thumbnail_url_full&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5fe144f5-8c29-4b2a-93db-5aa1d2d3f16d_1220x1002.png&quot;,&quot;height&quot;:506,&quot;title&quot;:&quot;Return Statistics (volatility scaled to 10%)&quot;,&quot;description&quot;:&quot;&quot;}" data-component-name="DatawrapperToDOM"><iframe id="iframe-datawrapper" class="datawrapper-iframe" src="https://datawrapper.dwcdn.net/aZgU0/2/" width="730" height="506" frameborder="0" scrolling="no"></iframe><script type="text/javascript">!function(){"use strict";window.addEventListener("message",(function(e){if(void 0!==e.data["datawrapper-height"]){var t=document.querySelectorAll("iframe");for(var a in e.data["datawrapper-height"])for(var r=0;r<t.length;r++){if(t[r].contentWindow===e.source)t[r].style.height=e.data["datawrapper-height"][a]+"px"}}}))}();</script></div><p>The animation below shows the equity curves for all vol-scaled strategies. </p><div class="native-video-embed" data-component-name="VideoPlaceholder" data-attrs="{&quot;mediaUploadId&quot;:&quot;54621622-92e5-4eff-87b7-11b4305fb293&quot;,&quot;duration&quot;:null}"></div><p>The graphs below show the allocations on each rebalance date. A few points to note: </p><ul><li><p>MVP allocates significantly to bonds, as expected. Over the second half of the sample, the bucket level constraint is binding most of the time.</p></li><li><p>The mean-risk (MVO, Mean-SV, Mean-CVaR) allocations follow almost identical patterns, with more volatility in allocations over time compared with MDP and RB. </p></li><li><p>MVP and MDP do not allocate to REITs at all over the second half of the sample. This is explained by the fact that the correlation between REITs and equity is significantly higher over the second period. As a result, there is little diversification to be gained by adding REITs to the portfolio.</p></li><li><p>The allocations of RB are very bond-heavy (as expected) and generally stable over time. </p></li></ul><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!pBMe!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b921877-6ea6-4df7-a155-658598e6acae_3162x2822.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!pBMe!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b921877-6ea6-4df7-a155-658598e6acae_3162x2822.png 424w, https://substackcdn.com/image/fetch/$s_!pBMe!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b921877-6ea6-4df7-a155-658598e6acae_3162x2822.png 848w, https://substackcdn.com/image/fetch/$s_!pBMe!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b921877-6ea6-4df7-a155-658598e6acae_3162x2822.png 1272w, https://substackcdn.com/image/fetch/$s_!pBMe!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b921877-6ea6-4df7-a155-658598e6acae_3162x2822.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!pBMe!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b921877-6ea6-4df7-a155-658598e6acae_3162x2822.png" width="1456" height="1299" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2b921877-6ea6-4df7-a155-658598e6acae_3162x2822.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1299,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:536444,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://systematicallybiased.substack.com/i/199367837?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b921877-6ea6-4df7-a155-658598e6acae_3162x2822.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!pBMe!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b921877-6ea6-4df7-a155-658598e6acae_3162x2822.png 424w, https://substackcdn.com/image/fetch/$s_!pBMe!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b921877-6ea6-4df7-a155-658598e6acae_3162x2822.png 848w, https://substackcdn.com/image/fetch/$s_!pBMe!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b921877-6ea6-4df7-a155-658598e6acae_3162x2822.png 1272w, https://substackcdn.com/image/fetch/$s_!pBMe!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b921877-6ea6-4df7-a155-658598e6acae_3162x2822.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption"></figcaption></figure></div><p>The table below shows the average monthly turnover of the models, computed from drifted weights and assuming a conservative one-way transaction cost of 25bps.<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-5" href="#footnote-5" target="_self">5</a>  Mean-risk approaches have higher turnover compared to other strategies, but the transaction costs remain very manageable. </p><div id="datawrapper-iframe" class="datawrapper-wrap outer" data-attrs="{&quot;url&quot;:&quot;https://datawrapper.dwcdn.net/mSiMY/2/&quot;,&quot;thumbnail_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/469a9e47-1aa0-4dd0-8de4-3fcaedccc13c_1220x768.png&quot;,&quot;thumbnail_url_full&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/34d724b6-5501-40b7-bb32-babcea75d416_1220x838.png&quot;,&quot;height&quot;:421,&quot;title&quot;:&quot;Average Monthly Turnover&quot;,&quot;description&quot;:&quot;&quot;}" data-component-name="DatawrapperToDOM"><iframe id="iframe-datawrapper" class="datawrapper-iframe" src="https://datawrapper.dwcdn.net/mSiMY/2/" width="730" height="421" frameborder="0" scrolling="no"></iframe><script type="text/javascript">!function(){"use strict";window.addEventListener("message",(function(e){if(void 0!==e.data["datawrapper-height"]){var t=document.querySelectorAll("iframe");for(var a in e.data["datawrapper-height"])for(var r=0;r<t.length;r++){if(t[r].contentWindow===e.source)t[r].style.height=e.data["datawrapper-height"][a]+"px"}}}))}();</script></div><div><hr></div><h1>Final Thoughts</h1><p>The objective of this post was to show a practical implementation of alternative methods to mean-variance optimization (MVO) for a diversified asset universe covering major asset classes. In this backtest, alternative mean-risk portfolio construction approaches, such as mean-semivariance and mean-CVaR, produced almost identical results to MVO. After scaling the strategies to the same volatility, the mean-risk optimization models also delivered better risk-adjusted performance than simpler allocations, including a global 60/40 benchmark and the 1/<em>N</em> allocation, as well as other risk-based approaches, such as risk budget and maximum diversification.</p><div class="callout-block" data-callout="true"><p>Once embbeded inside a sensible asset-allocation policy, mean-risk optimizations, including MVO, worked well.</p></div><p>I started this post with no prior expectation for how MVO would perform relative to the other methods. In fact, I have no horse in this race, and I believe we should follow the empirical evidence. Despite being often criticized, MVO performed well in this application, even though I relied on simple historical estimates of expected returns and imposed only relatively simple constraints. Of course, this does not mean that MVO is appropriate in every situation, or that more sophisticated methods cannot deliver better solutions, particularly if investors have specific preferences about risk and return.</p><p>One important caveat is that the mean-risk optimizations use historical expected returns. In a nine-asset universe with strong long-run differences across realized asset-class returns, this can make a lot of difference. The result should therefore not be interpreted as evidence that historical mean returns are generally reliable forecasts. Rather, it shows that in this particular universe, sample period, and constrained implementation, mean-risk optimization was able to exploit return differences without producing pathological portfolios.</p><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-1" href="#footnote-anchor-1" class="footnote-number" contenteditable="false" target="_self">1</a><div class="footnote-content"><p>With 3 years of daily data per asset and only 9 assets, the sample covariance matrix would have worked just as well. </p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-2" href="#footnote-anchor-2" class="footnote-number" contenteditable="false" target="_self">2</a><div class="footnote-content"><p>On the periods prior to BNDX availability, the strategy allocates 40% to AGG.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-3" href="#footnote-anchor-3" class="footnote-number" contenteditable="false" target="_self">3</a><div class="footnote-content"><p>The risk budget in this case would be as below. I don&#8217;t like this allocation as it gives over 40% of the total risk budget to Commodities and Gold. </p><ul><li><p>Equities: 33.3% </p></li><li><p>Fixed Income: 22.2%</p></li><li><p>Real Estate: 22.2%</p></li><li><p>Commodities/Gold: 22.2%</p></li></ul></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-4" href="#footnote-anchor-4" class="footnote-number" contenteditable="false" target="_self">4</a><div class="footnote-content"><p>The fact that RB produces lower volatility than MVP is explained by the fact that the RB portfolio is not subject to the same constraints. Since fixed income has lower risk, RB allocates more to this bucket to balance the risk contributions.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-5" href="#footnote-anchor-5" class="footnote-number" contenteditable="false" target="_self">5</a><div class="footnote-content"><p>I estimate transaction costs as follows (example for MVO <em>&#963;</em>=10%):</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;\\begin{align}\n\\text{Annualized TC} &amp;= \\text{Average Turnover} \\times \\text{Rebalances Per Year} \\times \\frac{\\text{bps}}{10{,}000}\\\\\n&amp;= 0.0635\\times 12 \\times \\frac{25}{10{,}000}=0.1905\\%\n\\end{align}&quot;,&quot;id&quot;:&quot;AGQLOPWMMN&quot;}" data-component-name="LatexBlockToDOM"></div></div></div>]]></content:encoded></item><item><title><![CDATA[Beyond Mean-Variance Optimization]]></title><description><![CDATA[Seven ways investors can avoid forecasts, redefine risk, or diversify differently]]></description><link>https://systematicallybiased.substack.com/p/beyond-mean-variance-optimization</link><guid isPermaLink="false">https://systematicallybiased.substack.com/p/beyond-mean-variance-optimization</guid><dc:creator><![CDATA[Systematically Biased]]></dc:creator><pubDate>Mon, 25 May 2026 17:26:03 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/6532f4e9-d71d-4c57-b9fd-c6b59e58e7d7_1920x1080.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Obs: this post contains some formulas that don&#8217;t display properly on mobile. They do display correctly on desktop. </p><p>In a <a href="https://systematicallybiased.substack.com/p/portfolio-optimization-what-could?r=6w89b">previous post</a>, I discussed mean-variance optimization (MVO), and some of the many things that can go wrong when MVO is implemented naively. A big issue with MVO or indeed any portfolio optimization method is estimation error. The animation below shows how unstable the MVO can be when we resample the data. </p><div class="native-video-embed" data-component-name="VideoPlaceholder" data-attrs="{&quot;mediaUploadId&quot;:&quot;e10fc500-d172-4dce-9a78-3a7006bc7f35&quot;,&quot;duration&quot;:null}"></div><p>One of the tricks commonly used in practice to stabilize MVO solutions is the use of constraints, which is related to regularization or shrinkage. <a href="https://scholar.archive.org/work/x4vdsep3mrdqhc56nmgt6d26ga/access/wayback/http://www.hec.fr/heccontent/download/3867/104597/version/2/file/72.pdf">Jagannathan and Ma (2003)</a> showed that adding long-only constraints to the optimization problem is equivalent to using a modified covariance matrix, with the effect of shrinking the largest elements of the covariance matrix. If large covariances are due to estimation error, this helps because the shrunk covariance matrix becomes more precise. </p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://systematicallybiased.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">This Substack is reader-supported. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>There are other ways to achieve this shrinkage, notably by using a more structured estimator for the covariance matrix (e.g. an index model), or by explicitly shrinking the sample covariance estimator towards such a structured target. In cases where enough data are available, or if the optimization problem is already constrained, as in many practical cases, the choice of covariance estimator is much less critical.<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-1" href="#footnote-1" target="_self">1</a> </p><p>This post is going to look at some alternatives to MVO. The papers mentioned in the post are shown in the references section at the end, and most have presentation decks in the <a href="https://systematicallybiased.substack.com/s/paper-library-and-decks">Systematically Biased Library</a>. </p><p>But before we dive into the alternatives, let&#8217;s take two steps back to consider:</p><ul><li><p>The measure of risk used in MVO.</p></li><li><p>Some misconceptions about MVO that regularly pop up. </p></li></ul><div><hr></div><h1>Risk &#8800; Variance</h1><p>Textbook MVO equates risk with variance. One formulation of MVO is in terms of maximizing the mean-variance criterion:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;\\max_{w}{\\underbrace{w'\\mu}_{\\text{expected return}} -\\frac{1}{2}\\gamma \\underbrace{w'\\Sigma w}_{\\text{variance}}} \\quad \\text{s.t.} \\quad w' \\mathbf{1}=1&quot;,&quot;id&quot;:&quot;KYBWKWMBGI&quot;}" data-component-name="LatexBlockToDOM"></div><p>where <em>&#947;</em> is a risk aversion parameter. This formulation says that investor utility increases with the portfolio expected return and  decreases with its variance.<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-2" href="#footnote-2" target="_self">2</a>  </p><p>However, variance as a measure of risk has shortcomings:</p><ol><li><p>Variance penalizes large positive returns as much as large negative returns. </p></li><li><p>Variance measures deviations from the mean, which may not be the appropriate level of return targeted by the investor. </p></li></ol><p>Regarding the first point, most investors are not worried about large positive returns. What really matters is <strong>downside risk</strong>: the occurrence of large, negative returns. Therefore, if returns are not symmetrically distributed, using the variance may not be an appropriate way to measure risk. </p><p>The second issue is more subtle. Variance is a statistical measure of dispersion, and the first moment (the expected value) is a natural reference point. However, when it comes to investments or measuring risk, there&#8217;s no <em>a priori</em> reason why we should measure deviations relative to the expected return. The investor may have a different benchmark level of return (say, the risk-free rate of return, or a fixed level of return like 5%).</p><p>In sum, if returns are not symmetrically distributed about the mean, or if the benchmark level of return is not the mean, variance does not in general coincide with downside risk.</p><p>One possibility, in this case, is to use another risk measure. A natural candidate is to replace variance with <strong>semivariance</strong>, which measures deviations only below a benchmark level of return <em>B</em>, and likewise replace covariances with <strong>semicovariances</strong>. In fact, Markowitz (1959) had already recognized that semivariance is a more plausible measure of risk than the variance. </p><div class="native-video-embed" data-component-name="VideoPlaceholder" data-attrs="{&quot;mediaUploadId&quot;:&quot;ecae99f3-9aa5-404e-bd43-5d34d2f4b01a&quot;,&quot;duration&quot;:null}"></div><p>Semivariance is not the only option. Another commonly used approach is to use a tail risk measure, such as the <a href="https://en.wikipedia.org/wiki/Expected_shortfall">conditional Value at Risk (CVaR)</a>. We discuss both mean-semivariance and mean-CVaR approaches later on.</p><p>Before turning to alternatives, it is worth clearing up two misconceptions that repeatedly appear whenever MVO is discussed. </p><div><hr></div><h1>Myths about MVO</h1><p>It&#8217;s important to remember that the estimation error issues discussed above are not specific to MVO. As a general rule, using noisy inputs (either for <em>&#181;</em> or <em>&#931;</em>) can lead to poor results in any optimization problem, especially if it&#8217;s unconstrained. But MVO is often the target of these criticisms, even when the results being discussed have been obtained in a naive setting (<em>&#181;</em> estimated from historical returns; no or unreasonable constraints). </p><p>But there are other persistent myths about MVO, and I recommend the article by <a href="https://www.researchgate.net/profile/Gordon-Ritter/publication/381902344_Untangling_Universality_and_Dispelling_Myths_in_Mean-Variance_Optimization/links/6717bd7c09ba2d0c76180b44/Untangling-Universality-and-Dispelling-Myths-in-Mean-Variance-Optimization.pdf">Benveniste, Kolm, and Ritter (2024)</a> for a discussion of those. I&#8217;ll focus here on two of those myths that are probably the most persistent. The first one is that MVO requires assuming normally distributed asset returns. The second one is that it requires assuming investors have a quadratic utility function. I think one of the reasons that these myths persist is that these assumptions indeed imply mean-variance preferences. However, they are not needed for MVO to be a reasonable approach. Let&#8217;s take a quick look at each. </p><h3>Normally Distributed Returns</h3><p>I don&#8217;t think that discussing the unreasonableness of assuming normally distributed asset returns is necessary. But if asset returns were normally distributed, then the entire return distribution would be characterized by its mean and variance. Any expected-utility comparison among normally distributed portfolios would therefore reduce to a comparison involving those two moments. While assuming normally distributed returns gives you mean-variance preferences, it is not needed. What matters is the maximization of expected utility. <a href="https://www.researchgate.net/profile/Gordon-Ritter/publication/381902344_Untangling_Universality_and_Dispelling_Myths_in_Mean-Variance_Optimization/links/6717bd7c09ba2d0c76180b44/Untangling-Universality-and-Dispelling-Myths-in-Mean-Variance-Optimization.pdf">Benveniste, Kolm, and Ritter (2024)</a> define a class of mean-variance equivalent distributions, which have the property that the expected utility maximization for any standard utility function coincides with the maximum of an equivalent MVO problem. Many distributions, including heavy tailed distributions, elliptical distributions, and even some skewed distributions, are mean-variance equivalent.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!61gd!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1a0e9d57-1682-4d2a-bed0-d778fd2740b2_887x655.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!61gd!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1a0e9d57-1682-4d2a-bed0-d778fd2740b2_887x655.png 424w, https://substackcdn.com/image/fetch/$s_!61gd!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1a0e9d57-1682-4d2a-bed0-d778fd2740b2_887x655.png 848w, https://substackcdn.com/image/fetch/$s_!61gd!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1a0e9d57-1682-4d2a-bed0-d778fd2740b2_887x655.png 1272w, https://substackcdn.com/image/fetch/$s_!61gd!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1a0e9d57-1682-4d2a-bed0-d778fd2740b2_887x655.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!61gd!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1a0e9d57-1682-4d2a-bed0-d778fd2740b2_887x655.png" width="887" height="655" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/1a0e9d57-1682-4d2a-bed0-d778fd2740b2_887x655.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:655,&quot;width&quot;:887,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:126374,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://systematicallybiased.substack.com/i/198770905?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1a0e9d57-1682-4d2a-bed0-d778fd2740b2_887x655.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!61gd!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1a0e9d57-1682-4d2a-bed0-d778fd2740b2_887x655.png 424w, https://substackcdn.com/image/fetch/$s_!61gd!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1a0e9d57-1682-4d2a-bed0-d778fd2740b2_887x655.png 848w, https://substackcdn.com/image/fetch/$s_!61gd!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1a0e9d57-1682-4d2a-bed0-d778fd2740b2_887x655.png 1272w, https://substackcdn.com/image/fetch/$s_!61gd!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1a0e9d57-1682-4d2a-bed0-d778fd2740b2_887x655.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Example of a mean-variance equivalent distribution from <a href="https://www.researchgate.net/profile/Gordon-Ritter/publication/381902344_Untangling_Universality_and_Dispelling_Myths_in_Mean-Variance_Optimization/links/6717bd7c09ba2d0c76180b44/Untangling-Universality-and-Dispelling-Myths-in-Mean-Variance-Optimization.pdf">Benveniste, Kolm, and Ritter (2024)</a> </figcaption></figure></div><h3>Quadratic Utility</h3><p>Quadratic utility indeed implies mean-variance preferences, but it also has some economically unreasonable properties. The most relevant is that quadratic utility implies increasing absolute risk aversion. This means that investors would invest less in risky assets as their wealth increases, i.e. risky assets are inferior goods. </p><p>As with the normality assumption, quadratic utility implies mean-variance preferences, but is not necessary for MVO to be a reasonable solution to a portfolio allocation problem. Indeed, the mean-variance criterion stated above is not a utility function, but rather an approximation of the expected utility. Under a second-order approximation to expected utility, expected return enters positively and variance enters negatively, with the coefficient on variance governed by risk aversion.<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-3" href="#footnote-3" target="_self">3</a> </p><p>The rest of this post is going to discuss some alternatives to MVO. </p><div><hr></div><h1>Alternative #0: Keep it Simple (SAA)</h1><p>The simplest alternative, which existed long before Markowitz proposed MVO, is not to optimize at all. Investors can simply define an asset allocation policy that makes sense in the long run and rebalance their portfolios periodically to maintain it. Of course, defining these allocations requires some assumptions about the expected return and risk of the assets, as well as the investor&#8217;s risk preferences. </p><p>This kind of approach is sometimes referred to as <a href="https://www.investopedia.com/terms/s/strategicassetallocation.asp">strategic asset allocation (SAA)</a>. The widely used 60/40 benchmark (60% in equities, 40% in bonds) is a canonical example. Many investment products implement variations of this, including options that change the allocations to reduce the portfolio risk at a target date (e.g. for retirement). </p><p>Many different SAAs have been proposed, using different assets and asset classes. I implemented several of them in my <a href="https://github.com/rubetron/AssetAllocation">AssetAllocation</a> package for R (as well as several tactical asset allocation strategies). </p><div><hr></div><h1>Alternative #1: Keep it Simple (the 1/<em>N</em> rule)</h1><p>Another rule, which is a special case of Alternative #0 and has been extensively studied, is the &#8220;Talmudic&#8221; or &#8220;1<em>/N</em>&#8221; rule of allocating equally across investments. This simple rule has the benefits of not requiring forecasts, mechanically enforcing diversification, and keeping turnover low.<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-4" href="#footnote-4" target="_self">4</a> However, 1/<em>N</em> is not assumption-free. The main active decision is shifted from the weights to the definition of the opportunity set. </p><p>In terms of empirical performance, there is some controversy. A widely cited study by <a href="https://doi.org/10.1093/rfs/hhm075">DeMiguel, Garlappi, and Uppal (2009)</a> ran an out-of-sample horse-race between the 1/<em>N</em> portfolio and 14 mean-variance models across seven datasets, concluding that none of the models outperformed 1<em>/N.</em> Their conclusion: any  potential gains due to optimal diversification are more than offset by estimation error. </p><p>These results, however, have been questioned by some studies on two fronts:</p><ul><li><p><a href="https://doi.org/10.2469/faj.v66.n2.6">Kritzman, Page, and Turkington (2010)</a> argue that the outperformance of the 1/<em>N</em> in DeMiguel, Garlappi, and Uppal (2009) is the result of using short samples. When estimates are constructed using longer samples, or simple but reasonable assumptions, Kritzman et al find that optimized portfolios outperform 1/<em>N</em> out of sample. </p></li><li><p><a href="https://doi.org/10.1080/0015198X.2019.1600958">Allen, Lizieri, and Satchell (2019)</a> make the point that, if investors have even modest forecasting ability, they can benefit substantially from MVO, which they substantiate analytically, via simulation, and empirically through out-of-sample comparisons.</p></li></ul><div class="callout-block" data-callout="true"><p>The 1/<em>N </em>rule allocates equally across investments. It requires no forecasts and mechanically forces diversification. Whether 1/<em>N</em> outperforms portfolios obtained using optimization models is debatable. </p><p>An example of an investment product based on the 1/<em>N</em> rule is the <a href="https://www.invesco.com/us/en/financial-products/etfs/invesco-sp-500-equal-weight-etf.html">RSP ETF</a>, which has about $87 billion in assets. </p></div><div><hr></div><h1>Alternative #2: Minimum Variance Portfolios</h1><p>The minimum variance portfolio (MVP) is the only portfolio on the mean-variance efficient frontier that doesn&#8217;t require estimation of expected returns. It is simply the solution of the problem below: <a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-5" href="#footnote-5" target="_self">5</a></p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;\\min_{w_1,\\dots,w_n}{w'\\Sigma w} \\quad \\text{s.t.} \\quad \\sum_{i=1}^n{w_i}=1&quot;,&quot;id&quot;:&quot;QDWHKLFHXV&quot;}" data-component-name="LatexBlockToDOM"></div><p>Because expected returns are harder to estimate and forecast, they are a major source of estimation error in MVO or indeed of any other optimization approach that requires them as inputs. In addition, variances and covariances are more persistent, and therefore more predictable. Therefore, although in principle other portfolios on the efficient frontier may be preferable, the MVP is likely to be estimated with more precision than other efficient portfolios.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!8FPy!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3311ee68-bf17-44b3-a5d3-b0abc75ad5d3_1448x1086.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!8FPy!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3311ee68-bf17-44b3-a5d3-b0abc75ad5d3_1448x1086.png 424w, https://substackcdn.com/image/fetch/$s_!8FPy!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3311ee68-bf17-44b3-a5d3-b0abc75ad5d3_1448x1086.png 848w, https://substackcdn.com/image/fetch/$s_!8FPy!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3311ee68-bf17-44b3-a5d3-b0abc75ad5d3_1448x1086.png 1272w, https://substackcdn.com/image/fetch/$s_!8FPy!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3311ee68-bf17-44b3-a5d3-b0abc75ad5d3_1448x1086.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!8FPy!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3311ee68-bf17-44b3-a5d3-b0abc75ad5d3_1448x1086.png" width="1448" height="1086" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/3311ee68-bf17-44b3-a5d3-b0abc75ad5d3_1448x1086.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1086,&quot;width&quot;:1448,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!8FPy!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3311ee68-bf17-44b3-a5d3-b0abc75ad5d3_1448x1086.png 424w, https://substackcdn.com/image/fetch/$s_!8FPy!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3311ee68-bf17-44b3-a5d3-b0abc75ad5d3_1448x1086.png 848w, https://substackcdn.com/image/fetch/$s_!8FPy!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3311ee68-bf17-44b3-a5d3-b0abc75ad5d3_1448x1086.png 1272w, https://substackcdn.com/image/fetch/$s_!8FPy!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3311ee68-bf17-44b3-a5d3-b0abc75ad5d3_1448x1086.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">The MVP is the only portfolio on the efficient frontier that doesn&#8217;t require expected return estimates/forecasts</figcaption></figure></div><p>Finance theory makes the prediction that the market-cap weighted portfolio of securities is the optimal efficient portfolio in equilibrium, and should, in principle, outperform the MVP.</p><p>Although neat, this result is based on a list of strong assumptions, all of which are violated in practice. Therefore, even a comprehensive market-cap weighted portfolio of all stocks in the market is bound to be inefficient. This point was made by <a href="http://www.efalken.com/LowVolClassics/HaugenBaker991.pdf">Haugen and Baker (1991)</a>, who compared such a portfolio (the <a href="https://www.wilshireindexes.com/products/ft-wilshire-5000-index-series">Wilshire 5000</a>) to an MVP constructed from the largest 1000 stock in the US, with some concentration and sector constraints. The MVP achieved similar returns to the market-cap weighted index, but with lower risk. <a href="https://www.pm-research.com/content/iijpormgmt/33/1/10.full.pdf">de Silva, Clarke, and Thorley (2006)</a> confirm this result with a long backtest, which shows that the MVP has about three-quarters of the realized risk of a cap-weighted portfolio, but earns higher average returns. <a href="https://www.hillsdaleinv.com/uploads/Minimum-Variance_Portfolio_Composition,_Roger_Clarke,_Harindra_de_Silva,_Steven_Thorley1.pdf">Clarke, De Silva, and Thorley (2011)</a> further study the composition of the MVP. They show that a long-only MVP typically invests in a small number of securities, tilted towards low betas.</p><p>The surprisingly good performance of the MVP relative to market-cap weighted portfolios is therefore related to the well-known critique of the CAPM (i.e., portfolios sorted on beta have negligible differences in return). More generally, this is related to the low beta and low volatility anomalies. </p><p>It should be noted that the MVP avoids expected return forecasts, but it still depends on a risk model (volatilities, correlations) and other choices like constraints and turnover assumptions.</p><div class="callout-block" data-callout="true"><p>The MVP is the only portfolio on the mean-variance efficient frontier that doesn&#8217;t require expected returns as inputs. In many long-run empirical studies, constrained minimum-variance portfolios have delivered lower volatility and competitive, sometimes higher, realized returns than capitalization-weighted indices.</p><p>An example of an investment product based on the MVP is the <a href="https://www.blackrock.com/us/financial-professionals/products/239695/ishares-msci-usa-minimum-volatility-etf">USMV ETF</a> (about $24 billion in assets).</p></div><div><hr></div><h1>Alternative 3: Mean-Risk Optimization</h1><p>As discussed above, the variance has some shortcomings as a risk measure. There are several alternative risk measures that can be used to replace the variance. The two most commonly used in practice are the semi-variance and the <a href="https://en.wikipedia.org/wiki/Expected_shortfall">conditional Value at Risk (CVaR)</a>.</p><h3>Mean-Semivariance Optimization</h3><p>The <strong>semivariance</strong> is similar to the variance, but considers only returns below a benchmark level of return <em>B</em>:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;SV=\\mathbb{E}\\left[\\left\\{(R-B)\\mathbb{1}_{r-b<0}\\right\\}^2\\right]&quot;,&quot;id&quot;:&quot;MVIYKLAEYR&quot;}" data-component-name="LatexBlockToDOM"></div><p>Intuitively, mean-semivariance optimization tries to find portfolios with attractive expected returns while penalizing only the observations in which the portfolio falls below the benchmark. This makes the relevant downside observations endogenous: changing the weights changes which scenarios count as downside scenarios. </p><p>For this reason, mean-semivariance optimization is not as simple as mean-variance optimization. The issue is that the semivariance of a portfolio cannot be written as a quadratic form, precluding the use of quadratic programming. </p><p>This problem can be resolved by noting that, within the regions where assets underperform the benchmark, the semivariance can be written in a quadratic form. This approach, presented by <a href="https://www.hudsonbaycapital.com/documents/FG/hudsonbay/research/599440_paper.pdf">Markowitz et al. (2020)</a>, is the typical implementation used in practice. It relies on introducing a matrix of excess returns or deviations relative to the benchmark. </p><p></p><h3>Mean-CVaR Optimization</h3><p>To talk about conditional Value-at-Risk (CVaR), we first need to define the Value-at-Risk. Loosely speaking, the VaR is a loss that we&#8217;re fairly sure won&#8217;t be exceeded over some horizon. For example, suppose that the level of confidence is 90%.<em> </em>If the daily VaR of a portfolio with a 90% confidence level is $1 million, we are 90% confident that we won&#8217;t lose more than $1 million on any given day. Conversely, we should expect to lose more than $1 million on 10% of days. </p><p>VaR has some shortcomings as a way to measure risk. Notably, VaR is not a <strong><a href="https://en.wikipedia.org/wiki/Coherent_risk_measure">coherent </a></strong><a href="https://en.wikipedia.org/wiki/Coherent_risk_measure">risk measure</a>. Particularly, VaR does not respect the sub-additivity property of a coherent risk measure, which requires that the risk measure applied to the sum of two portfolios must be at most equal to the sum of the risk measures applied to each portfolio. The consequence is that VaR may discourage diversification. </p><p>Another problem with VaR is that it only gives us a level of loss that we should not expect to exceed with some confidence, but it tells us nothing about what level of loss to expect when we do exceed it. The CVaR, or expected shortfall, gives you exactly that. </p><p>CVaR (also known as expected shortfall) is a tail risk measure. It tells us how much we expect to lose, <strong>given that the loss exceeds the VaR</strong>. The animation below shows examples of VaR and CVaR. Note that this is shown using the distribution of returns (i.e., negative values correspond to losses). </p><div class="native-video-embed" data-component-name="VideoPlaceholder" data-attrs="{&quot;mediaUploadId&quot;:&quot;d4445dd8-2fc5-4e71-94d6-62d8298a2e04&quot;,&quot;duration&quot;:null}"></div><p>To define VaR and CVaR mathematically, we need some notation:</p><ul><li><p><em>w : </em>vector of portfolio weights</p></li><li><p><em>r </em>: vector of asset returns</p></li><li><p><em>&#946; </em>&#8712; (0,1) : confidence level</p></li><li><p><em>L</em>(<em>w</em>, <em>r</em>) = <em>-w&#8217;r: </em>portfolio loss (negative of portfolio return)</p></li><li><p> <em>p</em>(<em>r</em>) : probability density function of returns</p></li><li><p>&#936;<em><sub>L</sub></em>(<em>y</em>): cumulative distribution function of losses (the probability of not exceeding a threshold loss <em>y</em>).<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-6" href="#footnote-6" target="_self">6</a> </p></li><li><p>&#945;<em><sub>&#946;</sub></em>(<em>w</em>): the VaR of portfolio <em>w</em> at confidence level <em>&#946;.</em></p></li></ul><p>Note that we went from working with returns to working with losses. This means the distribution shown above would be flipped, with large positive values corresponding to large losses. With this notation, the VaR at confidence level <em>&#946;</em> is defined as the smallest loss such that the probability of not exceeding it is at least <em>&#946;: </em> </p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;\\alpha_\\beta(w)=\\text{VaR}_\\beta(w)=\\min\\{y\\in \\mathbb{R}:\\Psi_L(y)>\\beta\\}&quot;,&quot;id&quot;:&quot;PBFWVMMJEV&quot;}" data-component-name="LatexBlockToDOM"></div><p>Now that we have defined VaR, we can define the CVaR mathematically as: </p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;\\text{CVaR}_\\beta(w)=\\frac{1}{1-\\beta}\\int_{L(w,r)\\geq \\alpha_\\beta(w)}{L(w,r)p(r)dr}&quot;,&quot;id&quot;:&quot;IIBUTNUTXZ&quot;}" data-component-name="LatexBlockToDOM"></div><p>The expression above resembles the expected loss. Indeed, the CVaR<em><sub>&#946; </sub></em>is a specialization of the expected value in which we&#8217;re averaging only over the worst (1-<em>&#946;</em>) fraction of losses.<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-7" href="#footnote-7" target="_self">7</a> </p><p>Portfolio optimization using the CVaR is complicated, because the CVaR depends on an integral over VaR values. However, <a href="https://www.risknet.de/fileadmin/eLibrary/Rockafellar-Conditional-Value-at-Risk.pdf">Rockafellar and Uryasev (2000)</a> proved two key results that make mean-CVaR optimization practical, effectively transforming it into a linear programming problem. Their paper rests on introducing the following function: </p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;F_\\beta(w, \\alpha)=\\alpha+\\frac{1}{1-\\beta}\\int_r{\\left[L(w,r)-\\alpha\\right]^+p(r)dr}&quot;,&quot;id&quot;:&quot;FNEZPSGXIG&quot;}" data-component-name="LatexBlockToDOM"></div><p>where[<em>x</em>]<sup>+</sup>=max(<em>x</em>,0). Their first theorem shows CVaR<em><sub>&#946;</sub></em> can be obtained as the minimum of this function in <em>&#945;</em>. Their second theorem states that minimizing<em><sub> </sub></em>CVaR<em><sub>&#946;</sub></em> over all portfolios <em>w</em> is equivalent to minimizing the function above over all values of (<em>w</em>, <em>&#945;</em>). </p><p>In practice, the integral can be approximated as a sum, using values that can be either simulated from <em>p</em>(<em>r</em>), if it&#8217;s available, or a sample (more commonly). Suppose that a sample of returns <em>r<sub>1</sub></em>,<em> r<sub>2</sub></em>, &#8230;,<em> r<sub>T</sub></em> is available. Then function can be approximated by </p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;F_\\beta(w, \\alpha)=\\alpha+\\frac{1}{(1-\\beta)T}\\sum_{t=1}^T{\\left[-w'r_t-\\alpha\\right]^+}&quot;,&quot;id&quot;:&quot;ICYVUTDSNW&quot;}" data-component-name="LatexBlockToDOM"></div><p>The optimization problem can then be written as a linear programming problem using auxiliary variables: </p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;\\min_{w,\\alpha}{\\left\\{\\alpha+\\frac{1}{(1-\\beta)T}\\sum_{t=1}^T{u_t}\\right\\}} \\quad s.t. \\left\\{\\begin{aligned} \n  u_t&amp;\\geq 0\\\\\n  u_t&amp;\\geq -w' r_t-\\alpha \n\\end{aligned}\\right.&quot;,&quot;id&quot;:&quot;VJCLOYTLFF&quot;}" data-component-name="LatexBlockToDOM"></div><div class="callout-block" data-callout="true"><p>Mean-risk approaches replace variance with other risk measures. Two commonly used approaches are the <strong>mean-semivariance</strong> and <strong>mean-CVaR</strong>. Both optimization approaches are more complicated than MVO, but can be resolved by augmenting the optimization problem through auxiliary variables.</p></div><div><hr></div><h1>Alternative 4: Maximum Diversification</h1><p>Choueifaty and Coignard (2008) note that one of the main difficulties with MVO is the need to estimate expected returns. They propose a heuristic approach based on maximizing the diversification ratio. Denote portfolio weights by <em>w, </em>the covariance matrix by <em>&#931;</em>, and <em>&#963;<sub>d</sub>=</em>diag(<em>&#931;</em>)<em><sup>1/2  </sup></em>the vector of volatilities for the assets. The diversification ratio is defined as </p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;D(w)=\\frac{w' \\sigma_d}{\\sqrt{w'\\Sigma w}}&quot;,&quot;id&quot;:&quot;YIMXUHBBJR&quot;}" data-component-name="LatexBlockToDOM"></div><p>The numerator is the weighted average of volatilities, which disregards diversification due to asset comovement. The denominator is the portfolio volatility, which accounts for asset comovement. Therefore, the diversification ratio captures the extent to which asset comovements reduce risk. They propose to compute the most diversified portfolio (MDP) by choosing <em>w </em> that maximizes this ratio. In their empirical applications, the MDP has higher Sharpe ratio than market-cap weighted indices. </p><p>MDP belongs to a class of <strong>risk-based</strong> portfolio construction approaches, which do not require estimation of expected returns. Other approaches in this category are the MVP and risk parity approaches. </p><p>A related paper is <a href="https://www.hillsdaleinv.com/uploads/Risk_Parity,_Maximum_Diversification,_and_Minimum_Variance-_An_Analytic_Perspective.pdf">Clarke, De Silva, and Thorley (2013)</a>, who derive analytical expressions for risk-based portfolios (MVP, MDP, and risk parity) under a single-index model. </p><div class="callout-block" data-callout="true"><p>The maximum diversification approach selects portfolio weights that maximize the ratio between the weighted average volatility of the individual assets and the volatility of the resulting portfolio.</p></div><div><hr></div><h1>Alternative 5: Risk Parity</h1><p>Risk parity is perhaps the most popular risk-based portfolio construction method. It is widely used by institutional investors because it provides a disciplined way to diversify risk. It is particularly popular with CTAs and trend followers to equalize how much risk each asset contributes to the overall portfolio. </p><p>A canonical example to explain risk parity is to look at a 60/40 portfolio of stocks and bonds. Using 10 years of data ending in April 2026, we get the following realized performance: </p><div id="datawrapper-iframe" class="datawrapper-wrap outer" data-attrs="{&quot;url&quot;:&quot;https://datawrapper.dwcdn.net/yOFGq/3/&quot;,&quot;thumbnail_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f52d06a9-88a2-451d-8494-859103231456_1220x324.png&quot;,&quot;thumbnail_url_full&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2c3fd94b-b444-4404-8efb-86d0e7a591aa_1220x324.png&quot;,&quot;height&quot;:155,&quot;title&quot;:&quot;Created with Datawrapper&quot;,&quot;description&quot;:&quot;&quot;}" data-component-name="DatawrapperToDOM"><iframe id="iframe-datawrapper" class="datawrapper-iframe" src="https://datawrapper.dwcdn.net/yOFGq/3/" width="730" height="155" frameborder="0" scrolling="no"></iframe><script type="text/javascript">!function(){"use strict";window.addEventListener("message",(function(e){if(void 0!==e.data["datawrapper-height"]){var t=document.querySelectorAll("iframe");for(var a in e.data["datawrapper-height"])for(var r=0;r<t.length;r++){if(t[r].contentWindow===e.source)t[r].style.height=e.data["datawrapper-height"][a]+"px"}}}))}();</script></div><p>The 60/40 portfolio has a volatility of 11.3%. However, approximately 93.5% of this total risk comes from the equity allocation. The bond allocation, while representing 40% of the total allocation, accounts for less than 10% of the risk. <strong>Risk parity</strong> focuses on finding the portfolio allocations that would result in equal risk contributions. In this example, the allocations that equalize risk contributions are 23.6% in SPY and 76.4% in BND. The resulting portfolio has a volatility of 6.39%. </p><div class="native-video-embed" data-component-name="VideoPlaceholder" data-attrs="{&quot;mediaUploadId&quot;:&quot;703c5036-eb25-4c44-9150-50f5b4a37cd5&quot;,&quot;duration&quot;:null}"></div><p>The example above uses volatility as the risk measure, but risk parity is more general. Denote by <em>R</em>(<em>w</em>) a risk measure for a portfolio <em>w</em>. The marginal risk contribution of asset <em>i</em> is defined as </p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;MRC_i= \\frac{\\partial R(w)}{\\partial w_i}&quot;,&quot;id&quot;:&quot;YDJSKALARG&quot;}" data-component-name="LatexBlockToDOM"></div><p>and the risk contribution of asset  <em>i </em>is defined as</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;RC_i=w_i MRC_i&quot;,&quot;id&quot;:&quot;RATGUSOBSI&quot;}" data-component-name="LatexBlockToDOM"></div><p>The risk measure must satisfy the Euler allocation principle, which states that risk can be decomposed as follows: </p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;R(w) =\\sum_{i=1}^n{RC_i}= \\sum_{i=1}^n{w_i MRC_i}&quot;,&quot;id&quot;:&quot;GHVYXHKYYJ&quot;}" data-component-name="LatexBlockToDOM"></div><p>In words: the total risk is the sum of the risk contributions, defined as each allocation multiplied by the derivative of the risk measure relative to the allocation. The portfolio risk can be obtained as the sum of risk contributions. </p><p>In the case of the volatility, we have </p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;\\sigma=\\sqrt{w'\\Sigma w}&quot;,&quot;id&quot;:&quot;XVNRFELMLJ&quot;}" data-component-name="LatexBlockToDOM"></div><p>and the vector of marginal risk contributions is</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;MRC=\\frac{\\Sigma w}{\\sqrt{w'\\Sigma w}}&quot;,&quot;id&quot;:&quot;ROHJXZCPZM&quot;}" data-component-name="LatexBlockToDOM"></div><p>Notice that the denominator is the same for all assets, such that the marginal risk contribution of asset <em>i </em>is:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;MRC_i=\\frac{(\\Sigma w)_i }{\\sqrt{w'\\Sigma w}}&quot;,&quot;id&quot;:&quot;FPIRVZFMYU&quot;}" data-component-name="LatexBlockToDOM"></div><p>where (&#931;<em>w</em>)<em><sub>i</sub></em> denotes the <em>i</em>-th element of &#931;<em>w</em>. We can verify that this satisfies the allocation property: </p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;\\sum_{i=1}^n{RC_i}=\\sum_{i=1}^n{w_i \\frac{(\\Sigma w)_i}{\\sqrt{w'\\Sigma w}}}=w'\\frac{\\Sigma w}{\\sqrt{w'\\Sigma w}}=\\sqrt{w'\\Sigma w}=\\sigma&quot;,&quot;id&quot;:&quot;DGGGMDDAEJ&quot;}" data-component-name="LatexBlockToDOM"></div><p>Suppose that we have a risk budget <em>b=</em>(<em>b<sub>1</sub>, &#8230;, b<sub>n</sub></em>) that defines how much risk each asset should contribute to the total risk. The risk parity case corresponds to equal risk contributions, i.e. <em>b<sub>1 </sub></em>= &#8943; = <em> b<sub>n</sub></em>. The risk budget portfolio is the solution of the following system: </p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;\\left\\{ \\begin{array} .RC_i&amp;=b_i \\\\w_i&amp;>0\\\\\\sum_i{w_i}&amp;=1 \\end{array}\\right.&quot;,&quot;id&quot;:&quot;TOXMTKGZFB&quot;}" data-component-name="LatexBlockToDOM"></div><p>Writing the Lagrangian for this problem and solving the first-order conditions, it can be shown that it is equivalent to solving the following problem: </p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;\\min_w \\sigma(w) \\quad s.t.\\quad \\left\\{\\begin{array}.\\sum_{i=1}^n{b_i\\ln{w_i}}  \\geq c \\\\w_i  > 0, i=1,\\dots, n\\end{array}\\right.&quot;,&quot;id&quot;:&quot;EZLZBHFDVN&quot;}" data-component-name="LatexBlockToDOM"></div><p>One important element to consider when using the risk budget approach, especially with equal risk budgets, is that the choice of the asset universe can have a significant impact on the resulting portfolio. For instance, suppose we added another equity ETF to the universe in the toy example above. Then a risk parity solution would end up allocating 2/3 of the risk budget to equities and 1/3 to bonds. A possibility in these cases is to have equal risk budgets within asset classes, and divide asset-level risk budgets equally across instruments within each asset class. </p><p>A related approach is <strong>hierarchical risk parity</strong>. Rather than solving directly for equal risk contributions across all assets, hierarchical risk parity first uses the correlation structure to cluster similar assets, and then allocates risk through the resulting hierarchy. This can make the allocation less sensitive to small changes in the covariance matrix and can avoid some of the arbitrary effects of treating all assets in the universe as exchangeable.</p><p>There&#8217;s an enormous literature on risk parity and its performance. Some interesting earlier papers are <a href="https://www.researchgate.net/profile/Jason-Hsu-5/publication/228206016_Risk_Parity_Portfolio_vs_Other_Asset_Allocation_Heuristic_Portfolios/links/00b7d532848224ae2d000000/Risk-Parity-Portfolio-vs-Other-Asset-Allocation-Heuristic-Portfolios.pdf">Chaves, Hsu, and Thorley (2011)</a>, <a href="https://pages.stern.nyu.edu/~afrazzin/pdf/Leverage%20Aversion%20and%20Risk%20Parity%20-%20Asness%20,%20Frazzini%20and%20Pedersen.pdf">Asness, Frazzini, and Pedersen (2012)</a>, and <a href="https://www.hillsdaleinv.com/uploads/Risk_Parity,_Maximum_Diversification,_and_Minimum_Variance-_An_Analytic_Perspective.pdf">Clarke, De Silva, and Thorley (2013)</a>. Hierarchical risk parity is introduced in <a href="https://papers.ssrn.com/sol3/papers.cfm?abstract_id=2708678">Lopez de Prado (2016)</a>. </p><p>Note that risk parity is usually solved as a long-only problem with <em>w<sub>i</sub></em> &gt; 0. In general, there&#8217;s no unique solution if weights are allowed to be negative. However, if we know which assets we want to short, a solution can be found by modifying the problem above. This approach is applied by <a href="https://www.pm-research.com/content/iijpormgmt/48/4/241">Rubesam (2022)</a> in the context of three systematic trading strategies (trend following, pairs trading, and factor investing). </p><div class="callout-block" data-callout="true"><p>Risk parity is a risk-based approach that constructs a portfolio so that assets or asset classes contribute equally, or according to predefined budgets, to total portfolio risk. Instead of allocating capital equally, it allocates risk equally.</p></div><div><hr></div><h1>Summary</h1><p>Mean-variance optimization is often criticized due to the sensitivity of optimal solutions to changes in the inputs, estimation error, and due to the shortcomings of variance as a risk measure. At the same time, there are some persistent misconceptions about MVO, such as MVO requiring an assumption of normality or a quadratic utility function, which deserve to be put to rest. </p><p>This post reviews some alternatives to MVO, which range from doing away with forecasts altogether (1<em>/N</em>), getting rid only of expected return forecasts (MVP, MDP, risk parity), and using different risk measures (mean-semivariance, mean-CVaR). The table below summarizes these alternatives and their tradeoffs. </p><div id="datawrapper-iframe" class="datawrapper-wrap outer" data-attrs="{&quot;url&quot;:&quot;https://datawrapper.dwcdn.net/AgQsv/1/&quot;,&quot;thumbnail_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c6374d76-8c2d-46e7-955c-472e9e9f7f9d_1220x918.png&quot;,&quot;thumbnail_url_full&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/53efeaf1-8db7-431a-b36e-e8befa1beb02_1220x988.png&quot;,&quot;height&quot;:501,&quot;title&quot;:&quot;Summary of Alternatives to MVO&quot;,&quot;description&quot;:&quot;&quot;}" data-component-name="DatawrapperToDOM"><iframe id="iframe-datawrapper" class="datawrapper-iframe" src="https://datawrapper.dwcdn.net/AgQsv/1/" width="730" height="501" frameborder="0" scrolling="no"></iframe><script type="text/javascript">!function(){"use strict";window.addEventListener("message",(function(e){if(void 0!==e.data["datawrapper-height"]){var t=document.querySelectorAll("iframe");for(var a in e.data["datawrapper-height"])for(var r=0;r<t.length;r++){if(t[r].contentWindow===e.source)t[r].style.height=e.data["datawrapper-height"][a]+"px"}}}))}();</script></div><p>In an upcoming post, I&#8217;ll discuss a practical implementation of these alternatives with ETFs</p><p>I should not also that this is not an exhaustive list of alternatives to MVO. Another family of approaches, robust optimization, keeps the optimization framework but explicitly accounts for uncertainty in the inputs. Views-based approaches, such as Black-Litterman and Entropy Pooling, also remain close to the optimization tradition, but they change the way investor views, priors, or scenarios enter the problem. I will discuss these approaches in future posts. </p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://systematicallybiased.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">This Substack is reader-supported. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><div><hr></div><h2>References</h2><p>Allen, D., Lizieri, C., &amp; Satchell, S. (2019).<a href="https://doi.org/10.1080/0015198X.2019.1600958"> In defense of portfolio optimization: What if we can forecast?</a>. <em>Financial Analysts Journal</em>, <em>75</em>(3), 20-38.</p><p>Asness, C. S., Frazzini, A., &amp; Pedersen, L. H. (2012). <a href="https://pages.stern.nyu.edu/~afrazzin/pdf/Leverage%20Aversion%20and%20Risk%20Parity%20-%20Asness%20,%20Frazzini%20and%20Pedersen.pdf">Leverage aversion and risk parity</a>. <em>Financial Analysts Journal</em>, <em>68</em>(1), 47-59.</p><p>Benveniste, J., Kolm, P. N., &amp; Ritter, G. (2024). <a href="https://www.researchgate.net/profile/Gordon-Ritter/publication/381902344_Untangling_Universality_and_Dispelling_Myths_in_Mean-Variance_Optimization/links/6717bd7c09ba2d0c76180b44/Untangling-Universality-and-Dispelling-Myths-in-Mean-Variance-Optimization.pdf">Untangling universality and dispelling myths in mean-variance optimization.</a> <em>The Journal of Portfolio Management</em>, <em>50</em>(8), 90-116.</p><p>Chaves, D., Hsu, J., Li, F., &amp; Shakernia, O. (2011). <a href="https://www.researchgate.net/profile/Jason-Hsu-5/publication/228206016_Risk_Parity_Portfolio_vs_Other_Asset_Allocation_Heuristic_Portfolios/links/00b7d532848224ae2d000000/Risk-Parity-Portfolio-vs-Other-Asset-Allocation-Heuristic-Portfolios.pdf">Risk parity portfolio vs. other asset allocation heuristic portfolios</a>. <em>Journal of Investing</em>, <em>20</em>(1), 108.</p><p>Choueifaty, Y., &amp; Coignard, Y. (2008). <a href="https://papers.ssrn.com/sol3/papers.cfm?abstract_id=4063676">Toward Maximum Diversification</a>. <em>The Journal of Portfolio Management</em>, <em>35</em>(1), 40-51.</p><p>Clarke, R., De Silva, H., &amp; Thorley, S. (2011). <a href="https://www.hillsdaleinv.com/uploads/Minimum-Variance_Portfolio_Composition,_Roger_Clarke,_Harindra_de_Silva,_Steven_Thorley.pdf">Minimum-variance portfolio composition</a>. <em>Journal of Portfolio Management</em>, <em>37</em>(2), 31.</p><p>Clarke, R., De Silva, H., &amp; Thorley, S. (2013). <a href="https://www.hillsdaleinv.com/uploads/Risk_Parity,_Maximum_Diversification,_and_Minimum_Variance-_An_Analytic_Perspective.pdf">Risk parity, maximum diversification, and minimum variance: An analytic perspective.</a> <em>The Journal of Portfolio Management</em>, <em>39</em>(3), 39-53.</p><p>DeMiguel, V., Garlappi, L., &amp; Uppal, R. (2009). <a href="https://doi.org/10.1093/rfs/hhm075">Optimal versus naive diversification: How inefficient is the 1/N portfolio strategy?</a>. <em>The review of Financial studies</em>, <em>22</em>(5), 1915-1953.</p><p>De Silva, R., Clarke, H., &amp; Thorley, S. (2006). <a href="https://www.pm-research.com/content/iijpormgmt/33/1/10.full.pdf">Minimum-variance portfolios in the US equity market</a>. <em>Journal of Portfolio Management</em>, <em>33</em>(1), 1-14.</p><p>Dom, M. S., Howard, C., Jansen, M., &amp; Lohre, H. (2025). <a href="https://doi.org/10.1080/14697688.2025.2468268">Beyond GMV: the relevance of covariance matrix estimation for risk-based portfolio construction</a>. <em>Quantitative Finance</em>, <em>25</em>(3), 403-419.</p><p>Haugen, R. A., &amp; Baker, N. L. (1991). <a href="http://www.efalken.com/LowVolClassics/HaugenBaker991.pdf">The efficient market inefficiency of capitalization-weighted stock portfolios</a>. <em>Journal of Portfolio Management</em>, <em>17</em>(3), 35.</p><p>Jagannathan, R., &amp; Ma, T. (2003). <a href="https://scholar.archive.org/work/x4vdsep3mrdqhc56nmgt6d26ga/access/wayback/http://www.hec.fr/heccontent/download/3867/104597/version/2/file/72.pdf">Risk reduction in large portfolios: A role for portfolio weight constraints</a>. <em>Journal of Finance</em>, <em>58</em>, 1651-1684.</p><p>Kritzman, M., Page, S., &amp; Turkington, D. (2010). In defense of optimization: the fallacy of 1/N. <em>Financial Analysts Journal</em>, <em>66</em>(2), 31-39.</p><p>Ledoit, O., &amp; Wolf, M. (2003). <a href="https://e-archivo.uc3m.es/bitstreams/e11e70d5-4800-4da6-b6d6-6380060f39b1/download">Improved estimation of the covariance matrix of stock returns with an application to portfolio selection</a>. <em>Journal of Empirical Finance</em>, <em>10</em>(5), 603-621.</p><p>Lopez de Prado, M. (2016). <a href="https://papers.ssrn.com/sol3/papers.cfm?abstract_id=2708678">Building diversified portfolios that outperform out-of-sample</a>. <em>Journal of Portfolio Management</em>.</p><p>Markovitz, H. (1959). Portfolio selection: Efficient diversification of investments.</p><p>Markowitz, H. M., Starer, D., Fram, H., &amp; Gerber, S. (2020). <a href="https://www.hudsonbaycapital.com/documents/FG/hudsonbay/research/599440_paper.pdf">Avoiding the downside: A practical review of the critical line algorithm for mean&#8211;semivariance portfolio optimization.</a> <em>Handbook of Applied Investment Research</em>, 369-415.</p><p>Pflug, G. C., Pichler, A., &amp; Wozabal, D. (2012). <a href="https://doi-org.ezproxy.univ-catholille.fr/10.1016/j.jbankfin.2011.07.018">The 1/N investment strategy is optimal under high model ambiguity</a>. <em>Journal of Banking &amp; Finance</em>, <em>36</em>(2), 410-417.</p><p>Rockafellar, R. T., &amp; Uryasev, S. (2000). <a href="https://www.risk.net/journal-risk/2161159/optimization-conditional-value-risk">Optimization of conditional value-at-risk</a>. <em>Journal of Risk</em>, <em>2</em>, 21-42.</p><p>Rubesam, A. (2022). <a href="https://www.pm-research.com/content/iijpormgmt/48/4/241">The Long and the Short of Risk Parity</a>. <em>Journal of Portfolio Management</em>, <em>48</em>(4).</p><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-1" href="#footnote-anchor-1" class="footnote-number" contenteditable="false" target="_self">1</a><div class="footnote-content"><p>This approach has been pioneered in a series of papers by Ledoit and Wolf and their shrinkage estimators are widely available in portfolio optimization packages. These estimators can be helpful when the number of assets is large relative to the number of data points available. <a href="https://doi.org/10.1080/14697688.2025.2468268">Dom et al. (2025)</a> study the impact of the covariance matrix estimator for minimum variance portfolios. Sophisticated models do not add much value compared to the sample covariance matrix when long-only and turnover constraints are used. </p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-2" href="#footnote-anchor-2" class="footnote-number" contenteditable="false" target="_self">2</a><div class="footnote-content"><p>Solving this problem for different levels of <em>&#947;</em> traces out the same efficient frontier as the more common approaches of minimizing the portfolio variance for different levels of expected return, or maximizing expected return for different levels of variance/volatility.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-3" href="#footnote-anchor-3" class="footnote-number" contenteditable="false" target="_self">3</a><div class="footnote-content"><p>If we also assume decreasing absolute risk aversion, we can also make statements about investors&#8217; preferences regarding skewness (investors prefer positive to negative skew) and kurtosis. If we also assume decreasing absolute prudence, investors dislike kurtosis. </p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-4" href="#footnote-anchor-4" class="footnote-number" contenteditable="false" target="_self">4</a><div class="footnote-content"><p>Interestingly, this rule can even be optimal under extreme model uncertainty, as shown by <a href="https://doi.org/10.3390/risks8010029">Pflug et al. (2012)</a>.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-5" href="#footnote-anchor-5" class="footnote-number" contenteditable="false" target="_self">5</a><div class="footnote-content"><p>Alternatively, it is the solution of mean-variance utility maximization stated previously with a coefficient of risk aversion <em>&#947;&#8594; &#8734;.</em></p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-6" href="#footnote-anchor-6" class="footnote-number" contenteditable="false" target="_self">6</a><div class="footnote-content"><p>Note that we omit the dependence on the portfolio <em>w</em>.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-7" href="#footnote-anchor-7" class="footnote-number" contenteditable="false" target="_self">7</a><div class="footnote-content"><p>That is, </p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;\\text{CVaR}_\\beta(w)=\\mathbb{E}[L(w,r)|L(w,r)\\geq \\text{VaR}_\\beta(w)]&quot;,&quot;id&quot;:&quot;DNEFFJXIJR&quot;}" data-component-name="LatexBlockToDOM"></div></div></div>]]></content:encoded></item><item><title><![CDATA[Portfolio Optimization: What Could Go Wrong?]]></title><description><![CDATA[A lot, actually...]]></description><link>https://systematicallybiased.substack.com/p/portfolio-optimization-what-could</link><guid isPermaLink="false">https://systematicallybiased.substack.com/p/portfolio-optimization-what-could</guid><dc:creator><![CDATA[Systematically Biased]]></dc:creator><pubDate>Thu, 30 Apr 2026 15:05:52 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!8FPy!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3311ee68-bf17-44b3-a5d3-b0abc75ad5d3_1448x1086.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Mean-variance optimization is one of those ideas that is both foundational and strangely easy to caricature. In theory, it gives us the cleanest possible answer to a portfolio choice problem. In practice, small changes in the inputs can lead to large changes in the portfolio. This post is about the second part. I&#8217;ll show some concrete examples of how easily things can go wrong when portfolio optimization is implemented na&#239;vely, and how some of these issues can be attenuated in practice. I&#8217;ll focus only on mean-variance optimization (MVO), the most vanilla of all portfolio optimization approaches. </p><p>Python code for all the examples shown in this article is provided at the end.</p><div><hr></div><h1>The Mean-Variance Optimization Problem</h1><p>Given <em>n</em> assets, we are trying to build an efficient portfolio in a mean-variance sense. For a target level of expected return <em>&#181;</em>, we want the portfolio with the lowest risk possible.<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-1" href="#footnote-1" target="_self">1</a></p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;\\min_{w_1,\\dots,w_n}{\\text{Var}(r_p)} \\quad \\text{such that} \\quad \\sum_{i=1}^n{w_i}=1 \\quad \\text{and} \\quad E(r_p)=\\mu&quot;,&quot;id&quot;:&quot;HIJBNEKJQR&quot;}" data-component-name="LatexBlockToDOM"></div><p>Any finance textbook gives us the following recipe:</p><ol><li><p>Find the minimum variance portfolio (MVP) and compute its expected return. The MVP is the solution of the problem above without the target expected return<em> </em>constraint. </p></li><li><p>Create a grid from the expected return of the MVP to a maximum expected return. </p></li><li><p>Solve the problem above for each target expected return, store portfolio weights, expected returns, and standard deviations. </p></li><li><p>Plot the resulting set of portfolios in standard deviation-expected return space.</p></li></ol><p>The result is the so-called <em>efficient frontier</em>, i.e., the set of portfolios with the lowest risk for each level of expected return (or alternatively, with highest expected return for each level of risk). This is the starting point for many classic results in finance which I&#8217;m not going to discuss here. </p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!8FPy!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3311ee68-bf17-44b3-a5d3-b0abc75ad5d3_1448x1086.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!8FPy!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3311ee68-bf17-44b3-a5d3-b0abc75ad5d3_1448x1086.png 424w, https://substackcdn.com/image/fetch/$s_!8FPy!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3311ee68-bf17-44b3-a5d3-b0abc75ad5d3_1448x1086.png 848w, https://substackcdn.com/image/fetch/$s_!8FPy!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3311ee68-bf17-44b3-a5d3-b0abc75ad5d3_1448x1086.png 1272w, https://substackcdn.com/image/fetch/$s_!8FPy!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3311ee68-bf17-44b3-a5d3-b0abc75ad5d3_1448x1086.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!8FPy!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3311ee68-bf17-44b3-a5d3-b0abc75ad5d3_1448x1086.png" width="1448" height="1086" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/3311ee68-bf17-44b3-a5d3-b0abc75ad5d3_1448x1086.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1086,&quot;width&quot;:1448,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:679054,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://systematicallybiased.substack.com/i/195638939?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3311ee68-bf17-44b3-a5d3-b0abc75ad5d3_1448x1086.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!8FPy!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3311ee68-bf17-44b3-a5d3-b0abc75ad5d3_1448x1086.png 424w, https://substackcdn.com/image/fetch/$s_!8FPy!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3311ee68-bf17-44b3-a5d3-b0abc75ad5d3_1448x1086.png 848w, https://substackcdn.com/image/fetch/$s_!8FPy!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3311ee68-bf17-44b3-a5d3-b0abc75ad5d3_1448x1086.png 1272w, https://substackcdn.com/image/fetch/$s_!8FPy!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3311ee68-bf17-44b3-a5d3-b0abc75ad5d3_1448x1086.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div><hr></div><h2>Problems with MVO</h2><p>Already we can identify some potential issues:</p><ul><li><p>We don&#8217;t really know the expected returns of the assets or their covariance matrix;</p></li><li><p>We&#8217;re assuming that the investor&#8217;s preference is fully captured by expected returns and the covariance matrix of returns; </p></li><li><p>Everything is static (this is a single period model);</p></li><li><p>The framework doesn&#8217;t take into account practical considerations, such as transaction costs.</p></li></ul><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://systematicallybiased.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">This Substack is reader-supported. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>I will come back to some of these issues in a future article. For now, let&#8217;s focus on the first one. Since we do not know the expected returns and covariances, the best we can do is to estimate them, which brings estimation error into the problem. The standard Markowitz machinery is not designed to take that into account. A common critique is that MVO is an &#8220;error maximizer&#8221;, but this is a critique of the estimation process, not of the method. </p><p>This distinction matters. Optimization does not create information. It operates on whatever information is in the inputs. If the inputs contain signal, optimization can turn small edges into meaningful portfolio gains. If the inputs contain mostly noise, optimization can amplify that noise into concentrated bets.</p><p>Among the inputs to be estimated, there is a pecking order in terms of the impact on the resulting portfolios:</p><div class="callout-block" data-callout="true"><p style="text-align: center;">Expected returns&#8594;Variances&#8594;Covariances</p></div><p>The intuition is simple: expected returns enter the optimizer as the reward for taking risk, so small differences in estimated means can dominate the allocation. Variances determine the scale of risk for each asset, while covariances determine diversification benefits across pairs.</p><p>The estimation of expected returns is the most critical. Not only are expected returns difficult to estimate precisely, but small differences in expected return estimates can significantly affect the resulting portfolios. In terms of the covariance matrix, errors in the variances are approximately twice as important as errors in the covariances.<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-2" href="#footnote-2" target="_self">2</a> There are also known issues in estimating covariances, especially in high dimensions (hundreds or thousands of assets), but there are many methods that can attenuate these issues. </p><p>Many empirical studies that find lackluster performance for MVO portfolios estimate inputs directly from historical returns. Interestingly, this is clearly at odds with what Markowitz himself suggested. His 1952 paper opens with the following statement: </p><blockquote><p><em>The process of selecting a portfolio may be divided into two stages. The first stage starts with observation and experience and ends with beliefs about the future performances of available securities. The second stage starts with the relevant beliefs about future performances and ends with the choice of portfolio. This paper is concerned with the second stage&#8230;</em></p><p><em>To use the E-V rule in the selection of securities, we must have procedures for finding reasonable &#956;<sub>i</sub> and &#963;<sub>ij</sub>. These procedures, I believe, should combine statistical techniques and the judgment of practical men.</em></p></blockquote><p>So dismissing MVO because na&#239;ve implementations based on historical return estimates perform poorly feels a bit like throwing the baby out with the bathwater. That is, the fact that estimates based only on past returns produce poor results should not automatically disqualify the optimization process.</p><p>One interesting aspect regarding the estimation of expected returns is that even a small forecasting edge can significantly improve results. Expected return estimates based only on realized returns are mostly noise, but firm characteristics and other variables can be used to construct forecasting models with low, but nonzero, predictive power. This is a point I will come back to in a future article. </p><div><hr></div><h1>Practical Examples</h1><p>The rest of this post is concerned with simple illustrations of the very real issues that arise when MVO is applied using na&#239;ve estimates from historical returns. These examples are pedagogical but typical of what happens in practice if we implement MVO &#8220;out of the box&#8221;. Examples 1 through 4 illustrate common problems that arise in na&#239;ve MVO implementations. Examples 5 and 6 show simple ways to attenuate some of them.</p><h3>Example 1: Input sensitivity</h3><p>A common issue with MVO is the sensitivity of the results to changes in the inputs. Consider the example below. There are three assets with the characteristics below. Assume all correlations are equal to 0.8, and the objective of the manager is to maximize the expected return for a 15% volatility target.<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-3" href="#footnote-3" target="_self">3</a></p><div id="datawrapper-iframe" class="datawrapper-wrap outer" data-attrs="{&quot;url&quot;:&quot;https://datawrapper.dwcdn.net/xF2Bm/1/&quot;,&quot;thumbnail_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/fd174910-7803-4de2-9c32-6cefef48ba99_1220x324.png&quot;,&quot;thumbnail_url_full&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/01ffbe67-ee3f-4b24-bfaa-06facc455221_1220x324.png&quot;,&quot;height&quot;:155,&quot;title&quot;:&quot;Created with Datawrapper&quot;,&quot;description&quot;:&quot;&quot;}" data-component-name="DatawrapperToDOM"><iframe id="iframe-datawrapper" class="datawrapper-iframe" src="https://datawrapper.dwcdn.net/xF2Bm/1/" width="730" height="155" frameborder="0" scrolling="no"></iframe><script type="text/javascript">!function(){"use strict";window.addEventListener("message",(function(e){if(void 0!==e.data["datawrapper-height"]){var t=document.querySelectorAll("iframe");for(var a in e.data["datawrapper-height"])for(var r=0;r<t.length;r++){if(t[r].contentWindow===e.source)t[r].style.height=e.data["datawrapper-height"][a]+"px"}}}))}();</script></div><p>If we solve the problem, we get the following portfolio weights: </p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;w^*=(0.383, 0.202, 0.414)'&quot;,&quot;id&quot;:&quot;QOUZYAZVXF&quot;}" data-component-name="LatexBlockToDOM"></div><p>Now suppose we increase all pairwise correlations from 0.8 to 0.9. In this case, we get the following solution:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;w^*=(0.446, 0.089, 0.465)'&quot;,&quot;id&quot;:&quot;JFUUIGYEYL&quot;}" data-component-name="LatexBlockToDOM"></div><p>An apparently small change in the correlation leads to large differences in portfolio weights. </p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!k6EL!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff6df6922-662d-43a7-b5e7-6d4e1a452e29_1584x944.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!k6EL!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff6df6922-662d-43a7-b5e7-6d4e1a452e29_1584x944.png 424w, https://substackcdn.com/image/fetch/$s_!k6EL!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff6df6922-662d-43a7-b5e7-6d4e1a452e29_1584x944.png 848w, https://substackcdn.com/image/fetch/$s_!k6EL!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff6df6922-662d-43a7-b5e7-6d4e1a452e29_1584x944.png 1272w, https://substackcdn.com/image/fetch/$s_!k6EL!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff6df6922-662d-43a7-b5e7-6d4e1a452e29_1584x944.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!k6EL!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff6df6922-662d-43a7-b5e7-6d4e1a452e29_1584x944.png" width="1456" height="868" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f6df6922-662d-43a7-b5e7-6d4e1a452e29_1584x944.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:868,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:65982,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://systematicallybiased.substack.com/i/195638939?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff6df6922-662d-43a7-b5e7-6d4e1a452e29_1584x944.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!k6EL!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff6df6922-662d-43a7-b5e7-6d4e1a452e29_1584x944.png 424w, https://substackcdn.com/image/fetch/$s_!k6EL!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff6df6922-662d-43a7-b5e7-6d4e1a452e29_1584x944.png 848w, https://substackcdn.com/image/fetch/$s_!k6EL!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff6df6922-662d-43a7-b5e7-6d4e1a452e29_1584x944.png 1272w, https://substackcdn.com/image/fetch/$s_!k6EL!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff6df6922-662d-43a7-b5e7-6d4e1a452e29_1584x944.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h3>Example 2: Estimation Error</h3><p>Example 1 illustrated the fact that MVO weights are very sensitive to the inputs. We also know that financial returns are very noisy, and the inputs are estimated under significant estimation error, especially expected returns. But that doesn&#8217;t mean that we can take covariance matrix estimates for granted. Even with a small number of assets, sample variability can have an important impact. </p><p>In this example, we consider an extremely simple application of MVO. There are only two assets:</p><ul><li><p>SPY (U.S. equities)</p></li><li><p>TLT (Long-Term U.S. Treasuries)</p></li></ul><p>And we are estimating the minimum variance portfolio (MVP) of the two assets on a given date. In the two-asset case, the MVP is calculated in closed form as</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;w_{MVP} =\\frac{\\sigma_2^2-\\sigma_{12} }{\\sigma_1^2 +\\sigma_2^2 -2\\sigma_{12}}&quot;,&quot;id&quot;:&quot;RSIMMQEGQG&quot;}" data-component-name="LatexBlockToDOM"></div><p>Therefore, we only need to estimate 3 parameters:</p><ul><li><p>The variance of SPY</p></li><li><p>The variance of TLT</p></li><li><p>The covariance between SPY and TLT</p></li></ul><p>Suppose we decide to estimate these parameters using 5 years of daily returns for the two ETFs. This means we have approximately 1,260 daily return observations for each asset, which is a relatively generous sample for estimating only three parameters.</p><p>Using 5 years of daily data ending in March 2026, we get the following estimates:</p><ul><li><p>SPY volatility: 17%</p></li><li><p>TLT volatility: 16%</p></li><li><p>Correlation (SPY,TLT): 0.0712 </p></li></ul><p>The corresponding MVP allocates 46% to SPY and 54% to TLT, resulting in a volatility of 12%. The reduction in risk is due to the low correlation. </p><p>In this simple example with only 3 parameters to estimate, how much sampling variability exists? That is, if we had used a slightly different sample, we would have obtained different estimates. To quantify the variability in these estimates, we use a technique known as bootstrapping. The idea is to construct new samples by randomly drawing, with replacement, from the original sample. The graphs below were generated with 1,000 bootstrap samples. </p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!3PyZ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdff9d7d6-a33c-43a3-9fed-91836c49ba0d_1411x561.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!3PyZ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdff9d7d6-a33c-43a3-9fed-91836c49ba0d_1411x561.png 424w, https://substackcdn.com/image/fetch/$s_!3PyZ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdff9d7d6-a33c-43a3-9fed-91836c49ba0d_1411x561.png 848w, https://substackcdn.com/image/fetch/$s_!3PyZ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdff9d7d6-a33c-43a3-9fed-91836c49ba0d_1411x561.png 1272w, https://substackcdn.com/image/fetch/$s_!3PyZ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdff9d7d6-a33c-43a3-9fed-91836c49ba0d_1411x561.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!3PyZ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdff9d7d6-a33c-43a3-9fed-91836c49ba0d_1411x561.png" width="1411" height="561" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/dff9d7d6-a33c-43a3-9fed-91836c49ba0d_1411x561.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:561,&quot;width&quot;:1411,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:64772,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://systematicallybiased.substack.com/i/195638939?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdff9d7d6-a33c-43a3-9fed-91836c49ba0d_1411x561.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!3PyZ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdff9d7d6-a33c-43a3-9fed-91836c49ba0d_1411x561.png 424w, https://substackcdn.com/image/fetch/$s_!3PyZ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdff9d7d6-a33c-43a3-9fed-91836c49ba0d_1411x561.png 848w, https://substackcdn.com/image/fetch/$s_!3PyZ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdff9d7d6-a33c-43a3-9fed-91836c49ba0d_1411x561.png 1272w, https://substackcdn.com/image/fetch/$s_!3PyZ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdff9d7d6-a33c-43a3-9fed-91836c49ba0d_1411x561.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>As we can see, even in this simple case in which data is relatively abundant, there&#8217;s a substantial amount of uncertainty. A 95% confidence interval for the MVP weight on SPY is [41.20%, 51.20%], while the corresponding interval for MVP volatility is [11.23%, 12.76%].</p><h3>Example 3: Corner Solutions/Extreme Concentration</h3><p>Another common issue in na&#239;vely implemented MVO portfolios, especially when weights are left unconstrained, is the emergence of corner solutions. The optimizer finds a solution that invests almost all capital in just a small subset of the assets, leading to extreme portfolio concentration. </p><p>Let's consider the problem of optimizing a portfolio using the following ETFs representing different asset classes:</p><ul><li><p>U.S. Stocks (SPY)</p></li><li><p>International Stocks (EFA)</p></li><li><p>Emerging Stocks (EEM)</p></li><li><p>Long-Term U.S. Treasuries (TLT)</p></li><li><p>Intermediate-Term U.S. Treasuries (IEF)</p></li><li><p>U.S. Corporate bonds (LQD)</p></li><li><p>Real Estate (VNQ)</p></li><li><p>Commodities (DBC)</p></li><li><p>Gold (GLD)</p></li></ul><p>The objective is to find the portfolio with the highest expected return subject to a target volatility of 10%.</p><p>Below are solutions obtained from a na&#239;ve implementation of MVO on a specific date (December 2019). Expected returns and the covariance matrix estimates are  obtained using sample moments from the previous 3 years of daily data. The &#8220;Unconstrained&#8221; solution allows short positions, while the &#8220;Long-Only&#8221; requires non-negative weights. In both cases, the full investment constraint (sum of weights = 1) is used. </p><div id="datawrapper-iframe" class="datawrapper-wrap outer" data-attrs="{&quot;url&quot;:&quot;https://datawrapper.dwcdn.net/vimE0/2/&quot;,&quot;thumbnail_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/4ec12717-a16b-4148-a8c5-ced7ea20fc10_1220x906.png&quot;,&quot;thumbnail_url_full&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/19185c79-627d-4429-9e74-f8a2a73606dc_1220x976.png&quot;,&quot;height&quot;:554,&quot;title&quot;:&quot;[ Portfolio solutions as of December 2019 ]&quot;,&quot;description&quot;:&quot;&quot;}" data-component-name="DatawrapperToDOM"><iframe id="iframe-datawrapper" class="datawrapper-iframe" src="https://datawrapper.dwcdn.net/vimE0/2/" width="730" height="554" frameborder="0" scrolling="no"></iframe><script type="text/javascript">!function(){"use strict";window.addEventListener("message",(function(e){if(void 0!==e.data["datawrapper-height"]){var t=document.querySelectorAll("iframe");for(var a in e.data["datawrapper-height"])for(var r=0;r<t.length;r++){if(t[r].contentWindow===e.source)t[r].style.height=e.data["datawrapper-height"][a]+"px"}}}))}();</script></div><p>The unconstrained solution invests in all ETFs, but the level of leverage is unreasonable (gross leverage of over 600%). The optimizer takes large opposite positions in IEF and LQD. The two ETFs are highly correlated (<em><a href="https://www.ascii-code.com/character/%CF%81">&#961;</a></em>=0.94), but have a difference in expected returns, so the optimizer aggressively buys LQD (expected return 6.7%) and shorts IEF (expected return 4%). While the positions are extreme, this is not unexpected. In the absence of other constraints, the optimizer is trying to take advantage of the fact that the two assets seem like close substitutes but have a return differential.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!pXWA!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff3cb2696-3874-4ff7-85c6-5842a4cd1df3_804x701.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!pXWA!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff3cb2696-3874-4ff7-85c6-5842a4cd1df3_804x701.png 424w, https://substackcdn.com/image/fetch/$s_!pXWA!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff3cb2696-3874-4ff7-85c6-5842a4cd1df3_804x701.png 848w, https://substackcdn.com/image/fetch/$s_!pXWA!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff3cb2696-3874-4ff7-85c6-5842a4cd1df3_804x701.png 1272w, https://substackcdn.com/image/fetch/$s_!pXWA!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff3cb2696-3874-4ff7-85c6-5842a4cd1df3_804x701.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!pXWA!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff3cb2696-3874-4ff7-85c6-5842a4cd1df3_804x701.png" width="804" height="701" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f3cb2696-3874-4ff7-85c6-5842a4cd1df3_804x701.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:701,&quot;width&quot;:804,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:83410,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://systematicallybiased.substack.com/i/195638939?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff3cb2696-3874-4ff7-85c6-5842a4cd1df3_804x701.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!pXWA!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff3cb2696-3874-4ff7-85c6-5842a4cd1df3_804x701.png 424w, https://substackcdn.com/image/fetch/$s_!pXWA!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff3cb2696-3874-4ff7-85c6-5842a4cd1df3_804x701.png 848w, https://substackcdn.com/image/fetch/$s_!pXWA!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff3cb2696-3874-4ff7-85c6-5842a4cd1df3_804x701.png 1272w, https://substackcdn.com/image/fetch/$s_!pXWA!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff3cb2696-3874-4ff7-85c6-5842a4cd1df3_804x701.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Estimated correlation matrix as of December 2019</figcaption></figure></div><p>The long-only solution, on the other hand, is extremely concentrated. It invests only in SPY (78.7%) and GLD (21.3%). </p><p>What about other efficient portfolios? The graph below shows the entire efficient frontier under the long-only constraint (<em>w<sub>i </sub>&#8805; </em>0). As we can see below, the entire frontier lacks diversification, investing on average in only 3 assets at any given level of volatility.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!sYWB!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55b297ab-e318-4f21-beab-e4f51a25a703_1190x990.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!sYWB!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55b297ab-e318-4f21-beab-e4f51a25a703_1190x990.png 424w, https://substackcdn.com/image/fetch/$s_!sYWB!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55b297ab-e318-4f21-beab-e4f51a25a703_1190x990.png 848w, https://substackcdn.com/image/fetch/$s_!sYWB!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55b297ab-e318-4f21-beab-e4f51a25a703_1190x990.png 1272w, https://substackcdn.com/image/fetch/$s_!sYWB!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55b297ab-e318-4f21-beab-e4f51a25a703_1190x990.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!sYWB!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55b297ab-e318-4f21-beab-e4f51a25a703_1190x990.png" width="1190" height="990" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/55b297ab-e318-4f21-beab-e4f51a25a703_1190x990.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:990,&quot;width&quot;:1190,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:74812,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://systematicallybiased.substack.com/i/195638939?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55b297ab-e318-4f21-beab-e4f51a25a703_1190x990.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!sYWB!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55b297ab-e318-4f21-beab-e4f51a25a703_1190x990.png 424w, https://substackcdn.com/image/fetch/$s_!sYWB!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55b297ab-e318-4f21-beab-e4f51a25a703_1190x990.png 848w, https://substackcdn.com/image/fetch/$s_!sYWB!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55b297ab-e318-4f21-beab-e4f51a25a703_1190x990.png 1272w, https://substackcdn.com/image/fetch/$s_!sYWB!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55b297ab-e318-4f21-beab-e4f51a25a703_1190x990.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h3> Example 4: Instability of Optimal Weights</h3><p>Considering the same setup of Example 3, let&#8217;s explore how the optimal weights for the target volatility of 10% in the long-only case evolve if we rebalance the portfolio at each month end. As can be seen in the graph below, the optimal allocations can vary dramatically over time. In Example 3, the optimal allocations as of December 2019 were concentrated in SPY (~80%) and GLD (~20%). But we can see that, prior to that date, the GLD allocation was replaced by either TLT or LQD.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!tSgM!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F00a2f3e5-8446-4cce-a7e8-2176f9d11604_1591x690.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!tSgM!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F00a2f3e5-8446-4cce-a7e8-2176f9d11604_1591x690.png 424w, https://substackcdn.com/image/fetch/$s_!tSgM!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F00a2f3e5-8446-4cce-a7e8-2176f9d11604_1591x690.png 848w, https://substackcdn.com/image/fetch/$s_!tSgM!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F00a2f3e5-8446-4cce-a7e8-2176f9d11604_1591x690.png 1272w, https://substackcdn.com/image/fetch/$s_!tSgM!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F00a2f3e5-8446-4cce-a7e8-2176f9d11604_1591x690.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!tSgM!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F00a2f3e5-8446-4cce-a7e8-2176f9d11604_1591x690.png" width="1456" height="631" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/00a2f3e5-8446-4cce-a7e8-2176f9d11604_1591x690.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:631,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:92494,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://systematicallybiased.substack.com/i/195638939?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F00a2f3e5-8446-4cce-a7e8-2176f9d11604_1591x690.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!tSgM!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F00a2f3e5-8446-4cce-a7e8-2176f9d11604_1591x690.png 424w, https://substackcdn.com/image/fetch/$s_!tSgM!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F00a2f3e5-8446-4cce-a7e8-2176f9d11604_1591x690.png 848w, https://substackcdn.com/image/fetch/$s_!tSgM!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F00a2f3e5-8446-4cce-a7e8-2176f9d11604_1591x690.png 1272w, https://substackcdn.com/image/fetch/$s_!tSgM!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F00a2f3e5-8446-4cce-a7e8-2176f9d11604_1591x690.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Optimal allocations can vary dramatically over time</figcaption></figure></div><p>The COVID shock shifts allocations dramatically due to changes in the estimated parameters. As shown below, the optimal portfolio moves to an almost 20% allocation to TLT in January 2020, and then abruptly allocates almost 100% to TLT in February 2020. </p><div id="datawrapper-iframe" class="datawrapper-wrap outer" data-attrs="{&quot;url&quot;:&quot;https://datawrapper.dwcdn.net/qBdgU/1/&quot;,&quot;thumbnail_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/368183d3-2428-4e09-9258-9591926716fa_1220x842.png&quot;,&quot;thumbnail_url_full&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f75b08b4-bdb8-499b-bbf9-baeaebf71e3d_1220x912.png&quot;,&quot;height&quot;:453,&quot;title&quot;:&quot;Optimal Allocations During COVID-19 Shock&nbsp;&quot;,&quot;description&quot;:&quot;&quot;}" data-component-name="DatawrapperToDOM"><iframe id="iframe-datawrapper" class="datawrapper-iframe" src="https://datawrapper.dwcdn.net/qBdgU/1/" width="730" height="453" frameborder="0" scrolling="no"></iframe><script type="text/javascript">!function(){"use strict";window.addEventListener("message",(function(e){if(void 0!==e.data["datawrapper-height"]){var t=document.querySelectorAll("iframe");for(var a in e.data["datawrapper-height"])for(var r=0;r<t.length;r++){if(t[r].contentWindow===e.source)t[r].style.height=e.data["datawrapper-height"][a]+"px"}}}))}();</script></div><p>Once again, this is not unexpected. We relied on sample moments for the optimization. When the sample moments abruptly changed, the optimization results changed accordingly. This is not necessarily bad: it would make sense to reduce exposure to risky assets during this period. But without forward-looking estimates, the best the optimizer can do is react when the sample moments change. Using shorter samples will lead to faster reactions, with higher portfolio turnover. </p><p>Another interesting question is the extent to which empirical regularities can make their way into the optimization process. For example, using past returns to construct estimates of expected returns can introduce a momentum effect in the portfolio construction process. This illustrates an important nuance: using historical returns as expected returns is not always an accidental mistake. Sometimes it is an intentional signal design choice. The problem is that once expected returns are estimated from recent returns, the optimizer needs additional discipline, usually in the form of constraints.</p><p>JPMorgan designed an index based exactly on this idea. The <a href="https://www.jpmorgan.com/content/dam/jpm/structured-products-documents/2015/jan/JPMorgan_ETF_Efficiente_5_Index_Supplement.pdf">JP Morgan Efficiente 5 index</a> uses a set of 12 ETFs and constructs an optimal portfolio using mean-variance optimization with a lookback window of 6 months to estimate expected returns. The idea behind the short lookback period is to try to capture time series momentum in the ETFs. To get around the lack of diversification we have seen in this example, they impose maximum weights per ETF and per asset class. The inclusion of these additional constraints is the simplest approach to attempt to discipline MVO results, as I&#8217;ll illustrate in the next example. </p><h3>Example 5: Adding Constraints</h3><p>One of the most commonly used approaches to deal with the concentration issues in MVO is to introduce constraints on the portfolio weights. We explore the impact of simple constraints for the long-only optimal portfolio in Examples 3 and 4. We consider the following portfolios: </p><ul><li><p>Long-only: the same portfolio in Example 3 (positive weights with full investment constraint). </p></li><li><p>Max. asset weight: long-only portfolio with an additional constraint that the weight on any asset is capped at 20%. </p></li><li><p>Max. asset class weight: long-only portfolio with the constraint that the weight on any asset is capped at 20% and additional caps per asset class. </p></li></ul><p>For the last one, we divide portfolios into 3 asset classes for simplicity as follows: </p><ul><li><p>Equity: SPY, EFA, EEM. The upper bound for the combined weights is 50%.</p></li><li><p>Fixed Income: TLT, IEF, LQD. The upper bound for the combined weights is 50%.</p></li><li><p>Alternatives: VNQ, GLD, DBC. The upper bound for the combined weights is 30%.</p></li></ul><p>For each case, we obtain the optimal portfolio with a target volatility of 10% at the end of December 2019. The solutions in each case are shown below. The first column shows the same overly concentrated portfolio from Example 3, which invests in only two out of the nine assets. The introduction of the maximum weight constraint at the asset level (column &#8220;Max. asset weight&#8221;) forces the optimizer to diversify. The constraint is binding for SPY and GLD, but the portfolio now invests in six assets. Finally, the introduction of the additional asset class constraints (column &#8220;Max. asset class weight&#8221;) improves diversification a bit more, although two assets (TLT and VNQ) are still left out. </p><div id="datawrapper-iframe" class="datawrapper-wrap outer" data-attrs="{&quot;url&quot;:&quot;https://datawrapper.dwcdn.net/qhvhb/2/&quot;,&quot;thumbnail_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a39a90ae-7516-42a2-9ccd-9c7dbfed7f59_1220x878.png&quot;,&quot;thumbnail_url_full&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/cf1d0c30-cfed-47fa-bc8d-67299f388494_1220x1070.png&quot;,&quot;height&quot;:532,&quot;title&quot;:&quot;Solutions under different constraints&quot;,&quot;description&quot;:&quot;Long-only: positive weights with full investment constraint; Max. asset weight: positive weights, capped at the asset level at 20%; Max. asset class weight: positive weights, capped at the asset level at 20% with additional constraints by asset class&quot;}" data-component-name="DatawrapperToDOM"><iframe id="iframe-datawrapper" class="datawrapper-iframe" src="https://datawrapper.dwcdn.net/qhvhb/2/" width="730" height="532" frameborder="0" scrolling="no"></iframe><script type="text/javascript">!function(){"use strict";window.addEventListener("message",(function(e){if(void 0!==e.data["datawrapper-height"]){var t=document.querySelectorAll("iframe");for(var a in e.data["datawrapper-height"])for(var r=0;r<t.length;r++){if(t[r].contentWindow===e.source)t[r].style.height=e.data["datawrapper-height"][a]+"px"}}}))}();</script></div><p>As we can see, introducing constraints into the portfolio optimization process has the practical effect of improving diversification. There are also <a href="https://pubsonline.informs.org/doi/abs/10.1287/mnsc.1080.0986">theoretical reasons</a> for why adding constraints can be beneficial, as they induce a shrinkage effect on the covariance matrix, which attenuates estimation error.  </p><h3>Example 6: A Simple Resampling Approach</h3><p>In Example 2, I used bootstrapping to assess the variability in MVP weights. An interesting approach that can be used to mitigate several of the issues with MVO illustrated above is to apply resampling to the portfolio optimization process. A typical process works as follows: </p><ul><li><p>Estimate expected returns and covariance matrix from the original sample.</p></li><li><p>Generate many simulated or bootstrap samples.</p></li><li><p>For each sample, estimate a new <em>&#181;</em> and <em>&#931;</em>.</p></li><li><p>Compute an efficient frontier for each resample.</p></li><li><p>Average the portfolio weights across resamples at corresponding points on the frontier.</p></li><li><p>Evaluate the averaged portfolios using the original estimates.</p></li></ul><p>I apply this approach to the multi-asset-class portfolio of the 9 ETFs on December 2019 (for comparison with the results in Examples 3-5). The resampling step uses 500 bootstrap samples. </p><p>The resampled frontier is shorter and plots below the full sample one (top chart). The bottom chart shows that the resampling approach improves diversification significantly. In particular, all assets are now part of the frontier, and the concentrations are much less extreme. Note that the resampled frontier below does not include any constraints, either at the individual asset or the asset class level. Nevertheless, the allocations are much less extreme compared to a single optimization. </p><p>The resampled frontier is much more diversified because it averages the optimal weights obtained from many slightly different versions of the data. In the original sample, mean-variance optimization tends to put large weights on the assets that look best in-sample, especially those with high estimated returns, low estimated risk, or favorable estimated correlations. </p><p>But these estimates are noisy, so the identity of the &#8220;best&#8221; assets changes across bootstrap samples. An asset that receives a large weight in one resample may receive a smaller weight, or no weight, in another. When the weights are averaged across many resampled frontiers, these unstable extreme positions are diluted, while assets that are consistently useful across samples retain higher weights. The result is a smoother and more diversified allocation that sacrifices some in-sample efficiency in exchange for lower sensitivity to estimation error. </p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!vSxz!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb0ee61a2-f9f2-40ad-a968-0eae8a866bbc_1190x990.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!vSxz!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb0ee61a2-f9f2-40ad-a968-0eae8a866bbc_1190x990.png 424w, https://substackcdn.com/image/fetch/$s_!vSxz!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb0ee61a2-f9f2-40ad-a968-0eae8a866bbc_1190x990.png 848w, https://substackcdn.com/image/fetch/$s_!vSxz!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb0ee61a2-f9f2-40ad-a968-0eae8a866bbc_1190x990.png 1272w, https://substackcdn.com/image/fetch/$s_!vSxz!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb0ee61a2-f9f2-40ad-a968-0eae8a866bbc_1190x990.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!vSxz!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb0ee61a2-f9f2-40ad-a968-0eae8a866bbc_1190x990.png" width="1190" height="990" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b0ee61a2-f9f2-40ad-a968-0eae8a866bbc_1190x990.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:990,&quot;width&quot;:1190,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:103965,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://systematicallybiased.substack.com/i/195638939?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb0ee61a2-f9f2-40ad-a968-0eae8a866bbc_1190x990.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!vSxz!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb0ee61a2-f9f2-40ad-a968-0eae8a866bbc_1190x990.png 424w, https://substackcdn.com/image/fetch/$s_!vSxz!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb0ee61a2-f9f2-40ad-a968-0eae8a866bbc_1190x990.png 848w, https://substackcdn.com/image/fetch/$s_!vSxz!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb0ee61a2-f9f2-40ad-a968-0eae8a866bbc_1190x990.png 1272w, https://substackcdn.com/image/fetch/$s_!vSxz!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb0ee61a2-f9f2-40ad-a968-0eae8a866bbc_1190x990.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div><hr></div><h1>Final Thoughts</h1><p>Mean-variance optimization (MVO) is often criticized as yielding nonsensical or impractical results. This article shows practical examples that illustrate: </p><ul><li><p>how sensitive MVO solutions can be to small changes in inputs;</p></li><li><p>how MVO can produce overly concentrated portfolios or extreme long-short positions;</p></li><li><p>how na&#239;ve sample-moment estimates can lead to unstable allocations over time;</p></li><li><p>how constraints and resampling can reduce, though not eliminate, these problems.</p></li></ul><p>MVO is not the only game in town. Other portfolio construction approaches are designed, at least in part,  to handle some of the issues illustrated in this article: </p><ul><li><p>The <a href="https://www.investopedia.com/terms/b/black-litterman_model.asp">Black-Littermann</a> model starts from the idea that market weights contain useful equilibrium information, then allows investors to incorporate views with an explicit degree of confidence.</p></li><li><p>Bayesian approaches, more generally, model uncertainty about expected returns, covariances, or other inputs directly rather than treating estimates as known.</p></li><li><p>The <a href="https://www.ft.com/content/540b2f9a-9c58-44f5-8ae9-36a58c95d26c?shareType=nongift">Total Portfolio Approach</a> uses the strategic asset allocation process to define a fund&#8217;s return objective and overall risk budget, but treats the resulting benchmark as a guide rather than a binding allocation. It recognizes the uncertainty in long-term forecasts and gives investment teams more discretion to deploy risk across the total portfolio as opportunities and market conditions change.</p></li><li><p><a href="https://www.aqr.com/-/media/AQR/Documents/Insights/White-Papers/Understanding-Risk-Parity.pdf">Risk parity</a> reduces reliance on expected return estimates by focusing instead on how much each asset contributes to total portfolio risk.</p></li><li><p><a href="https://rady.ucsd.edu/_files/faculty-research/valkanov/parametric-portfolio.pdf">Parametric portfolio policies</a> allow investors to link portfolio weights directly to asset characteristics that may contain information about expected returns.</p></li></ul><p>While there are many alternative portfolio construction methodologies, I would argue that MVO remains useful because it makes the trade-off between return, risk, and diversification explicit. Knowing its practical limitations, and ways to avoid them, remains essential. </p><div><hr></div><h1>Python Code</h1><p>Notebooks that replicate all examples in this article are provided below for supporters of the Systematically Biased. </p>
      <p>
          <a href="https://systematicallybiased.substack.com/p/portfolio-optimization-what-could">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Building a Systematic Trading System With AI (Post #4: Building the Trading Layer)]]></title><description><![CDATA[From Signals to Orders and Final Thoughts on the Project]]></description><link>https://systematicallybiased.substack.com/p/building-a-systematic-trading-system-b67</link><guid isPermaLink="false">https://systematicallybiased.substack.com/p/building-a-systematic-trading-system-b67</guid><dc:creator><![CDATA[Systematically Biased]]></dc:creator><pubDate>Fri, 24 Apr 2026 18:22:15 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!Wrnv!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff8aeb6ca-7f56-49f5-8409-95a18e5faded_1906x1464.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>This is the fourth and last post of a series in which I&#8217;m documenting the process of building a functional systematic trading system from scratch using an AI agent. Previous parts are here: </p><ul><li><p><a href="https://systematicallybiased.substack.com/p/building-a-trend-following-trading?r=6w89b">Post #1</a> (data pipeline). </p></li><li><p><a href="https://systematicallybiased.substack.com/p/building-a-systematic-trading-system?r=6w89b">Post #2</a> (trading rules/subsystems/strategies).</p></li><li><p><a href="https://systematicallybiased.substack.com/p/building-a-systematic-trading-system-19d?r=6w89b">Post #3</a> (backtesting engine)</p></li></ul><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://systematicallybiased.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">This Substack is reader-supported. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>Once the data pipeline and backtesting engine were in place, the next challenge was operational. At that point, the app could already process historical futures data for signal generation and backtest strategies under fairly realistic assumptions. But neither of those things is enough to trade a strategy in practice. That requires a different layer of the system.</p><p>The trading problem can be stated very simply:</p><blockquote><p>How do we take a signal generated by a model and turn it into an actual position held at the broker, while keeping the process observable, reviewable, and safe?</p></blockquote><p>This quickly expands into a chain of distinct steps. Strategies produce signals. Given an account size and choices about how to combine them to achieve some target risk, the signals need to be turned into target positions. The target positions then need to be compared with what is currently held in the account. That comparison produces one or more orders that, once executed, change the state of the portfolio, which in turn determine future P&amp;L.</p><p>In other words, the trading layer is really about managing the transformation</p><p style="text-align: center;"><strong>Signals &#8594;Target Positions&#8594;Orders&#8594;Actual Positions&#8594;P&amp;L</strong></p><p>in a way that maintains internal consistency. </p><div><hr></div><h1>From Signals to Targets</h1><p>The first step is to decide what the strategy wants to hold based on a signal. In the app, signals are generated by trading rules, then combined inside subsystems, and finally combined into a strategy and translated into desired positions by the portfolio engine. The output of this stage is not an order but a <em>target</em>: the number of contracts the strategy would like to hold for each instrument.</p><p>Conceptually, if the strategy signal for instrument <em>i </em>is <em>S<sub>i </sub></em>and the sizing engine determines that one unit of signal corresponds to some risk-scaled contract quantity, then the target can be written schematically as </p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;n_i^{\\ast} = f(S_i, \\text{capital}, \\text{volatility}, \\text{constraints})&quot;,&quot;id&quot;:&quot;WXEFUVETRH&quot;}" data-component-name="LatexBlockToDOM"></div><p>The exact form of the function depends on the sizing logic. While the distinction between signal and position may seem obvious, it&#8217;s important in order to avoid confusion between the two layers.</p><p>The strategy I adopted in this project is typical of many systematic trading setups. We take a snapshot of the current market state and append it, in effect, as the latest observation for the purpose of computing the current signal, which then is turned into a target position as described above.  </p><p>Once the app computes targets, it has to decide what to do with them. The current setup is <em>semi-automatic:</em> the app treats targets as snapshots. A target run is computed, saved, and then reviewed. Execution then operates on that persisted snapshot rather than silently recomputing the strategy again at order time. This keeps the trading decision being executed explicit and auditable. Obviously, if we were to move into higher frequency strategies, automation and speed would be much more important. </p><div><hr></div><h1>From Targets to Orders</h1><p>Once a target is known, the next step is to compare the target with the current position. If the desired position is <em>n<sub>i</sub><sup>*</sup></em> and the current position attributed to the strategy is <em>n<sub>i</sub><sup>B</sup></em>, then the order quantity is</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;\\Delta n_i = n_i^{\\ast} - n_i^{B}&quot;,&quot;id&quot;:&quot;BBTXJZZCHX&quot;}" data-component-name="LatexBlockToDOM"></div><p>Again, this is conceptually simple, but there are some important details. The app is not generating trades directly from signals, but from the difference between desired and current state. This also means that the app need to be able to read broker snapshots and reconcile positions with one or more strategies. Without a reliable view not only of current broker holdings, but of what each strategy is holding, the app cannot compute meaningful order deltas.</p><div><hr></div><h1>From Orders to Positions</h1><p>Making the app connect to my broker&#8217;s API was extremely easy (despite a few glitches related to a well-known issue in Interactive Brokers&#8217;s API running inside a Streamlit environment). But the trading layer is more than a wrapper around the broker API. Once orders are submitted, the app has to update its view of what the account actually holds. It has to compare that with what the broker reports and  decide whether the internal state and the external state are still aligned.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Wrnv!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff8aeb6ca-7f56-49f5-8409-95a18e5faded_1906x1464.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Wrnv!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff8aeb6ca-7f56-49f5-8409-95a18e5faded_1906x1464.png 424w, https://substackcdn.com/image/fetch/$s_!Wrnv!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff8aeb6ca-7f56-49f5-8409-95a18e5faded_1906x1464.png 848w, https://substackcdn.com/image/fetch/$s_!Wrnv!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff8aeb6ca-7f56-49f5-8409-95a18e5faded_1906x1464.png 1272w, https://substackcdn.com/image/fetch/$s_!Wrnv!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff8aeb6ca-7f56-49f5-8409-95a18e5faded_1906x1464.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Wrnv!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff8aeb6ca-7f56-49f5-8409-95a18e5faded_1906x1464.png" width="1456" height="1118" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f8aeb6ca-7f56-49f5-8409-95a18e5faded_1906x1464.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1118,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:201141,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://systematicallybiased.substack.com/i/195231018?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff8aeb6ca-7f56-49f5-8409-95a18e5faded_1906x1464.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Wrnv!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff8aeb6ca-7f56-49f5-8409-95a18e5faded_1906x1464.png 424w, https://substackcdn.com/image/fetch/$s_!Wrnv!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff8aeb6ca-7f56-49f5-8409-95a18e5faded_1906x1464.png 848w, https://substackcdn.com/image/fetch/$s_!Wrnv!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff8aeb6ca-7f56-49f5-8409-95a18e5faded_1906x1464.png 1272w, https://substackcdn.com/image/fetch/$s_!Wrnv!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff8aeb6ca-7f56-49f5-8409-95a18e5faded_1906x1464.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Connecting the app to Interactive Brokers was surprisingly easy</figcaption></figure></div><p>This is especially important because the broker account only knows account-level positions, ie it does not know about the internal hierarchy of rules, subsystems, and strategies that exists inside the app. So if several strategies are meant to coexist in the same account, the app has to maintain its own strategy-level accounting. That means the system needs to be able to retrieve broker positions, and attribute them internally to one or more strategies. The latter part needs to be coherently taken care of by the app. Without this internal attribution layer, a multi-strategy system running in one account becomes impossible to monitor coherently.</p><div><hr></div><h1>From Positions to P&amp;L</h1><p>Given existing positions, we would like to measure P&amp;L to monitor the system. In a futures systems, this means marking positions to market using current prices and contract multipliers. The calculation is relatively straightforward, but the system needs to maintain a coherent chain from executed positions to current account state to P&amp;L. Without that, the system cannot really be monitored.</p><p>The broad logic is simple enough, but the difficulty is in all the boundary conditions around it. Signals are generated on continuous futures series, but orders must be placed in actual tradable contracts. This means the app has to distinguish between the instrument used for signal generation and the instrument used for execution. I&#8217;ve also implemented a feature that allows the user to change the mapping of the contracts used for signal generation and trading. For example, I may use BTC for signal generation but execute on MBT.  </p><p>One important detail was to make sure  that the app would not break in case of unattributed positions. Since I have other positions in my brokerage account that have nothing to do with the system, I had the agent implement logic to put these aside in an &#8220;unattributed&#8221; bucket, which allows me to quickly check anything that is not internally attributed to a strategy. </p><p>That is what makes the trading layer a engineering problem rather than just an API integration (which the AI agent did very efficiently). </p><div><hr></div><h1>Monitoring Exceptions</h1><p>Real life is messy, and it is not possible to plan for every eventuality. However, we can and should carefully monitor what happens along the way, and the app should flag any situations that seem anomalous. My app ended up with separate monitoring and exception views to keep track of whether the system is currently in a trustworthy state.</p><p>I did (and do) intend to use this app for real trading. For this reason, paper trading is essential, as it reveals many of the operational issues that can happen. To avoid any risk of mixing paper and live trading, I implemented a strict segregation between the two: each has its separate state, separate storage, separate logs, and separate broker connections. </p><p>This part of the project also made the limitations of AI-assisted coding very visible. The difficulty was not in coding, but in making sure the entire chain was logically coherent, due to the many details involved in even a modestly complex trading system. Testing strategies in paper trading revealed several issues and allowed me to fix them safely. </p><div><hr></div><h1>What Still Needs Work</h1><p>So this is the end of the series, but not really the end of the project. At this stage, I&#8217;ve been paper trading strategies, but I still don&#8217;t trust it enough to put real money on the line. Some of the things in the list are:</p><ul><li><p>The system still runs locally on my laptop, with a local database. That is fine for development and paper trading, but too fragile for serious live use. A more robust deployment and storage architecture is still needed.</p></li><li><p>Incorporate a complete workflow for ETF strategies so I can automate the tactical allocation strategy that I use. </p></li><li><p>Improve the backtesting engine to use contract/expiry level data for futures. Although the continuous futures series are good enough to backtest trend following and mean reversion strategies, using the right expiries is more realistic and will allow me to more correctly model rollover assumptions, as well as test strategies like carry, which require trading in more than one contract. </p></li><li><p>Maintenance tools: if something goes wrong (say, I decide to manually close a position), there should be a way to provide that information to the system. Of course, I can tell the agent to fix it, but the app should be able to do this independently.</p></li><li><p>Improve the overall robustness of the system, including better P&amp;L and strategy monitoring. </p></li><li><p>Further automation of data update and signal generation.</p></li></ul><div><hr></div><h1>What This Project Taught Me</h1><p>I had very little experience working with AI agents when I started this project. In the process of building the app, I learned enough about how to scope, guide, and iterate with them that part of me is tempted to start over and rebuild it from scratch with that knowledge in hand. I&#8217;ve been experimenting with agents on other projects as well, and both the speed and the quality of what I can produce keep improving.</p><p>One lesson in particular has become very clear to me: it pays to spend more time upfront defining the context, clarifying the scope, and working with the agent to produce a coherent plan before any coding begins. I&#8217;ve found it especially useful to instruct the agent to keep asking questions until it has clarity on all relevant points.</p><p>Over the course of building this app, the AI agent was extremely useful. It could write large amounts of code quickly, scaffold interfaces, refactor workflows, build database layers, and implement features at a pace I could never have matched on my own. While I&#8217;m quite comfortable with trading strategies, backtests, and portfolio construction, my knowledge of database architecture and UI development is rudimentary at best. In that sense, AI significantly expanded what I could realistically build.</p><p>This project also reinforced for me that AI is not a substitute for domain knowledge. It can accelerate development dramatically, but it is most useful when we can provide detailed specifications of how things should work and still detect problems even when, superficially, things look correct. There were very few cases where the code itself was the main problem. Most bugs arose because I had not provided enough detail about the desired behavior, or because the problem being tackled turned out to be more complex than it initially appeared. In other cases, the code would run and the results would seem reasonable, yet still be conceptually wrong in ways that were not obvious unless I inspected the logic directly or questioned the agent. These are the kinds of mistakes that can get lost in &#8220;vibe coding.&#8221; In that respect, developers have a real advantage when using these tools, because they are used to thinking in terms of specifications, tests, and edge cases.</p><p>I began this series with a simple question:</p><blockquote><h4>Can an AI agent build a functional systematic trading system from scratch?</h4></blockquote><p>My answer is yes, but with some caveats. AI can greatly accelerate the process, but the quality of the result still depends heavily on the human component. I was able to build a prototype that is close to functional for my own purposes, although I would still want much more testing and validation before trusting it with real money. Even so, it remains far from anything I have used, or would consider using, in a professional setting. Now, I&#8217;m not a professional developer, and I&#8217;m well aware of my limitations in that regard. I have no doubt that a professional developer using AI agents could build something orders of magnitude better.</p><p>So that, at least, is what this project taught me. AI made it possible to build much more, much faster, than I could have on my own. AI agents have effectively removed the speed of code creation as a limiting factor, but in a project like this, coherence and a vision of how the parts fit together is what matters. This still depends heavily on the human(s) in the loop.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://systematicallybiased.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">This Substack is reader-supported. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Your Trend Strategy is Just a Weighted Average of Past Returns]]></title><description><![CDATA[How Popular Trend Indicators Embed Views on Return Dynamics]]></description><link>https://systematicallybiased.substack.com/p/your-trend-strategy-is-just-a-weighted</link><guid isPermaLink="false">https://systematicallybiased.substack.com/p/your-trend-strategy-is-just-a-weighted</guid><dc:creator><![CDATA[Systematically Biased]]></dc:creator><pubDate>Fri, 17 Apr 2026 14:47:32 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!Y4Gu!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F05e23f30-0af3-48bc-9d6e-8d44df70a547_686x846.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Trend following is one of the most widely used systematic trading strategies. There&#8217;s solid evidence that trend following has worked across long periods of time, different markets, and is particularly useful during periods of crisis<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-1" href="#footnote-1" target="_self">1</a> There are endless ways to implement it, but many popular trend indicators turn out to be much closer cousins than they first appear. In fact, once you rewrite them in return space rather than price space, a lot of trend-following rules are just weighted averages of past returns. What differs across rules is not the basic idea, but the shape of the weights.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://systematicallybiased.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">This Substack is reader-supported. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>There are countless trend indicators around, but most are variations of simple ideas like: </p><ul><li><p>Compare the current price with price <em>n</em> periods ago.</p></li><li><p>Compare current price with a moving average of prices. </p></li><li><p>Compare a fast moving average with a slow one.</p></li></ul><div><hr></div><h1>Time Series Momentum Rules</h1><p>Take a simple rule based on the first idea. We can express the signal as follows: </p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;I_t(n) = \\begin{cases} &amp;1, \\quad P_t-P_{t-n}\\geq0 \\\\-&amp;1, \\quad P_t-P_{t-n}<0\\end{cases}&quot;,&quot;id&quot;:&quot;EEHTKWRWGC&quot;}" data-component-name="LatexBlockToDOM"></div><p>That means we should go long when the current price is above the price <em>n </em>periods ago, and short otherwise. We can express the same rule by looking at the cumulative returns, which is the time series momentum signal used in many of the papers on trend following:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;I_t(n) = \\begin{cases} &amp;1, \\quad r_{t-n,t}\\geq0 \\\\-&amp;1, \\quad  r_{t-n,t}<0\\end{cases}&quot;,&quot;id&quot;:&quot;WZKKCPQGIQ&quot;}" data-component-name="LatexBlockToDOM"></div><p>where <em>r<sub>t-n,t</sub></em> is the cumulative return over the last <em>n </em>periods. How we define returns here matters. If we work with simple returns, then  <em>r<sub>t-n,t</sub></em>&#8203; must reflect compounding. Alternatively, we can write the signal as a sum of past price differences. If we use log returns, then is simply the sum of the last <em>n</em> one-period log returns: </p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;r_{t-n,t}=\\sum_{j=0}^{n-1}{r_{t-j}}&quot;,&quot;id&quot;:&quot;PANFBAROFV&quot;}" data-component-name="LatexBlockToDOM"></div><div><hr></div><h1>Price-SMA Crossover</h1><p>Another commonly used trend following indicator is the price-simple moving average crossover. This is based on comparing the current price with a moving average of past prices. Suppose we compare the current price with a simple moving average (<em>SMA</em>) of the past <em>n</em> prices. For simplicity, let&#8217;s assume we&#8217;re working with log prices <em>p<sub>t</sub>=</em>log(<em>P<sub>t</sub></em>):</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;I_t(n) = \\begin{cases} &amp;1, \\quad p_t-SMA_t(n)\\geq0 \\\\-&amp;1, \\quad  p_t-SMA_t(n)<0\\end{cases}&quot;,&quot;id&quot;:&quot;ZKOPBVHIGP&quot;}" data-component-name="LatexBlockToDOM"></div><p>The <em>SMA</em> is defined as: </p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;SMA_t(n)=\\frac{1}{n}\\sum_{j=0}^{n-1}{p_{t-j}}&quot;,&quot;id&quot;:&quot;BWDJNJVSTX&quot;}" data-component-name="LatexBlockToDOM"></div><p>Then the price/SMA crossover signal can be written as (see the end of the post): </p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;p_t-SMA_t^p(n)=\\sum_{i=0}^{n-2}\\frac{(n-1-i)}{n}\\,r_{t-i}.&quot;,&quot;id&quot;:&quot;WXRQNLHEHN&quot;}" data-component-name="LatexBlockToDOM"></div><p>Therefore, the price/SMA crossover signal is equivalent to a weighted average of the past <em>n-</em>1 returns, where the weights are linearly decreasing. </p><div><hr></div><h1>Price-EMA Crossover</h1><p> What about replacing the simple with an exponential moving average (EMA)? The signal then becomes </p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;I_t(n) = \\begin{cases} &amp;1, \\quad p_t-EMA_t(n)\\geq0 \\\\-&amp;1, \\quad  p_t-EMA_t(n)<0\\end{cases}&quot;,&quot;id&quot;:&quot;BWVYPSZEWC&quot;}" data-component-name="LatexBlockToDOM"></div><p>where </p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;EMA_t=(1-\\lambda)\\sum_{j=0}^{\\infty}\\lambda^j p_{t-j},\n\\qquad 0<\\lambda<1.&quot;,&quot;id&quot;:&quot;OSTVEPSXIT&quot;}" data-component-name="LatexBlockToDOM"></div><p>Following the same strategy used for the price-SMA signal, we can show that </p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;p_t-EMA_t=\\sum_{i=0}^{\\infty}\\lambda^{i+1}r_{t-i}\n=\n\\lambda\\sum_{i=0}^{\\infty}\\lambda^i r_{t-i}.&quot;,&quot;id&quot;:&quot;IFWBTEFRPZ&quot;}" data-component-name="LatexBlockToDOM"></div><p>Noting that multiplication by <em>&#955;</em>&gt;0 doesn&#8217;t change the sign of the signal, the price-EMA crossover rule is equivalent to using the sign of an EMA of returns:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;I_t(n) = \\begin{cases} &amp;1, \\quad \\sum_{i=0}^{\\infty}\\lambda^i r_{t-i}\\geq0 \\\\-&amp;1, \\quad  \\sum_{i=0}^{\\infty}\\lambda^i r_{t-i}<0\\end{cases}&quot;,&quot;id&quot;:&quot;XDNHOOXNXJ&quot;}" data-component-name="LatexBlockToDOM"></div><div><hr></div><h1>Moving Averages in General</h1><p>Valeriy Zakamulin and Javier Giner have several papers that look at trend following in general, and alternative formulations of different rules in terms of returns:</p><ul><li><p>This <a href="https://papers.ssrn.com/sol3/papers.cfm?abstract_id=2585056">paper</a> by Valeriy Zakamulin provides results for other kinds of signals based on different types of weighted averages. </p></li><li><p>In a subsequent <a href="https://www.tandfonline.com/doi/pdf/10.1080/14697688.2020.1716057">paper</a> with Javier Giner, the authors compare time series momentum and different moving average strategies.</p></li><li><p>In a more recent <a href="https://papers.ssrn.com/sol3/papers.cfm?abstract_id=4217513">paper</a>, Zakamulin and Giner look at optimal trend strategies under a two-state regime switching model.</p></li></ul><p>I particularly like the chart below from the last paper, which shows the shape of the weights on past returns for different trend following rules: </p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Y4Gu!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F05e23f30-0af3-48bc-9d6e-8d44df70a547_686x846.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Y4Gu!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F05e23f30-0af3-48bc-9d6e-8d44df70a547_686x846.png 424w, https://substackcdn.com/image/fetch/$s_!Y4Gu!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F05e23f30-0af3-48bc-9d6e-8d44df70a547_686x846.png 848w, https://substackcdn.com/image/fetch/$s_!Y4Gu!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F05e23f30-0af3-48bc-9d6e-8d44df70a547_686x846.png 1272w, https://substackcdn.com/image/fetch/$s_!Y4Gu!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F05e23f30-0af3-48bc-9d6e-8d44df70a547_686x846.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Y4Gu!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F05e23f30-0af3-48bc-9d6e-8d44df70a547_686x846.png" width="686" height="846" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/05e23f30-0af3-48bc-9d6e-8d44df70a547_686x846.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:846,&quot;width&quot;:686,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:65674,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://systematicallybiased.substack.com/i/194497728?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F05e23f30-0af3-48bc-9d6e-8d44df70a547_686x846.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Y4Gu!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F05e23f30-0af3-48bc-9d6e-8d44df70a547_686x846.png 424w, https://substackcdn.com/image/fetch/$s_!Y4Gu!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F05e23f30-0af3-48bc-9d6e-8d44df70a547_686x846.png 848w, https://substackcdn.com/image/fetch/$s_!Y4Gu!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F05e23f30-0af3-48bc-9d6e-8d44df70a547_686x846.png 1272w, https://substackcdn.com/image/fetch/$s_!Y4Gu!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F05e23f30-0af3-48bc-9d6e-8d44df70a547_686x846.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The three cases I mentioned above (time series momentum, price-SMA crossover, and price-EMA crossover) are shown in the chart as MOM, SMA, and EMA. Zakamulin and Giner distinguish the following cases: </p><ul><li><p>Constant weights: using the time series momentum (MOM) is equivalent to using an equal average of past returns.</p></li><li><p>Declining weights: using price/SMA or price/EMA crossover is equivalent to overweighting the most recent returns. </p></li><li><p>Hump-shaped weights that underweight the most recent and most distant returns: this pattern describes different cases like SMA and EMA crossovers.</p></li><li><p>Shapes where the sign of the weights can alternate between positive and negative. This is the case for the moving average convergence/divergence (MACD) indicator. In the last case, the rule negatively weights distant returns, suggesting return reversal at long horizons. </p></li></ul><div><hr></div><h1>Trend Indicators and Return Dynamics</h1><p>This matters because those weighting schemes are not just technical details. They embed views about the dynamics of returns. Equal weights assume that all past returns inside the lookback window matter similarly. Declining weights put more emphasis on recent information. Hump-shaped weights imply that the most informative lags may be somewhere in the middle, while sign-changing weights, as in MACD, effectively combine short-run continuation with long-run reversal. So when we choose a trend rule, we are not just choosing an indicator. We are implicitly choosing a model of return persistence.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://systematicallybiased.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">This Substack is reader-supported. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><div><hr></div><h1>Appendix: Deriving the Price-SMA Crossover Rule</h1><p>The price-SMA crossover signal can be expressed as: </p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;\\begin{align*}\np_t-SMA_t^p(n)\n&amp;=\np_t-\\frac{1}{n}\\sum_{j=0}^{n-1} p_{t-j} \\\\\n&amp;=\n\\frac{1}{n}\\sum_{j=0}^{n-1}(p_t-p_{t-j})\\\\\n&amp;=\\frac{1}{n}\\sum_{j=1}^{n-1}(p_t-p_{t-j})\n\\end{align*}&quot;,&quot;id&quot;:&quot;TGUDRONZMH&quot;}" data-component-name="LatexBlockToDOM"></div><p>In the first passage, we use the fact that <em>p<sub>t</sub></em> can be written as a sum with <em>n</em> terms equal to (1/<em>n</em>)<em>p<sub>t</sub></em>,  which allows us to put <em>p<sub>t</sub></em> inside the sum. In the second passage, we used the fact that the term for <em>j=</em>0 is equal to 0. Next, we use the fact that </p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;p_t-p_{t-j}=r_{t-j,t}=\\sum_{i=0}^{j-1} r_{t-i}&quot;,&quot;id&quot;:&quot;YEDQGELQTQ&quot;}" data-component-name="LatexBlockToDOM"></div><p>Substituting this above gives</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;p_t-SMA_t^p(n)\n=\n\\frac{1}{n}\\sum_{j=1}^{n-1}\\sum_{i=0}^{j-1} r_{t-i}.&quot;,&quot;id&quot;:&quot;WUBDRJGNDD&quot;}" data-component-name="LatexBlockToDOM"></div><p>Expanding this sum, we can see that each return <em>r<sub>t-i</sub></em> appears for all <em>j</em>=<em>i</em>+1,&#8230;,<em>n-1</em>, that is, exactly <em>m</em>-1-<em>i</em> times. Therefore,</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;p_t-SMA_t^p(n)=\\sum_{i=0}^{n-2}\\frac{(n-1-i)}{n}\\,r_{t-i}.&quot;,&quot;id&quot;:&quot;RMJSQIHWXB&quot;}" data-component-name="LatexBlockToDOM"></div><p>The signal in the price/SMA crossover using <em>n </em>prices reduces to a weighted average of <em>n-1</em> returns. We could re-index the rule by the number of returns entering the signal. Let <em>k</em>=<em>n-1</em>, then the rule becomes</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;p_t-SMA_t^p(k+1)=\\sum_{i=0}^{k-1}\\left(\\frac{k-i}{k+1}\\right)r_{t-i},\\qquad k\\ge 1.&quot;,&quot;id&quot;:&quot;IITVZMDKWG&quot;}" data-component-name="LatexBlockToDOM"></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-1" href="#footnote-anchor-1" class="footnote-number" contenteditable="false" target="_self">1</a><div class="footnote-content"><p>Here are some references with links: </p><ul><li><p>Moskowitz, T. J., Ooi, Y. H., &amp; Pedersen, L. H. (2012). <a href="https://doi.org/10.1016/j.jfineco.2011.11.003">Time series momentum</a>. <em>Journal of financial economics</em>, <em>104</em>(2), 228-250.</p></li><li><p>Hurst, B., Ooi, Y. H., &amp; Pedersen, L. H. (2013). <a href="https://www.aqr.com/-/media/AQR/Documents/Insights/Journal-Article/Demystifying-Managed-Futures.pdf">Demystifying managed futures</a>. <em>Journal of Investment Management</em>, <em>11</em>(3), 42-58.</p></li><li><p>Hurst, B., Ooi, Y. H., &amp; Pedersen, L. H. (2017). <a href="https://www.aqr.com/-/media/AQR/Documents/Insights/Journal-Article/AQR-JPM-Fall-2017.pdf">A Century of Evidence on Trend-Following Investing</a>. <em>The Journal of Portfolio Management</em>, 2017, vol. 44, no 1, p. 15-29.</p></li><li><p>Lim, B. Y., Wang, J. G., &amp; Yao, Y. (2018). <a href="https://doi.org/10.1016/j.jbankfin.2018.10.010">Time-series momentum in nearly 100 years of stock returns</a>. <em>Journal of Banking &amp; Finance</em>, <em>97</em>, 283-296.</p></li><li><p>Yang, K., Qian, E., &amp; Belton, B. (2019). <a href="https://www.panagora.com/insights/protecting-the-downside-of-trend-when-it-is-not-your-friend/">Protecting the downside of trend when it is not your friend</a>. <em>The Journal of Portfolio Management</em>, <em>45</em>(5), 99-111.</p></li><li><p>Harvey, C. R., Hoyle, E., Rattray, S., Sargaison, M., Taylor, D., &amp; Van Hemert, O. (2019). <a href="https://papers.ssrn.com/sol3/Delivery.cfm?abstractid=3383173">The best of strategies for the worst of times: Can portfolios be crisis proofed?</a>. <em>The Journal of Portfolio Management</em>, <em>45</em>(5), 7-28.</p></li><li><p>Rubesam, A. (2022). <a href="https://www.pm-research.com/content/iijpormgmt/48/4/241">The Long and the Short of Risk Parity</a>. <em>Journal of Portfolio Management</em>, <em>48</em>(4).</p></li></ul></div></div>]]></content:encoded></item><item><title><![CDATA[Building a Systematic Trading System With AI (Post #3: Building the Backtesting Engine)]]></title><description><![CDATA[From Signals to Tradable Positions]]></description><link>https://systematicallybiased.substack.com/p/building-a-systematic-trading-system-19d</link><guid isPermaLink="false">https://systematicallybiased.substack.com/p/building-a-systematic-trading-system-19d</guid><dc:creator><![CDATA[Systematically Biased]]></dc:creator><pubDate>Tue, 07 Apr 2026 16:25:27 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!0Lme!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0c1ab64b-6b7f-4e70-9557-bfe429f3270b_1712x486.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>This post is part of a series in which I&#8217;m documenting the process of building a functional trading system from scratch using agentic AI. <a href="https://systematicallybiased.substack.com/p/building-a-trend-following-trading?r=6w89b">Post #1</a> was about building the data pipeline (focused on futures for now). <a href="https://systematicallybiased.substack.com/p/building-a-systematic-trading-system?r=6w89b">Post #2</a> covered the design of the infrastructure to handle trading rules (one trading signal for one instrument), trading subsystems (combinations of trading rules for one instrument) and trading strategies (combinations of subsystems for multiple instruments). This post is about the implementation of the backtesting engine. But before I get into that, a quick update.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://systematicallybiased.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">This Substack is reader-supported. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><div><hr></div><h1>Stuff Breaks</h1><p>This series is happening in real time. At this stage, my systematic trading app/platform already &#8220;works&#8221;. I can: </p><ul><li><p>Run a daily routine to update historical data</p></li><li><p>Create strategies with different trading rules and backtest them</p></li><li><p>Connect to my broker</p></li><li><p>Execute orders</p></li><li><p>Keep track of positions by strategy/instrument and automatically reconcile them with broker positions. </p></li></ul><p>I have been paper trading a set of strategies on a group of futures for a few weeks. However, some events have happened that have caused me to make adjustments as I go along: </p><ul><li><p>Data update was still fragile. Depending on when during the day I ran the update routine, I was ending up with incomplete bars for the current trading session. The expected behavior was that, on the next update, the system would complete those bars, but this wasn&#8217;t working, so I had to make some changes. It&#8217;s much more robust now, and I include some automated checks to ensure everything is ok. </p></li><li><p>The data provider ocasionally has its own issues, which made me think about redundancy. For the goal of this project, I&#8217;ll keep it as is for now though, as I&#8217;m aiming for zero cost. </p></li></ul><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!0Lme!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0c1ab64b-6b7f-4e70-9557-bfe429f3270b_1712x486.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!0Lme!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0c1ab64b-6b7f-4e70-9557-bfe429f3270b_1712x486.png 424w, https://substackcdn.com/image/fetch/$s_!0Lme!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0c1ab64b-6b7f-4e70-9557-bfe429f3270b_1712x486.png 848w, https://substackcdn.com/image/fetch/$s_!0Lme!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0c1ab64b-6b7f-4e70-9557-bfe429f3270b_1712x486.png 1272w, https://substackcdn.com/image/fetch/$s_!0Lme!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0c1ab64b-6b7f-4e70-9557-bfe429f3270b_1712x486.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!0Lme!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0c1ab64b-6b7f-4e70-9557-bfe429f3270b_1712x486.png" width="1456" height="413" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/0c1ab64b-6b7f-4e70-9557-bfe429f3270b_1712x486.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:413,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:102377,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://systematicallybiased.substack.com/i/192612358?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0c1ab64b-6b7f-4e70-9557-bfe429f3270b_1712x486.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!0Lme!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0c1ab64b-6b7f-4e70-9557-bfe429f3270b_1712x486.png 424w, https://substackcdn.com/image/fetch/$s_!0Lme!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0c1ab64b-6b7f-4e70-9557-bfe429f3270b_1712x486.png 848w, https://substackcdn.com/image/fetch/$s_!0Lme!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0c1ab64b-6b7f-4e70-9557-bfe429f3270b_1712x486.png 1272w, https://substackcdn.com/image/fetch/$s_!0Lme!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0c1ab64b-6b7f-4e70-9557-bfe429f3270b_1712x486.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><ul><li><p>In the process of dealing with the data issue, I decided at one point to do a clean reset of the DB to reingest all historical data. However, the DB also contained metadata on saved strategies, including the ones I was paper trading. This was, of course, my fault for not having considered it, but after this, I decided to save strategies in a separate DB to avoid this kind of issue. I also implemented some tools for backups. </p><div><hr></div></li></ul><h1>The Backtesting Engine</h1><p>At first glance, backtesting sounds simple. Given a price series and a trading rule, it is easy enough to write a few lines of code that generate positions and compute returns. But that kind of toy backtest is not a backtesting engine. An actual engine has to combine signals across rules and instruments, translate them into tradable contracts, apply volatility targeting and transaction costs, and do all this in a way that remains consistent with how the strategy would actually be traded. The danger of toy backtests is not just that they are simplistic, but that they can give you misleading confidence about whether the strategy can actually be traded.</p><div><hr></div><h1>What the Backtesting Engine Needs to do</h1><p>For this project, the backtesting engine has to solve a very specific problem. It must:</p><ul><li><p>take continuous futures series as the inputs for signal generation</p></li><li><p>allow multiple trading rules per instrument</p></li><li><p>combine those rules into an instrument-level subsystem</p></li><li><p>combine multiple subsystems into a portfolio strategy</p></li><li><p>transform signals into futures contracts</p></li><li><p>apply volatility targeting</p></li><li><p>incorporate transaction costs</p></li><li><p>handle instruments with different data histories</p></li></ul><p>The backtesting engine is not just producing an equity curve. It is acting as the layer that connects research signals to tradable positions, allowing realistic calculation of simulated P&amp;L/returns. This is also slightly different from the usual backtesting approach used in academic papers, which are mostly focused on returns, not necessarily P&amp;L, and which mostly focus only on weights, not position sizes. </p><p>In sum, the engine has to make decisions about hierarchy, position sizing, rebalancing, and contract-level implementation.</p><div><hr></div><h1>Signal Generation vs Position Sizing</h1><p>A second important design choice was to separate signal generation from position sizing. A trading rule should tell us something about direction or conviction. It should not decide final leverage.</p><p>Some rules naturally produce discrete signals such as:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;s_t \\in \\{-1,0,+1\\}&quot;,&quot;id&quot;:&quot;UZGNCRKTLI&quot;}" data-component-name="LatexBlockToDOM"></div><p>Others naturally produce continuous signals bounded in some interval such as:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;s_t \\in [-1,+1]&quot;,&quot;id&quot;:&quot;FLTUEITRXG&quot;}" data-component-name="LatexBlockToDOM"></div><p>But regardless of whether a rule is binary or continuous, the rule output is only an input into the portfolio engine: it is not yet the final position.</p><p>For this project, the cleanest approach was:</p><ul><li><p>trading rules produce signals,</p></li><li><p>subsystems combine signals,</p></li><li><p>the backtesting engine converts the resulting signal into futures positions.</p></li></ul><div><hr></div><h1>Futures </h1><p>With ETFs or stocks, one can often get away with backtesting directly on adjusted price series. Futures are less forgiving. For futures strategies, at least three separate objects matter:</p><ul><li><p>the continuous series used for signal generation,</p></li><li><p>the actual contract being traded at a given point in time,</p></li><li><p>and the contract specifications needed for P&amp;L and risk sizing.</p></li></ul><p>This forces the engine to keep several things separate that are often conflated in simpler systems. For now, the signals of the strategies I&#8217;m considering are computed on continuous series. But positions must ultimately be expressed in real contracts, each with its own price and multiplier.</p><p>That means a backtest engine for futures has to answer two distinct questions:</p><ul><li><p>What is the signal on the synthetic continuous series?</p></li><li><p>What does that imply in terms of contracts in the currently tradable maturity?</p></li></ul><p>For now, I&#8217;m relying on continuous series, which for strategies like trend following or mean reversion, provide a very good approximation. In a future version, I plan to modify the engine to work directly with each instrument&#8217;s contract chain, which will also allow me to incorporate other types of strategies that require trading more than one contract for the same instrument, like carry.</p><div><hr></div><h1>Position Sizing and Volatility Targeting</h1><p>The most important part of the engine is probably how it sizes positions. The engine currently supports two broad approaches:</p><ul><li><p>per-subsystem volatility targeting,</p></li><li><p>portfolio-level volatility targeting with covariance scaling.</p></li></ul><h2>Per-Contract Notional and Dollar Volatility</h2><p>For an instrument indexed by <em>i,</em> define current futures price and contract multiplier as <em>P<sub>i</sub></em> and <em>m<sub>i</sub></em>. The notional value of one contract is then <em>N<sub>i</sub> = P<sub>i</sub> m<sub>i</sub></em>. Let annualized return volatility be <em>&#963;<sub>i, ann </sub></em>.Then annualized dollar volatility per contract is:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;\\sigma_i^{\\$} = \\sigma_{i,\\mathrm{ann}} \\cdot N_i&quot;,&quot;id&quot;:&quot;QBSKGMKIOT&quot;}" data-component-name="LatexBlockToDOM"></div><p>This converts percentage price risk into dollar risk per contract, which is the quantity the engine needs for sizing.</p><h2>Per-Subsystem Vol Targeting</h2><p>Under the simpler approach, each subsystem is sized independently. Suppose account equity is <em>A</em> and the target annualized volatility is <em>&#963;<sup>*</sup></em>.If the subsystem signal is <em>S<sub>i</sub></em> &#8712; [-1,1].  Then the float number of contracts is approximately:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;\\tilde{n}_i = \\frac{A \\cdot \\sigma^{\\ast}}{\\sigma_i^{\\$}} \\cdot S_i&quot;,&quot;id&quot;:&quot;LMMOOFNHLL&quot;}" data-component-name="LatexBlockToDOM"></div><p>The backtest then rounds this to an integer contract count.</p><p>This is simple and intuitive, but since it ignores cross-asset correlations, it will undershoot target volatility.</p><h2>Portfolio-Level Vol Targeting</h2><p>A more interesting case is portfolio-level volatility targeting. Here, we first build inverse-volatility base positions. Suppose each instrument also has a risk budget weight <em>w<sub>i</sub></em>. Then the base float contracts are:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;\\tilde{n}_i =\n\n\\frac{A \\cdot \\sigma^{\\ast} \\cdot w_i}{\\sigma_i^{\\$}} \\cdot S_i&quot;,&quot;id&quot;:&quot;FWMEZRKUWF&quot;}" data-component-name="LatexBlockToDOM"></div><p>This gives a risk-balanced composition (although it still ignores correlations). To translate this into portfolio weights, we use:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;\\tilde{w}_i = \\frac{\\tilde{n}_i \\cdot N_i}{A}&quot;,&quot;id&quot;:&quot;GXXXXCNQNT&quot;}" data-component-name="LatexBlockToDOM"></div><p>Let the annualized covariance matrix of returns be <em>&#931;. </em>The portfolio volatility is then:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;\\hat{\\sigma}_{\\mathrm{port}} =\n\n\\sqrt{\n\n\\tilde{w}^{\\top}\n\n\\Sigma\n\n\\tilde{w}\n\n}&quot;,&quot;id&quot;:&quot;TZVEGYJKXP&quot;}" data-component-name="LatexBlockToDOM"></div><p>To hit the target, the engine computes a global scaling factor:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;k = \\frac{\\sigma^{\\ast}}{\\hat{\\sigma}_{\\mathrm{port}}}&quot;,&quot;id&quot;:&quot;LBTLYUTVEU&quot;}" data-component-name="LatexBlockToDOM"></div><p>and rescales the base contracts:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;n_i = k \\cdot \\tilde{n}_i&quot;,&quot;id&quot;:&quot;AYVBKAVFLP&quot;}" data-component-name="LatexBlockToDOM"></div><p>Only after this step are contracts rounded. This last point is important, because rounding too early produces systematic distortions, especially when capital is small relative to contract size or when many instruments are competing for limited risk budget.</p><p>I&#8217;m also going to implement a full risk budget/parity option soon. </p><div><hr></div><h2>Rebalancing Frequency</h2><p>The system currently handles strategies which compute signals daily. To make backtesting more flexibile, the system can simulate trading at a lower frequency, such as weekly or monthly. This matters because the research frequency of a signal and the trading frequency of a strategy do not always need to coincide. It will also come in handy later for my other book, which has tactical allocation strategies with ETFs that rebalance monthly or weekly.</p><div><hr></div><h2>Transaction Costs</h2><p>Any realistic futures backtest needs to model trading costs in contract space.</p><p>The app currently incorporates two simple but useful cost components:</p><ul><li><p>commission per contract,</p></li><li><p>slippage in ticks.</p></li></ul><p>These costs are applied whenever the position changes, not only when the sign of the signal flips. That matters because a volatility-targeted system may resize positions even when its directional view remains unchanged.</p><p>Commission cost is proportional to the absolute contract change |&#916;<em>n<sub>i</sub></em>|, whereas slippage costs depend on: |&#916;<em>n<sub>i</sub></em>| x tick value. This is still a fairly simple model, and I still need to incorporate rolling costs. </p><div><hr></div><h2>Trading Micro Futures</h2><p>For some futures contracts, different versions exist, typically with different multipliers. The larger contracts usually have longer histories and better quality data. For example, micro Bitcoin futures (MBT) started trading later than the mini contracts BTC. Similarly for ES vs MES. I added functionality that allows me to use the larger contracts for signal generation and simulate trading with the micro by adjusting the contract multipliers and customizing cost assumptions.</p><div><hr></div><h1>What Broke While Building It</h1><p>This part is worth emphasizing because it says something about both the engineering challenge and the use of AI for development. A backtesting engine sounds easy until one starts dealing with the boundary between signal generation and actual trading logic. Some of the issues I ran into included:</p><ul><li><p>I discovered bugs in the contract roll logic for continuous series due to weird backtested P&amp;L,</p></li><li><p>Rounding positions too early was causing issues in some edge cases,</p></li><li><p>Inconsistencies between signal contracts and execution contracts</p></li></ul><p>These were not cosmetic bugs: they reflected places where the internal logic of the system was not yet fully coherent. This was also one of the places where working with an AI agent was most revealing. The agent was very good at producing working code quickly, but consistency across the entire pipeline had to be enforced through repeated testing, inspection, and revision</p><div><hr></div><h1>What the Engine Still Does Not Do Perfectly</h1><p>At this stage, the backtesting engine is good enough to support serious experimentation, which is all I need for now. It is not &#8220;finished,&#8221; and probably never will be in any absolute sense. But it is now coherent enough to connect research signals to tradable positions without relying on the kinds of shortcuts that make toy backtests misleading. </p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://systematicallybiased.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">This Substack is reader-supported. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Building a Systematic Trading System With AI (Post #2: Trading Rules)]]></title><description><![CDATA[Infrastructure for trading rules and strategies]]></description><link>https://systematicallybiased.substack.com/p/building-a-systematic-trading-system</link><guid isPermaLink="false">https://systematicallybiased.substack.com/p/building-a-systematic-trading-system</guid><dc:creator><![CDATA[Systematically Biased]]></dc:creator><pubDate>Sat, 14 Mar 2026 10:50:50 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!EVKC!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fpbs.substack.com%2Fmedia%2FHCG1ijbWQAAOhCV.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>This is the second post in a series in which I&#8217;m documenting an experiment to build a fully functional systematic trading system using AI. You can read part 1, in which I went over the data pipeline for futures contracts <a href="https://systematicallybiased.substack.com/p/building-a-trend-following-trading">here</a>. </p><div><hr></div><h2>A Disclaimer</h2><div class="twitter-embed" data-attrs="{&quot;url&quot;:&quot;https://x.com/qcapital2020/status/2027101000511701023?s=20&quot;,&quot;full_text&quot;:&quot;Hi Claude, build me the Renaissance Medallion Fund with better returns and less risk , don't make any mistakes &quot;,&quot;username&quot;:&quot;qcapital2020&quot;,&quot;name&quot;:&quot;&#59904; Q-Cap &#59904;&quot;,&quot;profile_image_url&quot;:&quot;https://pbs.substack.com/profile_images/1877913985753747456/XvS9wZc5_normal.jpg&quot;,&quot;date&quot;:&quot;2026-02-26T19:18:34.000Z&quot;,&quot;photos&quot;:[{&quot;img_url&quot;:&quot;https://pbs.substack.com/media/HCG1ijbWQAAOhCV.png&quot;,&quot;link_url&quot;:&quot;https://t.co/daYTngKK5z&quot;}],&quot;quoted_tweet&quot;:{},&quot;reply_count&quot;:52,&quot;retweet_count&quot;:205,&quot;like_count&quot;:3155,&quot;impression_count&quot;:155098,&quot;expanded_url&quot;:null,&quot;video_url&quot;:null,&quot;belowTheFold&quot;:false}" data-component-name="Twitter2ToDOM"></div><p>AI is a powerful technology, but with all the <a href="https://x.com/verbove/status/2027135416063623470?s=20">vibe coding hype</a> that has been going around lately, I feel like I need to add some disclaimers:</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://systematicallybiased.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">This Substack is reader-supported. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><ul><li><p>This series is not &#8220;I vibe coded a full trading platform in one day&#8221;. First, it&#8217;s taking much longer than a day. Second, the scope is limited to what I described as a &#8220;functional&#8221; app on the first post of this series. </p></li><li><p>Specifically, I want to explore if/how AI can fill the gaps in my knowledge that I have no time or interest to fix. I have no interest in becoming really good at designing UI or backend systems.</p></li><li><p>I&#8217;m not selling anything. If it turns out to be good enough, I&#8217;ll simply use it myself. </p></li><li><p>This Substack is written by me, not by an AI. Any errors are the result of traditional human stupidity. Also, no em dashes. No <strong>short impactful</strong> sentences with <strong>bold type</strong>. And &#8212; I hope &#8212; no slop. </p></li></ul><div><hr></div><h1>The Framework</h1><p>As a recap, my trading framework is a web-based app running (for now) locally on my machine. In the first post of this series, I covered the data pipeline:</p><ul><li><p>setting up a database</p></li><li><p>ingesting downloaded historical data</p></li><li><p>automatically updating data from a provider</p></li><li><p>creating continuous time series for different futures contracts</p><div><hr></div></li></ul><h2>Building Daily Bars</h2><p>After playing around with the data from my provider, it became clear that their daily bars data were not a good option for me, even for strategies that use daily data. The reason has to do with how futures markets trade (ie markets are open for ~23 hours per day), and how the data are recorded in the provider's daily bar schema, which uses UTC dates. Following the provider&#8217;s recommendation, I switched to a higher frequency (1-hour) schema for data ingestion and now the app reconstructs daily bars internally, matching them to exchange sessions. This change was surprisingly painless. Once I realized the issue, I requested the change to the agent and the implementation worked without any issues. An added benefit is that the app can now handle higher frequency data, which may be handy in the future.</p><p>I also ran several diagnostics and checks on the continuous time series, and I&#8217;m quite confident with the results. With all of that in place, I have clean time series that can be used to generate and test different strategies.  </p><div><hr></div><h2>Hierarchy</h2><p>In this post, I focus on how trading signals are implemented and aggregated into trading subsystems. At this stage my goal is not to fine-tune trading rules, but to build infrastructure that allows systematic experimentation. The framework I&#8217;m developing follows the hierarchy described in the first post:</p><p style="text-align: center;"><strong>trading signal+instrument = trading rule</strong></p><p style="text-align: center;"><strong>trading rules&#8594;trading subsystems&#8594;strategy</strong></p><p>A trading rule applies one signal generation idea to one instrument. A trading subsystem is a collection of trading rules for one instrument. A strategy is a combination of several trading subsystems for multiple instruments.<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-1" href="#footnote-1" target="_self">1</a></p><div><hr></div><h2>Trading Rules</h2><p>The main requirements I had in mind for trading rules were the following: </p><ul><li><p>Ability to add/modify new signal generation rules. Examples:</p><ul><li><p>A time-series momentum rule with a look-back of <em>n</em> days.</p></li><li><p>A Bollinger breakout rule with look-back of <em>n</em> days and multiplier <em>k</em>.</p></li></ul></li><li><p>Ability to toggle between long/short, long-only or even short-only versions of specific rules. Trend-following on equities, for example, tends to work much better as long-only rules. </p></li><li><p>Bulk generation of multiple trading rules for a given instrument.</p><ul><li><p>Example: generating variations of the time-series momentum with <em>n</em>&#8712; {21, 63, 126} days for the instrument ES. <em> </em></p></li></ul></li><li><p>Sweeping rules within a range of parameters and performing simplified backtests (this comes with a very strong warning, which I discuss below). </p></li><li><p>Easy visualization of signals from individual/multiple trading rules. </p><div><hr></div></li></ul><h3>An example</h3><p>In general, my preference is to use signals that generate a discrete output. For example, a time series momentum (TSMOM) signal can be defined as follows: </p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;TSMOM_t(n)=\\left\\lbrace \\begin{array}{cc}\n+1 &amp; \\textrm{if}\\;{r_{t-n,t} > 0}\\\\\n-1 &amp; \\textrm{if}\\;{r_{t-n,t} <0}\n\\end{array}\\right.\n&quot;,&quot;id&quot;:&quot;FXJHQHUOXB&quot;}" data-component-name="LatexBlockToDOM"></div><p>where <em>r<sub>t-n,t</sub></em> is the cumulative return between <em>t-n</em> and <em>t</em>. This is a basic trend following indicator that goes long the asset when the return over the look-back window is positive, and goes short otherwise. The AI agent came up with this: </p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;27f22f90-1b6e-4938-947c-70e692267608&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python">@dataclass(frozen=True)
class TSMOMRule:
    instrument: str
    lookback: int
    name: str | None = None

    def __post_init__(self) -&gt; None:
        if self.lookback &lt;= 0:
            raise ValueError("lookback must be positive")
        if self.name is None:
            object.__setattr__(self, "name", f"tsmom_{self.lookback}")

    def compute_signal(self, data: pd.DataFrame) -&gt; pd.Series:
        if "date" not in data.columns or "close" not in data.columns:
            raise ValueError("Data must include 'date' and 'close' columns")

        df = data.copy()
        df["date"] = pd.to_datetime(df["date"])
        df = df.sort_values("date")

        ret = df["close"] / df["close"].shift(self.lookback) - 1.0
        signal = (ret &gt; 0).astype(int) - (ret &lt; 0).astype(int)
        signal = signal.where(~signal.isna(), 0)
        signal.index = df["date"]
        signal.name = self.name or "tsmom"
        return signal</code></pre></div><p>This seems reasonable. It could have used returns and compounded, or used log returns and summed, but this approach will give the same signal. Of course, creating the code for signal generation is just one part of the puzzle. The other parts include the DB schema, some helper functions (to fetch, insert, delete, edit existing rules), UI layers etc. The agent handled all of that seamlessly for this signal and several other trading signals that I asked for. At the moment, the easiest way to add a new trading signal is to make a request to the agent and have it handle all of that. Of course, if I wanted to do all of this manually, I could, but I&#8217;d be spending a lot of time handling DB migrations and UI design. The AI agent makes all of that very straightforward, but the tradeoff is dependence on the agent. One possibility that I&#8217;m thinking of exploring in the future is to have the agent build the functionality to allow for signal creation/editing directly on the app. The user provides the code for the class in a pre-specified format, the app validates and handles all the dirty work in the backend. </p><p>In terms of bulk creation of trading rules, the solution I implemented allows me to quickly sweep a trading rule within a range of parameters and bulk-save selected ones as individual trading rules.  This leads to an important warning.</p><h3><strong>A word of warning:</strong> </h3><blockquote><p><em>The absolute worst way to do select individual trading rules, which will guarantee overfitting and give you disappointing live results, is by backtesting that rule over a grid of parameters and selecting the best performing one(s). </em></p></blockquote><p>Backtesting is the Schr&#246;dinger&#8217;s cat of systematic investing. Until you backtest an idea, you don&#8217;t know whether a trading rule makes money. Once you backtest it, you have already used the data. If you used the entire historical sample for that instrument, that&#8217;s it, you&#8217;ve opened the door to look-ahead bias. If you now select the parameters/combinations that performed the best in your backtest, the most you can hope for is that your backtest seriously overstates performance.</p><p>Another way to think about it is this. No matter how long the historical time series you used, it&#8217;s still only one observed sample or realized history for that particular financial instrument. Your backtest provides you with <em>one realization</em> of a set of metrics (CAGR, Sharpe ratio etc) that reflects that realized history. A more relevant question is what is the distribution of that performance metric.</p><h3><strong>Some relevant questions</strong></h3><p>There are many things to take into account when selecting trading signals in a systematic trading system. This is not the main focus of this series and has been discussed extensively elsewhere, so I will only highlight a few important questions we should ask:</p><ol><li><p><strong>Why does this make money?</strong> Is it because we&#8217;re collecting a risk premium? Is it a market structure issue? Is it because investors collectively have some sort of cognitive/behavioral bias? If you don&#8217;t have a reasonable explanation for why something works, you won&#8217;t be in a position to understand the risks and probably shouldn&#8217;t trade it.</p></li><li><p><strong>How sensitive is it to implementation details? </strong>Does performance change dramatically if we slightly change the signal definition, sampling frequency, or execution timing? </p></li><li><p><strong>Does it work across instruments?</strong> Does the signal work only on one instrument, or does it appear across multiple markets or asset classes? Signals that work only in one market are more likely to reflect noise or sample-specific effects.</p></li><li><p><strong>Does the signal depend on a specific market regime? </strong>Does the strategy only work during a particular period (e.g., the 2008 crisis, the post-2010 QE era, or the COVID crash)? Strategies that rely heavily on one episode are unlikely to be robust.</p></li><li><p><strong>How correlated is it with existing signals? </strong>A signal that performs well on its own but is highly correlated with existing strategies may add little value to the overall system.</p></li><li><p><strong>How frequently does it trade? </strong>Faster trading signals trade more frequently and therefore incur higher transaction costs. A trading signal that rarely triggers may produce very misleading performance metrics. </p></li><li><p><strong>Does it survive transaction costs? </strong>How much does it cost to trade the instrument, accounting for broker commissions, slippage etc? This is related to the speed of trading.</p></li><li><p><strong>What is the minimum notional exposure and risk?</strong> This is particularly relevant for futures. The minimum notional exposure depends on the contract specs, and the risk depends on the volatility of the instrument. Examples: </p><ol><li><p><strong>ES:</strong> contract multiplier = $50 per S&amp;P 500 index point. At current levels (~6700), one contract has a notional exposure of about $335k. Assuming annualized volatility of 15%, the annualized dollar volatility is roughly $50k.</p></li><li><p><strong>MES:</strong> micro version of the ES contract with a multiplier of $5. One contract therefore has about $33.5k notional exposure and annualized dollar volatility of roughly $5k.</p></li><li><p><strong>MBT:</strong> micro bitcoin futures at CME with a multiplier of 0.10 BTC. At current prices (~$70k), one contract has notional exposure of about $7k. Because bitcoin volatility is much higher (~60%), the annualized dollar volatility is roughly $4.2k.</p></li><li><p><strong>ZF:</strong> contract size = $100,000 face value of a Treasury note. The volatility of ZF is only about 3%, so despite the larger notional the annualized dollar volatility is roughly $3k, lower than one MBT contract with a notional exposure of about $7k.<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-2" href="#footnote-2" target="_self">2</a>   </p><div id="datawrapper-iframe" class="datawrapper-wrap outer" data-attrs="{&quot;url&quot;:&quot;https://datawrapper.dwcdn.net/KTLnj/3/&quot;,&quot;thumbnail_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ab80fc13-5416-48ca-b98c-2a7640dfb807_1220x504.png&quot;,&quot;thumbnail_url_full&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f04f2d0b-dce8-457d-bdcd-d62b3bf3df6e_1220x624.png&quot;,&quot;height&quot;:313,&quot;title&quot;:&quot;[ Notional exposure and annualized $ risk of futures contracts]&quot;,&quot;description&quot;:&quot;&quot;}" data-component-name="DatawrapperToDOM"><iframe id="iframe-datawrapper" class="datawrapper-iframe" src="https://datawrapper.dwcdn.net/KTLnj/3/" width="730" height="313" frameborder="0" scrolling="no"></iframe><script type="text/javascript">!function(){"use strict";window.addEventListener("message",(function(e){if(void 0!==e.data["datawrapper-height"]){var t=document.querySelectorAll("iframe");for(var a in e.data["datawrapper-height"])for(var r=0;r<t.length;r++){if(t[r].contentWindow===e.source)t[r].style.height=e.data["datawrapper-height"][a]+"px"}}}))}();</script></div></li></ol></li></ol><div><hr></div><h2>Trading Subsystems</h2><blockquote><p><em>Diversification is the only free lunch in finance.</em></p></blockquote><p>In a systematic trading program, several layers of diversification are possible :</p><ul><li><p>Across asset classes</p></li><li><p>Across instruments within the same asset class</p></li><li><p>Across trading rules for the same instrument (e.g. combining trend following with carry)</p></li></ul><p>In the hierachy I&#8217;m using, the last type of diversification takes place within a trading subsystem, which combines different trading rules for the same instrument. My requirements for trading subsystems were similar to those for trading rules, with one addition which is the ability to customize the weights assigned to the trading rules:</p><ul><li><p>Aggregation of trading rules into a subsystem. Example: </p><ul><li><p>a trading system for ES could combine variations of  time-series momentum with different lookbacks and a Bollinger breakout rule. </p></li></ul></li><li><p>Ability to customize weights assigned to each trading rule (equal weights, custom weights, or weights obtained through bootstrapping optimization).</p></li><li><p>Ability to force a discrete signal as the output of the subsystem. When aggregating signals from multiple trading rules (eg by averaging), the result is no longer discrete. Using that signal directly mixes signal generation with position sizing. If the agreement between trading rules is informative of market movements, this could be a good idea. For example, when aggregating trend indicators with different lookbacks, as a trend weakens, the signal starts to decrease from the maximum value of +1, reducing exposures. </p><div><hr></div></li></ul><h3>Optimization</h3><p>I&#8217;m generally skeptical of using any kind of optimization due to the risk of overfitting. The worst kind of optimization is full-sample or in-sample optimization: using the full sample of historical data to pick the weights that compose the subsystem.<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-3" href="#footnote-3" target="_self">3</a> This tends to produce very concentrated weights and introduces in-sample overfitting. A better approach is to use a resampling technique such as  <a href="https://en.wikipedia.org/wiki/Bootstrapping_(statistics)">bootstrapping</a>, which works by resampling blocks of the original time series to preserve temporal dependence. Optimizations across each of the samples are then aggregated, which reduces concentrations and gives much more stable results. Although resampling provides less concentrated and more stable results compared with full sample optimization, the results are not truly out-of-sample, unless we do it at each point in time using only past data and allow the weights assigned to different signals to vary over time. I implemented optimization as a research tool, but my preference is for simpler, equal-weighting schemes. </p><div><hr></div><h3>An example: TSMOM on ES</h3><p>Here&#8217;s a concrete example for ES. Research has shown that time-series momentum rules on equities typically work with different lookbacks of up to 12 months. Slower rules (such as a lookback window such as <em>n</em> = 252 days, roughly one year) may take too long to react during sharp market reversals. On the other hand, fast rules (<em>n</em>=21 days, roughly one month) may produce many short signals that are subsequently reversed (i.e., a &#8220;whipsaw&#8221;). At the daily or lower frequency, it&#8217;s very hard to make money shorting the ES. Therefore, I tested long-only versions of TSMOM with lookbacks of 21, 63, 126, 189, and 252 days (1, 3, 6, 9 and 12 months, respectively). Full sample Sharpe ratio optimization gives the following result: </p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Kff0!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa36af874-343f-4a75-8d80-57f818db3a97_546x267.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Kff0!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa36af874-343f-4a75-8d80-57f818db3a97_546x267.png 424w, https://substackcdn.com/image/fetch/$s_!Kff0!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa36af874-343f-4a75-8d80-57f818db3a97_546x267.png 848w, https://substackcdn.com/image/fetch/$s_!Kff0!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa36af874-343f-4a75-8d80-57f818db3a97_546x267.png 1272w, https://substackcdn.com/image/fetch/$s_!Kff0!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa36af874-343f-4a75-8d80-57f818db3a97_546x267.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Kff0!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa36af874-343f-4a75-8d80-57f818db3a97_546x267.png" width="546" height="267" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a36af874-343f-4a75-8d80-57f818db3a97_546x267.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:267,&quot;width&quot;:546,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:38626,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://systematicallybiased.substack.com/i/189918528?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa36af874-343f-4a75-8d80-57f818db3a97_546x267.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Kff0!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa36af874-343f-4a75-8d80-57f818db3a97_546x267.png 424w, https://substackcdn.com/image/fetch/$s_!Kff0!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa36af874-343f-4a75-8d80-57f818db3a97_546x267.png 848w, https://substackcdn.com/image/fetch/$s_!Kff0!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa36af874-343f-4a75-8d80-57f818db3a97_546x267.png 1272w, https://substackcdn.com/image/fetch/$s_!Kff0!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa36af874-343f-4a75-8d80-57f818db3a97_546x267.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The result is not really surprising. Since the rules are highly correlated, the full-sample optimization concentrates on the top two performers (21 and 126 days). </p><p>Using a block bootstrap:<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-4" href="#footnote-4" target="_self">4</a> </p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!sLLc!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feefcbd53-4489-42e4-8382-016904a224a9_554x269.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!sLLc!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feefcbd53-4489-42e4-8382-016904a224a9_554x269.png 424w, https://substackcdn.com/image/fetch/$s_!sLLc!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feefcbd53-4489-42e4-8382-016904a224a9_554x269.png 848w, https://substackcdn.com/image/fetch/$s_!sLLc!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feefcbd53-4489-42e4-8382-016904a224a9_554x269.png 1272w, https://substackcdn.com/image/fetch/$s_!sLLc!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feefcbd53-4489-42e4-8382-016904a224a9_554x269.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!sLLc!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feefcbd53-4489-42e4-8382-016904a224a9_554x269.png" width="554" height="269" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/eefcbd53-4489-42e4-8382-016904a224a9_554x269.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:269,&quot;width&quot;:554,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:39244,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://systematicallybiased.substack.com/i/189918528?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feefcbd53-4489-42e4-8382-016904a224a9_554x269.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!sLLc!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feefcbd53-4489-42e4-8382-016904a224a9_554x269.png 424w, https://substackcdn.com/image/fetch/$s_!sLLc!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feefcbd53-4489-42e4-8382-016904a224a9_554x269.png 848w, https://substackcdn.com/image/fetch/$s_!sLLc!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feefcbd53-4489-42e4-8382-016904a224a9_554x269.png 1272w, https://substackcdn.com/image/fetch/$s_!sLLc!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feefcbd53-4489-42e4-8382-016904a224a9_554x269.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>And finally using an expanding window bootstrap:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!GaNM!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd7322db4-1d42-45f5-b45d-b62e62989ae4_548x261.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!GaNM!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd7322db4-1d42-45f5-b45d-b62e62989ae4_548x261.png 424w, https://substackcdn.com/image/fetch/$s_!GaNM!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd7322db4-1d42-45f5-b45d-b62e62989ae4_548x261.png 848w, https://substackcdn.com/image/fetch/$s_!GaNM!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd7322db4-1d42-45f5-b45d-b62e62989ae4_548x261.png 1272w, https://substackcdn.com/image/fetch/$s_!GaNM!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd7322db4-1d42-45f5-b45d-b62e62989ae4_548x261.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!GaNM!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd7322db4-1d42-45f5-b45d-b62e62989ae4_548x261.png" width="548" height="261" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d7322db4-1d42-45f5-b45d-b62e62989ae4_548x261.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:261,&quot;width&quot;:548,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:39610,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://systematicallybiased.substack.com/i/189918528?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd7322db4-1d42-45f5-b45d-b62e62989ae4_548x261.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!GaNM!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd7322db4-1d42-45f5-b45d-b62e62989ae4_548x261.png 424w, https://substackcdn.com/image/fetch/$s_!GaNM!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd7322db4-1d42-45f5-b45d-b62e62989ae4_548x261.png 848w, https://substackcdn.com/image/fetch/$s_!GaNM!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd7322db4-1d42-45f5-b45d-b62e62989ae4_548x261.png 1272w, https://substackcdn.com/image/fetch/$s_!GaNM!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd7322db4-1d42-45f5-b45d-b62e62989ae4_548x261.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>These results of the bootstrap optimizations suggest reducing the weights of the 189 and 252 days rules. I would argue this is not worth the hassle and my preference would be to equally weight the signals. The chart below shows a composite equally weighted subsystem for ES (end date = March 06, 2026). The recent market movements have reduced exposure, although not to zero. The reason is that the three slower TSMOM rules (<em>n</em>=126, <em>n</em>=189, and <em>n</em>=252) are still active. </p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!8KAh!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd22fa362-ddee-4c93-bc25-4798eb1f931f_933x343.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!8KAh!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd22fa362-ddee-4c93-bc25-4798eb1f931f_933x343.png 424w, https://substackcdn.com/image/fetch/$s_!8KAh!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd22fa362-ddee-4c93-bc25-4798eb1f931f_933x343.png 848w, https://substackcdn.com/image/fetch/$s_!8KAh!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd22fa362-ddee-4c93-bc25-4798eb1f931f_933x343.png 1272w, https://substackcdn.com/image/fetch/$s_!8KAh!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd22fa362-ddee-4c93-bc25-4798eb1f931f_933x343.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!8KAh!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd22fa362-ddee-4c93-bc25-4798eb1f931f_933x343.png" width="933" height="343" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d22fa362-ddee-4c93-bc25-4798eb1f931f_933x343.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:343,&quot;width&quot;:933,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:60315,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://systematicallybiased.substack.com/i/189918528?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd22fa362-ddee-4c93-bc25-4798eb1f931f_933x343.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!8KAh!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd22fa362-ddee-4c93-bc25-4798eb1f931f_933x343.png 424w, https://substackcdn.com/image/fetch/$s_!8KAh!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd22fa362-ddee-4c93-bc25-4798eb1f931f_933x343.png 848w, https://substackcdn.com/image/fetch/$s_!8KAh!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd22fa362-ddee-4c93-bc25-4798eb1f931f_933x343.png 1272w, https://substackcdn.com/image/fetch/$s_!8KAh!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd22fa362-ddee-4c93-bc25-4798eb1f931f_933x343.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div><hr></div><h2>Strategies</h2><p>Strategy construction in this framework is explicitly hierarchical: a trading rule is one signal model on one instrument with one parameter set, a subsystem combines multiple rules for the same instrument, and a strategy combines multiple subsystems across instruments with configurable subsystem weights. Signals are generated daily, can be constrained by position mode (long/short, long-only, short-only), and are then translated into futures contracts through a volatility-targeting engine. For risk control, the app supports (for now) two volatility-targeting modes: </p><ul><li><p><strong>Per-Subsystem</strong>: each subsystem sized independently to a target risk budget</p></li><li><p><strong>Portfolio-Level</strong>: first builds inverse-vol base exposures, then rescales the full vector using the return covariance matrix to target overall portfolio volatility. </p></li></ul><p>Both of these are simple inverse-vol approaches. The per-subsystem approach will undershoot target volatility because it ignores correlations across subsystems. This undershoot occurs because portfolio volatility depends not only on the volatility of individual subsystems but also on their correlations; when each subsystem is scaled independently, the resulting portfolio variance is typically lower than the sum of the individual variance targets. The portfolio-level approach starts from subsystem exposures scaled by inverse volatility and then rescales the full vector to achieve a desired portfolio volatility. A next step would be to use a proper risk parity approach that lets you define risk budgets per instrument or per asset class, as I did on <a href="https://www.pm-research.com/content/iijpormgmt/48/4/241">this paper.</a> </p><div><hr></div><h2>Next: Backtesting</h2><p>On the next part, I will go over the backtesting engine of the app.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://systematicallybiased.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">This Substack is reader-supported. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-1" href="#footnote-anchor-1" class="footnote-number" contenteditable="false" target="_self">1</a><div class="footnote-content"><p>As mentioned in the first post, the hierarchy for trading rules, subsystems and strategies was inspired by <a href="https://www.systematicmoney.org/">this book</a> by Robert Carver.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-2" href="#footnote-anchor-2" class="footnote-number" contenteditable="false" target="_self">2</a><div class="footnote-content"><p>For Treasury futures, practitioners typically measure risk using DV01 (the dollar value of a one-basis-point move in yields) together with yield volatility, rather than price volatility. I use price volatility here only to keep the comparison across contracts simple.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-3" href="#footnote-anchor-3" class="footnote-number" contenteditable="false" target="_self">3</a><div class="footnote-content"><p>If you pre-select only the best trading rules in the previous step, overfitting will have two chances to manifest: first in the pre-selection of the most profitable rules, second in the optimization step.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-4" href="#footnote-anchor-4" class="footnote-number" contenteditable="false" target="_self">4</a><div class="footnote-content"><p>I used 200 bootstrap samples each with size equal to 15% of the length of the available historical time series and a block size of 20 days)</p></div></div>]]></content:encoded></item><item><title><![CDATA[Building a Systematic Trading System With AI (Post #1: Data)]]></title><description><![CDATA[If you&#8217;re here, you&#8217;re probably interested in systematic trading, so we have that in common.]]></description><link>https://systematicallybiased.substack.com/p/building-a-trend-following-trading</link><guid isPermaLink="false">https://systematicallybiased.substack.com/p/building-a-trend-following-trading</guid><dc:creator><![CDATA[Systematically Biased]]></dc:creator><pubDate>Thu, 26 Feb 2026 10:27:17 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!s_zn!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa0510c5e-1aee-4e67-b80f-32ec44a099cc_1024x1536.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>If you&#8217;re here, you&#8217;re probably interested in systematic trading, so we have that in common. Over the years, I&#8217;ve built tools for testing and trading systematic strategies, both in industry and academia. I&#8217;ve written plenty of code, but mostly for research and strategy logic. Whenever I tried to build full applications myself, I ran into the same bottlenecks: UI design, database structure, API integrations, deployment, and keeping code maintainable as complexity grows.</p><p>Recently, I&#8217;ve been experimenting with agentic AI for coding. That led to a concrete experiment:</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://systematicallybiased.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">This Substack is reader-supported. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><blockquote><h4>Can an AI agent build a functional systematic trading system from scratch?</h4></blockquote><p>To make this a meaningful test, I&#8217;m going to focus (mostly) on trend-following using futures, but the idea is to build something general for systematic strategies.<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-1" href="#footnote-1" target="_self">1</a> Using futures will also force the AI agent to deal with some technical details:</p><ul><li><p>Futures data comes with structural complexities: expiries, contract multipliers, tick sizes, roll rules, and the construction of continuous series.</p></li><li><p>Trend following is simple enough to explain clearly, but rich enough to expose real system-design tradeoffs.</p></li><li><p>End-to-end automation is realistic: data ingestion, signal generation, monitoring, and broker execution can all be connected.</p></li></ul><p>In other words, this isn&#8217;t a toy backtest: it&#8217;s a self-contained but non-trivial engineering problem. Throughout this series, I&#8217;ll highlight where the agent was surprisingly effective, where it struggled, where I had to intervene, and where domain knowledge proved indispensable.</p><p>This first post will focus on the data workflow (data acquisition and management). </p><div><hr></div><h1>The AI Agent Hype (or is it?)</h1><p>While I&#8217;m more on the skeptical side regarding the AI hype in general (particularly when it comes to claims about cognition and AGI), the speed of improvement in AI for coding is mind-boggling. A lot of friends who are professional developers are doing very sophisticated things which make me feel like the ape in this <a href="https://www.instagram.com/reels/DU5iSWwkoFX/">meme</a>. They have agents managing agents in self-improving loops that work while they, well, work even more on other things. It&#8217;s clear to me that AI agents are being used everywhere (even at <a href="https://www.thegamer.com/rockstar-actively-embracing-generative-ai-not-for-gta-6/">Rockstar</a>!), but I also see some criticism and risks:</p><ul><li><p>It&#8217;s great for demos, but not for production. </p></li><li><p>What about debugging and code maintenance?</p></li><li><p>Governance and accountability: who is responsible when an AI agent ships bad code?</p></li><li><p>Security vulnerabilities</p></li><li><p>&#8220;AI agent made me forget how to code&#8221;</p></li></ul><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!s_zn!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa0510c5e-1aee-4e67-b80f-32ec44a099cc_1024x1536.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!s_zn!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa0510c5e-1aee-4e67-b80f-32ec44a099cc_1024x1536.png 424w, https://substackcdn.com/image/fetch/$s_!s_zn!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa0510c5e-1aee-4e67-b80f-32ec44a099cc_1024x1536.png 848w, https://substackcdn.com/image/fetch/$s_!s_zn!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa0510c5e-1aee-4e67-b80f-32ec44a099cc_1024x1536.png 1272w, https://substackcdn.com/image/fetch/$s_!s_zn!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa0510c5e-1aee-4e67-b80f-32ec44a099cc_1024x1536.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!s_zn!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa0510c5e-1aee-4e67-b80f-32ec44a099cc_1024x1536.png" width="1024" height="1536" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a0510c5e-1aee-4e67-b80f-32ec44a099cc_1024x1536.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1536,&quot;width&quot;:1024,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:3278633,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://systematicallybiased.substack.com/i/188697747?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa0510c5e-1aee-4e67-b80f-32ec44a099cc_1024x1536.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!s_zn!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa0510c5e-1aee-4e67-b80f-32ec44a099cc_1024x1536.png 424w, https://substackcdn.com/image/fetch/$s_!s_zn!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa0510c5e-1aee-4e67-b80f-32ec44a099cc_1024x1536.png 848w, https://substackcdn.com/image/fetch/$s_!s_zn!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa0510c5e-1aee-4e67-b80f-32ec44a099cc_1024x1536.png 1272w, https://substackcdn.com/image/fetch/$s_!s_zn!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa0510c5e-1aee-4e67-b80f-32ec44a099cc_1024x1536.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div><hr></div><h1>Define &#8220;Functional&#8221;</h1><p>Like many people, I recently started experimenting with AI coding agents. I particularly like how I can quickly prototype simple web apps running locally on my machine to speed up recurring and time-consuming tasks. I&#8217;ve created some apps for teaching, some for research, and some for trading. It&#8217;s perfect for me because it gets me around the gaps in my coding skill set (which I have to admit, I already had little intention of fixing, even before AI agents came along).</p><p>My objective was to build a local web app that could run a systematic trading workflow with minimal hassle. The requirements I had in mind were:</p><ul><li><p><strong>Data pipeline:</strong> ingest historical contract-level data, fetch updates on demand, process continuous futures series, inspect quality/coverage.</p></li><li><p><strong>Research workflow:</strong> generate trading signals, combine them into subsystems/strategies, and backtest across multiple instruments.</p></li><li><p><strong>Execution plumbing:</strong> connect to broker, monitor signals/positions, stage orders, and optionally execute with safeguards.</p></li><li><p><strong>Usable interface:</strong> browser-based UI on a local server. Based on advice from the AI agent, I used <a href="https://streamlit.io/">Streamlit </a>for rapid iteration.</p></li></ul><p>In short, &#8220;functional&#8221; means end-to-end: data, research, execution, and interface.</p><div><hr></div><h1>The Framework</h1><p>The framework I&#8217;m building assumes that systems would trade at most on a daily basis. I have futures in mind for the moment, but it could be adapted to other asset classes like ETFs (in that case, it would look more like a tactical allocation program that should probably trade at most on a weekly or monthly basis, and the level of automation may be overkill).  </p><p>The system is based on the following hierarchy<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-2" href="#footnote-2" target="_self">2</a>:</p><ul><li><p><strong>Parent instrument</strong>: the family of contracts for one instrument. For example, ES is the parent contract for S&amp;P 500 futures trading on the CME. </p><ul><li><p><strong>Children instruments</strong>: associated with each parent are the individual contracts with specific maturities. For example, ESH6 is the ES contract with expiry in March 2026. </p></li><li><p><strong>Continuous series</strong>: associated with each parent, we will build a continuous time series that &#8220;stitches together&#8221; the children contracts. This is needed because as we roll positions, there is a difference in price between the current contract that will expire soon and the next one. </p></li></ul></li><li><p><strong>Trading rule:</strong> a trading rule generates a signal to be long or short a particular instrument. Example: a time-series momentum (TSMOM) rule on ES with a lookback period of 6 months would provide a +1 or -1 signal to be long or short that instrument. </p></li><li><p><strong>Trading subsystem:</strong> a trading subsystem combines trading rules for one parent instrument. These could be either variations of the same trading rule (e.g. TSMOM with lookbacks of 6 and 12 months on ES), or different kinds of trading rules (e.g. TSMOM with lookback of 6 months and a moving average crossover). Later, we need to make choices about how to combine the signals from different trading rules. </p></li><li><p><strong>Strategy</strong>: a combination of several trading subsystems for multiple instruments. When we get to this stage, we will need to focus on risk management and position sizing. I&#8217;ll implement different options and discuss the tradeoffs. </p></li></ul><div><hr></div><h1>How I used the AI Agent</h1><p>I started from scratch and built the app in an exploratory, sequential way. This was partly because I wanted to explore how the AI agent works, but also because I knew that many details and issues would only become clear to me when I started building the app, so I didn&#8217;t write detailed specs to start with. Instead, I initially provided as many details as possible. I had some ideas about the workflows, but I iterated quite a bit with the agent and requested suggestions on some architecture decisions (data layout, DB, web interface). According to my AI agent: </p><blockquote><p>You optimized for fast learning and domain correctness under evolving requirements; professional usage usually optimize for predictability, auditability, and safe integration into established codebases. </p></blockquote><p>I provided some of my own existing code to the AI agent, especially for the backtesting engine, but this was mostly because I wanted it to build something that I would be familiar with.</p><p>Even with this approach, I was able to build something functional fairly quickly. I have no doubt that a professional quant developer with AI minions could build something much more sophisticated within the same time frame.</p><div><hr></div><h1>Show me the Data</h1><p>The first step was to find a data provider. To build a trading/backtesting app, I wanted to find a provider with:</p><ul><li><p>contract-level historical futures data,</p></li><li><p>API access for updates,</p></li><li><p>low enough cost (ideally free) for iterative development.</p></li></ul><p>A friend who is a professional systematic trader recommended I take a look at  <a href="https://databento.com/portal/browse">databento</a>. For daily frequency data, the startup credits when you create a new account are more than enough to download historical data for a large number of futures contracts.<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-3" href="#footnote-3" target="_self">3</a> The downside is that historical data are available for a relatively short period going back to 2010 for most contracts. After setting up the databento account, generating an API key is straightforward. This will come in handy later to automate downloads.</p><p>Databento provides multiple schemas. For this system&#8217;s first milestone, the key ones are:</p><ul><li><p><strong>Definitions</strong><br>Instrument metadata over time: symbol mappings, instrument IDs, activation/expiration timestamps, exchange, tick size, multiplier, and related contract fields.<br>This info will be needed for correct contract mapping and roll logic.</p></li><li><p><strong>OHLCV-1d</strong><br>Daily open/high/low/close/volume bars at instrument level.<br>This is the core dataset needed for backtesting and daily signal generation.</p></li></ul><p>Databento allows downloads in different formats. I chose &#8220;Databento Binary Encoding (DBN)&#8221; which seems to be the most convenient/fastest. I gave the <a href="https://databento.com/docs/standards-and-conventions/databento-binary-encoding#comparison-with-other-encodings-and-formats?historical=python&amp;live=python&amp;reference=python">DBN documentation</a> to the AI agent so that it knew how to work with this format.   </p><h2>Tickers</h2><p>In this series, I&#8217;ll use the following contracts for illustration purposes:</p><ul><li><p><a href="https://www.cmegroup.com/markets/equities/sp/e-mini-sandp500.contractSpecs.html">ES</a> (E-mini S&amp;P 500)</p></li><li><p><a href="https://www.cmegroup.com/markets/interest-rates/us-treasury/5-year-us-treasury-note.html">Z</a>F (5-Year T-Note)</p></li><li><p><a href="https://www.cmegroup.com/markets/cryptocurrencies/bitcoin/bitcoin.contractSpecs.html">BTC</a> (Bitcoin)</p></li><li><p><a href="https://www.cmegroup.com/markets/fx/g10/e-micro-euro.contractSpecs.html">M6E</a> (Micro EUR/USD)</p></li></ul><p>There&#8217;s nothing special about these specific contracts. I chose them because they cover different asset classes and illustrate the complexities of futures data (eg the contracts have different maturities, different contract multipliers etc). </p><div><hr></div><h1>Data Workflow</h1><p>The app supports two operational paths for data workflow:</p><ol><li><p>Download data from API and ingest</p></li><li><p>Ingest previously downloaded local files</p></li></ol><p>In practice, local ingest is often preferable for larger definition pulls, because databento takes a few minutes to prepare downloads of definitions files. In the future, I plan to automate this so I never have to touch it unless I want to add a new ticker.</p><h2><strong>Pipeline Design</strong></h2><ul><li><p>Raw files are stored unchanged in a dedicated folder (data/raw/).</p></li><li><p>Ingestion parses and validates raw files.</p></li><li><p>Cleaned contract-level bars are written to curated Parquet datasets.</p></li><li><p>SQLite stores metadata such as:</p><ul><li><p>parent-to-contract mappings,</p></li><li><p>data coverage windows,</p></li><li><p>parent-level contract specs (exchange, currency, tick size, multiplier, roll settings, adjustment method).</p></li></ul></li></ul><p>This raw/curated/metadata separation keeps updates reproducible and debugging manageable.</p><div><hr></div><h1>Creating Continuous Time Series </h1><p>For backtesting and signal generation, we need to create a continuous time series that &#8220;stitches&#8221; together data from different maturities. There are several details that affect how these series are created<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-4" href="#footnote-4" target="_self">4</a>:</p><h4><strong>Contract listing structure</strong></h4><ul><li><p>Example: ES is listed quarterly (H/M/U/Z), while other markets may list monthly.</p></li></ul><h4><strong>Roll rule</strong></h4><p>The rule for when to roll positions as contracts approach expiry. Examples: </p><ul><li><p>roll N days before expiry.</p></li><li><p>roll on liquidity trigger (e.g., next contract overtakes front contract in volume/open interest).</p></li></ul><h4><strong>Adjustment method</strong></h4><p>This avoids artificial jumps at roll dates. Typical choices are:</p><ul><li><p>Ratio adjustment: multiplicative scaling across rolls.</p></li><li><p>Additive adjustment: constant offsets across rolls.</p></li></ul><p>The app infers available contract months from ingested metadata and lets the user define and save rolling rules per parent contract. Then, it builds or updates continuous series from those definitions, which the user can inspect on charts/tables before using them in backtests. </p><p>Different methods to create continuous series come with different tradeoffs. I chose to implement a methodology that always adjusts prices backwards. This means that the current price for the front contract matches the continuous series. However, whenever a new roll enters the dataset, we need to reprocess the entire series. This takes seconds so it&#8217;s totally acceptable for me. It can also be optimized by storing adjustment factors (which currently I don&#8217;t see a need to do). </p><div><hr></div><h1>Hiccups and Bugs</h1><p>When I started building the app, I wasn&#8217;t quite sure what to expect. This is a breakdown of my experience in this part of the experiment: </p><h3>Where AI Was Strong</h3><ul><li><p>AI agent trivially set up the local database and created worflows to store curated data in parquet format.</p></li><li><p>Likewise, the web app looked decent on the first iteration.</p></li><li><p>Implementing on-demand data updates using the databento API worked straightaway, with a few bugs on date ranges that were easily fixed.</p></li><li><p>The speed to prototype and test new features and make changes in the UI is incredible. </p></li></ul><h3>Where AI Struggled</h3><ul><li><p>UI behavior was occasionally flaky. A recurring issue was certain parts of a view being hidden because the AI agent placed the block in the wrong place. </p></li><li><p>AI tended to infer structure from ingested data that caused noisy metadata because the structure is not homogeneous for all contracts. </p></li><li><p>Occasionally, asking the agent to fix one bug (example symbol normalization) made something else break (although this is pretty common when a human is writing code?)</p></li></ul><h3>Human Intervention</h3><p>I had to intervene heavily in a few cases:</p><ul><li><p>The AI agent inferred contract roll dates incorrectly in a few cases because it was overgeneralizing based on inference about one of the contracts. This caused gaps in the continuous series for some contracts. </p></li><li><p>The first implementation of the contract stitching logic was incorrect. The error was not obvious nor easy to detect. </p></li></ul><h3>Some Lessons</h3><ul><li><p>AI excels at structured &#8220;plumbing&#8221; tasks.</p></li><li><p>It struggles with heterogeneous domain structures.</p></li><li><p>Ambiguous specifications lead to brittle implementations. This was particularly relevant for UI-related requests. I learned that I need to be very precise when describing the behavior I wanted. </p></li></ul><div><hr></div><h1>What the app looks like at this stage</h1><p>In the video below, I go over the data management part of the current version of the app. </p><div class="native-video-embed" data-component-name="VideoPlaceholder" data-attrs="{&quot;mediaUploadId&quot;:&quot;629a9401-26d8-439d-9fed-d949f634eb39&quot;,&quot;duration&quot;:null}"></div><div><hr></div><h1>In the Next Post</h1><p>Systematic trading requires good quality data. This initial step gave me a decent starting point. In the next post in this series, I&#8217;ll move from data plumbing to signal generation:</p><ol><li><p>Defining trading rules</p></li><li><p>Combining rules into subsystems</p></li><li><p>Building strategies combining different instruments</p></li></ol><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://systematicallybiased.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">This Substack is reader-supported. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><div><hr></div><h1>Building Along</h1><p>If you want to build it along, these <a href="https://github.com/SystematicallyBiased/SystematicTrading/blob/main/AI_FUTURES_DATA_APP_SPEC.md">specs </a>can be given to any AI agent. </p><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-1" href="#footnote-anchor-1" class="footnote-number" contenteditable="false" target="_self">1</a><div class="footnote-content"><p>Trend-following has strong long-run empirical support across markets. See for example <a href="https://www.aqr.com/-/media/AQR/Documents/Insights/Journal-Article/Demystifying-Managed-Futures.pdf">this</a>, <a href="https://www.aqr.com/-/media/AQR/Documents/Insights/Journal-Article/Demystifying-Managed-Futures.pdf">this</a>, <a href="https://www.aqr.com/-/media/AQR/Documents/Insights/Journal-Article/AQR-JPM-Fall-2017.pdf">this</a> and <a href="https://www.panagora.com/insights/protecting-the-downside-of-trend-when-it-is-not-your-friend/">this</a>. Trend-following also seems to be particularly helpful during crises: see <a href="https://www.man.com/insights/best-of-strategies-for-the-worst-of-times">this</a> and <a href="https://www.pm-research.com/content/iijpormgmt/48/4/241">this</a>. </p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-2" href="#footnote-anchor-2" class="footnote-number" contenteditable="false" target="_self">2</a><div class="footnote-content"><p>The hierarchy for trading rules, subsystems and strategies was heavily inspired by <a href="https://www.systematicmoney.org/">this book</a> by Robert Carver. The rest diverges because I mostly focus on binary trading signals, while his approach is based on continuous forecasts. On the site for his book, there&#8217;s a link to a python project that implements the systematic trading framework exactly as in his book.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-3" href="#footnote-anchor-3" class="footnote-number" contenteditable="false" target="_self">3</a><div class="footnote-content"><p>I have no affiliation with databento.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-4" href="#footnote-anchor-4" class="footnote-number" contenteditable="false" target="_self">4</a><div class="footnote-content"><p>See <a href="https://quantpedia.com/continuous-futures-contracts-methodology-for-backtesting/">this </a>for a discussion. </p></div></div>]]></content:encoded></item></channel></rss>