<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Matthew Strawbridge&apos;s weblog</title>
    <description>Technical articles and ephemera</description>
    <link>http://matthewstrawbridge.com/</link>
    <atom:link href="http://matthewstrawbridge.com/feed.xml" rel="self" type="application/rss+xml" />
    <pubDate>Sun, 02 Aug 2026 20:33:42 +0100</pubDate>
    <lastBuildDate>Sun, 02 Aug 2026 20:33:42 +0100</lastBuildDate>
    <generator>Jekyll v4.3.4</generator>
    
      <item>
        <title>Serving Python on AWS Lambda with Zappa</title>
        <description>&lt;p&gt;This post introduces the Zappa project – a tool for deploying Python
services to run on AWS Lambda, without needing a server running full-time. If
you write Python applications and have access to an AWS environment, Zappa
offers a distinctive deployment model that can be both cost-effective and
scalable. It’s not right for every project, but when it fits, it can be an
elegant solution worth knowing about.&lt;/p&gt;

&lt;h1 id=&quot;what-is-zappa&quot;&gt;What is Zappa?&lt;/h1&gt;

&lt;p&gt;&lt;img src=&quot;/images/not-frank-zappa.png&quot; alt=&quot;A no entry sign indicating that we&apos;re talking about the Zappa Python library, not Frank Zappa&quot; style=&quot;float: right; margin-left: 10px; margin-bottom: 10px; width: 256px;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://github.com/zappa/Zappa&quot;&gt;Zappa&lt;/a&gt; is a fascinating library for running
Python code, including web apps, on AWS Lambda.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;So, instead of a web server running 24/7, your services get spun up on demand in
response to HTTP requests as they come in, and then destroyed (or at least,
allowed to sleep) again.&lt;/p&gt;

&lt;p&gt;This can be a good fit in a couple of situations:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Low-traffic services.&lt;/strong&gt; Because you pay only for the time when your
service is actively executing code, sites with low traffic can be &lt;em&gt;very&lt;/em&gt;
inexpensive (say, less that $1/month and potentially free) to run in this way.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;High-traffic services.&lt;/strong&gt; Responding to web requests using serverless
Lambda functions effectively gives you infinite parallel web servers.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Zappa does some really clever things behind the scenes to package up your
Python code so that it runs smoothly on AWS Lambda (and it &lt;em&gt;does&lt;/em&gt; need to be
AWS – no other cloud services are supported so far). It builds a zip file
containing all the dependencies needed by your Python code, automatically
fetching Lambda-compatible wheels.&lt;/p&gt;

&lt;p&gt;For deployment, Zappa builds the package Zip, uploads it to AWS S3 blob
storage, loads it into a Lambda function, and deletes the S3 blob again.&lt;/p&gt;

&lt;h1 id=&quot;okay-but-this-only-works-for-simple-things-right&quot;&gt;Okay, but this only works for simple things, right?&lt;/h1&gt;

&lt;p&gt;Well, no. Zappa supports WSGI web servers, including full-blown Django applications.&lt;/p&gt;

&lt;p&gt;For the project I decided to try Zappa with, I chose to
use another library I was interested in learning about:
&lt;a href=&quot;https://github.com/radiac/nanodjango&quot;&gt;nanodjango&lt;/a&gt;. This provides a simple,
lightweight way to start a Django project using a single file, with the
ability to convert it to a full-blown Django project automatically later.&lt;/p&gt;

&lt;h1 id=&quot;setting-up-zappa-in-practice&quot;&gt;Setting up Zappa in practice&lt;/h1&gt;

&lt;p&gt;This isn’t intended to be a full how-to. But I did want to provide some
specific notes from my implementation about several specific areas: package
zip sizes, AWS permissions, and keeping the service warm.&lt;/p&gt;

&lt;h2 id=&quot;package-zip-sizes&quot;&gt;Package zip sizes&lt;/h2&gt;

&lt;p&gt;You can run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;zappa package [env]&lt;/code&gt; to create the package’s zip file locally.
Then you can look inside it to see what’s included. Then you can specify
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;exclude&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;exclude_glob&lt;/code&gt; entries in your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;zappa_settings.json&lt;/code&gt; file to
trim the size down (and to ensure no sensitive information is included).&lt;/p&gt;

&lt;p&gt;I used these settings:&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;w&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;exclude&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;.gitignore&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;.pytest_cache&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;README.md&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;__pycache__&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;_pytest&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;black&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;justfile&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;mypy_extensions.py&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;poetry&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;poetry_plugin_export&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;poetry.lock&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;pyproject.toml&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;pytest&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;zappa_settings.json&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;zappa_settings.py&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;exclude_glob&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;*.dist-info&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;big-day-diaries-prod-template*.json&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;django/test/*&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;docs/*&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;test_*.py&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;which took my package zip file size down from about 65 MiB to about 53 MiB
– a reasonable reduction. I expect there’s more that could be pruned,
particularly unused things inside the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;django/&lt;/code&gt; directory.&lt;/p&gt;

&lt;p&gt;Note that this is, shall we say, chunky for a Lambda function. Zappa issues
a warning that going above 50 MiB probably won’t work, but it does (it runs
fine, but AWS won’t allow you to edit it interactively).&lt;/p&gt;

&lt;h2 id=&quot;minimal-aws-permissions-set&quot;&gt;Minimal AWS permissions set&lt;/h2&gt;

&lt;p&gt;In general, spinning up a working nanodjango site on AWS Lambda using Zappa
was straightforward. But by far the most annoying part of the process was
figuring out the minimal set of AWS permissions that were necessary to enable
the deployment steps to work cleanly.&lt;/p&gt;

&lt;p&gt;It’s good practice not to over-provision the permissions: just use the
minimal subset that will work. Unfortunately, there’s no guidance in Zappa’s
documentation about what’s required, so I fell back into trial and error:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;try to deploy&lt;/li&gt;
  &lt;li&gt;get an error message&lt;/li&gt;
  &lt;li&gt;add the missing permission in AWS&lt;/li&gt;
  &lt;li&gt;tear down and try again.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I applied the following two AWS-managed policies:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AWSLambda_FullAccess&lt;/code&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AWSCodeDeployRoleForLambda&lt;/code&gt;&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In addition, I created a custom policy to allow access to the specific
resources used in this deployment: the relevant Lambda execution role, S3
bucket, API Gateway routes, and CloudFormation stack. It also included limited
permissions for IAM role creation and for interacting with associated services
like CloudFront, Route 53, and CloudWatch Events.&lt;/p&gt;

&lt;p&gt;I’m not sharing the full policy document here, partly because it’s
specific to my setup and partly because I don’t want to mislead anyone into
copying a configuration that might not be appropriate for their environment.&lt;/p&gt;

&lt;p&gt;So, be prepared for some annoyance with this part of the process.&lt;/p&gt;

&lt;h2 id=&quot;keeping-warm&quot;&gt;Keeping warm&lt;/h2&gt;

&lt;p&gt;AWS Lambda caches the Lambda functions for a few minutes so that they are
available straight away when called multiple times in succession. After that,
they are unloaded, and reloading them can take a significant amount of time
(say 30 seconds for these large zip packages). For some workloads, that scale
of delay would be fine, but for responding to Web requests it is too long.&lt;/p&gt;

&lt;p&gt;Zappa includes a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;keep_warm&lt;/code&gt; setting, which will ping the deployed service
every few minutes to keep it warm and in the cache. This way, the service is
always available and can respond immediately to incoming requests. These ping
calls are quick and cheap, so their cost is negligible.&lt;/p&gt;

&lt;h1 id=&quot;example-service&quot;&gt;Example service&lt;/h1&gt;

&lt;p&gt;To test all this out, I created a simple service for generating a custom
PDF countdown calendar for a big day: a birthday,
anniversary, wedding, etc.&lt;/p&gt;

&lt;p&gt;After a few rounds of redesign, the single-page user interface looks like this:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/big-day-screenshot.png&quot; alt=&quot;A screenshot showing the website&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Django is overkill for this: it’s essentially just a form for entering
the title and date, validation of the form, generation of a PDF (I used
&lt;a href=&quot;https://docs.reportlab.com/install/open_source_installation/&quot;&gt;ReportLab&lt;/a&gt; for
this) and serving up the PDF. There’s no database to speak of in this case,
and I was also able to directly embed the small images, which kept things nice
and simple.&lt;/p&gt;

&lt;p&gt;Here is an example extract of the PDFs that this service can generate:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/big-day-pdf-example.png&quot; alt=&quot;An extract of a PDF generated with the BigDay service&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Notice that it marks public holidays and draws quite a pleasing red swirl
around the big day itself. The actual generated PDF is for the full year
leading up to the big day.&lt;/p&gt;

&lt;h1 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h1&gt;

&lt;h2 id=&quot;frameworks&quot;&gt;Frameworks&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;Remember &lt;a href=&quot;https://github.com/zappa/Zappa&quot;&gt;Zappa&lt;/a&gt; in case you need a way to
run Python on AWS Lambda.
    &lt;ul&gt;
      &lt;li&gt;Also take a look at &lt;a href=&quot;https://github.com/aws/chalice&quot;&gt;Chalice&lt;/a&gt;, which is
  another library for deploying Python to AWS Lambda. I haven’t tried it
  myself. It was written by Amazon themselves.&lt;/li&gt;
      &lt;li&gt;Another notable framework is
  &lt;a href=&quot;https://github.com/serverless/serverless&quot;&gt;Serverless&lt;/a&gt;. Again, I haven’t
  tried this.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Consider using &lt;a href=&quot;https://github.com/radiac/nanodjango&quot;&gt;nanodjango&lt;/a&gt; for simple
Django sites that might need to grow over time.&lt;/li&gt;
&lt;/ul&gt;

</description>
        <pubDate>Sat, 26 Apr 2025 13:00:00 +0100</pubDate>
        <link>http://matthewstrawbridge.com/content/2025/serving-python-on-aws-lambda-with-zappa/</link>
        <guid isPermaLink="true">http://matthewstrawbridge.com/content/2025/serving-python-on-aws-lambda-with-zappa/</guid>
        
        <category>software</category>
        
        <category>programming</category>
        
        <category>python</category>
        
        <category>zappa</category>
        
        <category>aws</category>
        
        
        <category>software</category>
        
        <category>programming</category>
        
        <category>python</category>
        
        <category>zappa</category>
        
        <category>aws</category>
        
      </item>
    
      <item>
        <title>Creating a balanced investment portfolio with skfolio in Python</title>
        <description>&lt;div style=&quot;border: 5px solid red; padding: 1em;&quot;&gt;

  &lt;p&gt;&lt;strong&gt;Nothing in this article is financial advice. I am not qualified to give
financial advice. This is simply an educational article showing one way in which
a portfolio could be balanced. No fitness for any purpose is expressed or implied.&lt;/strong&gt;&lt;/p&gt;

&lt;/div&gt;

&lt;h1 id=&quot;motivation&quot;&gt;Motivation&lt;/h1&gt;

&lt;p&gt;&lt;img src=&quot;/images/portfolio3.png&quot; alt=&quot;Performance chart&quot; style=&quot;float: right; margin-left: 10px; margin-bottom: 10px; width: 600px;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;At the start of the tax year, it’s a natural time for those of us lucky enough
to have savings to have a think about how they are invested. Recent turmoil in
world equity markets makes this particularly relevant.&lt;/p&gt;

&lt;p&gt;Like most retail investors, I think I’m better at choosing where to put my
money than I actually am. The &lt;a href=&quot;https://en.wikipedia.org/wiki/Dunning%E2%80%93Kruger_effect&quot;&gt;Dunning–Kruger
effect&lt;/a&gt; is in
full force, and I like to choose my own investments.&lt;/p&gt;

&lt;p&gt;However, I do understand the need to spread risk by having a balanced
portfolio. Being a Python developer, it is natural for me to wish to use
Python to help me achieve this. This article highlights a reasonable approach
that I am considering.&lt;/p&gt;

&lt;p&gt;I shall present various code snippets throughout this post, but the full
listing (repeating those snippets in context) is at the end.&lt;/p&gt;

&lt;div style=&quot;clear: both&quot; /&gt;

&lt;h1 id=&quot;python-libraries&quot;&gt;Python libraries&lt;/h1&gt;

&lt;p&gt;We’ll be using several Python libraries, together with a simple Python script,
to construct balanced investment portfolio.&lt;/p&gt;

&lt;p&gt;I recommend using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;poetry&lt;/code&gt; for dependency management, in which case you can
set things up in an empty directory by running:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;poetry init&lt;/code&gt; (and accepting all the defaults)&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;poetry shell&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;poetry add skfolio&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;poetry add yfinance&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;poetry add matplotlib&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let’s have a quick look at the specific third-part libraries we’ll be using.&lt;/p&gt;

&lt;h2 id=&quot;skfolio&quot;&gt;skfolio&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://skfolio.org/&quot;&gt;skfolio&lt;/a&gt;, so called because it is built on top of
&lt;a href=&quot;https://scikit-learn.org/stable/index.html&quot;&gt;scikit-learn / sklearn&lt;/a&gt;, will be
doing most of the heavy lifting. It is a library specifically for portfolio
optimisation, and has only been around since December 2023.&lt;/p&gt;

&lt;p&gt;skfolio is essentially a toolkit for trying and comparing various portfolio
theory models. It is a very powerful collection of tools, so we will only be
touching on the basics of the functionality it offers in this article.
Nevertheless, we will see how simple it can be to build a plausible investment
portfolio that attempts to maximise returns and minimise risk.&lt;/p&gt;

&lt;h2 id=&quot;yfinance&quot;&gt;yfinance&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://yfinance-python.org/&quot;&gt;yfinance&lt;/a&gt; is a library for downloading
financial data from &lt;a href=&quot;https://finance.yahoo.com/&quot;&gt;Yahoo! Finance&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This is a very convenient way to get free historical data, as long as you need
only daily prices.&lt;/p&gt;

&lt;h2 id=&quot;matplotlib&quot;&gt;Matplotlib&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://matplotlib.org/&quot;&gt;Matplotlib&lt;/a&gt; is the standard charting library
familiar to most Python users.&lt;/p&gt;

&lt;p&gt;We will use it to draw a custom chart of the matrix of correlations between the assets.&lt;/p&gt;

&lt;h1 id=&quot;declaring-an-asset-universe&quot;&gt;Declaring an asset universe&lt;/h1&gt;

&lt;p&gt;A significant task is defining the total set of assets – shares, bonds, exchange-traded funds (ETFs),
etc. – we might consider investing in.&lt;/p&gt;

&lt;p&gt;This will depend partly on what the broker makes available, and partly on
personal preference (for example, you might choose not to consider equities in
certain types of business, or sectors that you think are overvalued).&lt;/p&gt;

&lt;p&gt;For the purpose of this article, I’ll choose a small selection of possible
investments across equities, bonds and commodities. For using this code in
practice I will expand this asset universe considerably: essentially, the more
options the better. There are tools in skfolio for filtering down a large
asset universe into a smaller one, for example by automatically discarding
highly correlated assets, but that is outside the scope of this post.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Choosing the list of potential investments is a non-trivial task. We need to
include the symbol used by Yahoo! Finance, which won’t necessarily match the
one your broker provides. Generally some web searching is necessary, as well
as making sure that the latest prices match up between your broker and
Yahoo!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here is a simple asset universe for us to get started with. Don’t draw any
conclusions from this choice; I’m certainly not making any claims about how
good or bad it would be to invest in any of these, but they do represent some
variety. For practical purposes, you’d choose a much larger pool of potential
investments to work with.&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;ASSETS&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# individual companies
&lt;/span&gt;    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;symbol&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;TW.L&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Taylor Wimpey&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;groups&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;uk&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;equity&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
        &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;management_fee&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;symbol&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;NG.L&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;National Grid&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;groups&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;uk&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;equity&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
        &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;management_fee&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;symbol&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;LLOY.L&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Lloyds Bank&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;groups&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;uk&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;equity&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
        &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;management_fee&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;symbol&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;BARC.L&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Barclays&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;groups&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;uk&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;equity&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
        &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;management_fee&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;symbol&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;MSFT&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Microsoft&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;groups&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;us&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;equity&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;tech&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
        &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;management_fee&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# commodities
&lt;/span&gt;    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;symbol&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;GBSS.L&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Gold Bullion Securities&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;groups&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;commodity&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
        &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;management_fee&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;symbol&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;BCOG.L&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;L&amp;amp;G All Commodities UCITS ETF&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;groups&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;commodity&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
        &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;management_fee&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.02&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# bonds
&lt;/span&gt;    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;symbol&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;0P0000KM23.L&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Vanguard Global Bond Index Fund&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;groups&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;world&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;bonds&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
        &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;management_fee&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.15&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;symbol&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;0P0000XBPM.L&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Invesco Corporate Bond Fund (UK) Z (Acc)&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;groups&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;uk&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;bonds&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
        &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;management_fee&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Notes:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;This dictionary format is just a convenient grouping of information for
later manipulation.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;The symbol has to match the symbol used by Yahoo! Finance. We use this to
download the historic data for the asset.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;The name doesn’t need to match Yahoo! Finance. It can be whatever makes sense to you.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;The groups can be any labels that make sense to you. These will be useful
for setting constraints (see the next section). Each group needs to have its
entries in the same order (here we have used country, asset class and
sub-class). It’s fine to omit entries (i.e. I’ve only marked up the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tech&lt;/code&gt;
sub-class); anything else will be automatically treated as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;None&lt;/code&gt;.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;The management fee is given as an annual percentage. skfolio expects these
to be fractions in the same granularity as the data (i.e. daily), so we’ll
convert them later.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;setting-constraints&quot;&gt;Setting constraints&lt;/h1&gt;

&lt;p&gt;One particularly handy feature of skfolio is how you can describe any hard
constraints you want to impose on the portfolio as a set of readable strings.&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;LINEAR_CONSTRAINTS&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;tech &amp;lt;= 0.2&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;uk &amp;gt;= 0.5&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;us &amp;lt;= 0.1&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;equity &amp;lt;= 0.7&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;bonds &amp;gt;= 0.3&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;commodity &amp;lt;= 0.3&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Here we’re imposing the following constraints:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;tech investments (anything with “tech” in its &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;groups&lt;/code&gt;) must constitute at most 20% of the portfolio.&lt;/li&gt;
  &lt;li&gt;at least 50% of the portfolio must be UK based&lt;/li&gt;
  &lt;li&gt;at most 10% of the portfolio may be US based&lt;/li&gt;
  &lt;li&gt;at most 70% of the portfolio may be invested in equities&lt;/li&gt;
  &lt;li&gt;at least 30% of the portfolio must be invested in bonds&lt;/li&gt;
  &lt;li&gt;at most 30% of the portfolio may be invested in commodities&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can also, if you wish, build up more complex conditions, such as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;uk &amp;gt;= us * 1.5&lt;/code&gt;
if you wanted at least one and a half times as much invested in the UK as in the US.&lt;/p&gt;

&lt;p&gt;Note that we will also separately use this constraint to prevent more than 20%
of the portfolio being invested in any one thing:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;MAX_PROPORTION_IN_ONE_ASSET&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;optimising-a-portfolio-with-skfolio&quot;&gt;Optimising a portfolio with skfolio&lt;/h1&gt;

&lt;p&gt;There’s various glue code needed to fetch and transform the data, but let’s
concentrate on the most interesting part… feeding the data into skfolio:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;X&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;prices_to_returns&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;prices&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;                                         &lt;span class=&quot;err&quot;&gt;①&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;X_train&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;X_test&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;train_test_split&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;X&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;test_size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;0.33&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;shuffle&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;False&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;  &lt;span class=&quot;err&quot;&gt;②&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;model&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;MeanRisk&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;                                                     &lt;span class=&quot;err&quot;&gt;③&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;risk_free_rate&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;RISK_FREE_RATE&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;TRADING_DAYS_PER_YEAR&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;objective_function&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ObjectiveFunction&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;MAXIMIZE_RATIO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;              &lt;span class=&quot;err&quot;&gt;④&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;risk_measure&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;RiskMeasure&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;VARIANCE&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;                                &lt;span class=&quot;err&quot;&gt;⑤&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;min_weights&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;0.0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;max_weights&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;MAX_PROPORTION_IN_ONE_ASSET&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;groups&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;groups&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;linear_constraints&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;LINEAR_CONSTRAINTS&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;management_fees&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;management_fees&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;fit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;X_train&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;portfolio&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;predict&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;X_test&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;It’s impressive how such a small amount of code is needed to achieve the bulk
of what we need for creating a balanced portfolio of investments.&lt;/p&gt;

&lt;p&gt;Let’s highlight some important parts:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;big&gt;①&lt;/big&gt; We convert from raw (closing) prices to linear daily returns: \(\left( \frac{S_t}{S_{t-1}} - 1 \right)\).&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;big&gt;②&lt;/big&gt; Split the historical data into ⅔ training and ⅓ test data.
We’re not shuffling, so we’ll be training on old data and testing against
more recent data.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;big&gt;③&lt;/big&gt; Use the &lt;a href=&quot;https://skfolio.org/generated/skfolio.optimization.MeanRisk.html#skfolio.optimization.MeanRisk&quot;&gt;Mean-Risk Optimization
estimator&lt;/a&gt;,
which is a flexible, general-purpose optimiser.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;big&gt;④&lt;/big&gt; Use the
&lt;a href=&quot;https://skfolio.org/generated/skfolio.optimization.ObjectiveFunction.html#skfolio.optimization.ObjectiveFunction&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MAXIMIZE_RATIO&lt;/code&gt;&lt;/a&gt;
objective function, which tries to maximise the &lt;a href=&quot;https://en.wikipedia.org/wiki/Sharpe_ratio&quot;&gt;Sharpe
ratio&lt;/a&gt; of the portfolio.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;big&gt;⑤&lt;/big&gt; We want the lowest variance portfolio that can achieve the high
Sharpe ratio we find. Ideally we’d like a straight line going up, with no
variance at all!&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So, this set-up will try to maximize the Sharpe ratio subject to the
constraints we set up, and find the portfolio make-up that minimises the
variance to get there.&lt;/p&gt;

&lt;h1 id=&quot;output&quot;&gt;Output&lt;/h1&gt;

&lt;p&gt;As mentioned before, the full code is at the end of this article. But before
we get to that, let’s take a look at the output it generates.&lt;/p&gt;

&lt;h2 id=&quot;historical-price-data&quot;&gt;Historical price data&lt;/h2&gt;

&lt;p&gt;The first thing the script generates is a chart of the historic prices loaded from Yahoo! Finance.&lt;/p&gt;

&lt;p&gt;Look out for any missing data or sudden jumps in price. (These happen more
than you would expect because sometimes there’s a sudden switch in reporting
between pounds and pence for UK assets. We set &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;repair=True&lt;/code&gt; in the call to
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;yf.download()&lt;/code&gt;, which tries to repair these automatically, but it doesn’t
always get it right.)&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/portfolio1.png&quot; alt=&quot;Historical price data chart&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Note that the Y-axis is logarithmic, which makes it easy to see all the
asserts with their very different prices on the same chart.&lt;/p&gt;

&lt;p&gt;Note also the clear effect of the 2008 market crash, and how some assets were
impacted more than others. I wanted enough historic data so that the portfolio
takes bad times into account as well as good.&lt;/p&gt;

&lt;h2 id=&quot;correlation-matrix&quot;&gt;Correlation matrix&lt;/h2&gt;

&lt;p&gt;The next window shown by the script is a (symmetric) correlation matrix, which
shows for each asset how correlated it is with the others.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/portfolio2.png&quot; alt=&quot;Correlation matrix&quot; /&gt;&lt;/p&gt;

&lt;p&gt;A high correlation (i.e. assets whose prices tend to move in the same
direction) is shown in green or yellow. Every asset’s correlation with itself
is 1.0, so the leading diagonal of the matrix is bright yellow.&lt;/p&gt;

&lt;p&gt;An inverse correlation (i.e. assets whose prices tend to move in opposite
directions) is shown in blue.&lt;/p&gt;

&lt;p&gt;The third row, for Lloyds Bank, is a good example. Lloyds Bank shares are
strongly inversely correlated with shares in the National Grid; but they are
strongly positively correlated with shares in Barclays, which shouldn’t be too
surprising.&lt;/p&gt;

&lt;p&gt;Note also how the Vanguard Global Bond Index Fund and the L&amp;amp;G All Commodities
UCITS ETF are very strongly inversely correlated.&lt;/p&gt;

&lt;p&gt;So, at a very simple level, if all your investments were split across the two
banking assets then you would have very low diversification and a high-risk
portfolio (although one that would do very well when banking shares do well).
Conversely, investing only across bonds and commodities is likely to give you
very low risk – and losses in one area are likely to be offset by profits in
the other – but at the same time it might be difficult to make a good return.&lt;/p&gt;

&lt;p&gt;For my real portfolio I chose a much larger asset universe, and wanted to see
a big spread of colours on this chart to give the algorithm plenty of scope
for constructing a balanced portfolio.&lt;/p&gt;

&lt;h2 id=&quot;cumulative-returns&quot;&gt;Cumulative returns&lt;/h2&gt;

&lt;p&gt;The next chart shows what would have happened for the chosen portfolio in the
test period.&lt;/p&gt;

&lt;p&gt;It’s gone up, which is a good sign ;-)&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/portfolio3.png&quot; alt=&quot;Portfolio cumulative returns&quot; /&gt;&lt;/p&gt;

&lt;div style=&quot;background-color: #800000; color: white; padding: 1em;&quot;&gt;

  &lt;p&gt;Here, you’ll notice a tactical error. This chart is supposed to show the
performance over the whole test period. But this was 30 years of history in
a ⅔ training to ⅓ test split, so you’d expect 10 years of data to be shown.&lt;/p&gt;

  &lt;p&gt;If you look back at the historical price data, you’ll notice what’s happened.
Not all the data sets go back to the start of the requested period. In
particular, the Ivesco Corporate Bond Fund doesn’t start until 2019. So
skfolio (specifically the call to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;prices_to_returns&lt;/code&gt;) is considering only the
period with data for all assets, so effectively training on 4 years and
testing on 2 instead of the expected training on 20 and testing on 10. I think
that’s reasonable behaviour, but we need to be aware of it. In practice, the
simplest workaround is probably to discard any assets that don’t stretch back
far enough (see &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DISCARD_SHORT_DATASETS&lt;/code&gt; in the script below).&lt;/p&gt;

&lt;/div&gt;

&lt;h2 id=&quot;portfolio-composition&quot;&gt;Portfolio composition&lt;/h2&gt;

&lt;p&gt;The final chart is the meat and potatoes, what we’ve been working towards.
This shows which assets the algorithm has chosen, and in which proportions.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/portfolio4.png&quot; alt=&quot;Portfolio cumulative returns&quot; /&gt;&lt;/p&gt;

&lt;p&gt;As noted before, this is definitely not investment advice! It’s not even how
I plan to invest for myself, it being a cut-down asset universe. But it is
indicative of the output you might get.&lt;/p&gt;

&lt;p&gt;You can print out the portfolio’s make-up in python, or hover over the chart
to see the tooltips.&lt;/p&gt;

&lt;p&gt;In this run, the portfolio is comprised as follows:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;20% Ivesco Corporate Bond Fund&lt;/li&gt;
  &lt;li&gt;20% National Grid&lt;/li&gt;
  &lt;li&gt;20% Gold Bullion Securities&lt;/li&gt;
  &lt;li&gt;10% L&amp;amp;G All Commodities&lt;/li&gt;
  &lt;li&gt;10% Vanguard Global Bond Index Fund&lt;/li&gt;
  &lt;li&gt;10% Microsoft&lt;/li&gt;
  &lt;li&gt;5% Barclays&lt;/li&gt;
  &lt;li&gt;3% Lloyd’s Bank&lt;/li&gt;
  &lt;li&gt;2% Taylor Wimpey&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this case, we had nine assets in our universe and skfolio has allocated
some percentage of the portfolio to all of them. That won’t typically be the
case for a more realistically large asset universe.&lt;/p&gt;

&lt;p&gt;Let’s refer back to the constraints we set:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;tech&lt;/strong&gt; investments must constitute at most 20% of the portfolio.
    &lt;ul&gt;
      &lt;li&gt;10% Microsoft &amp;lt;= 20% &lt;i class=&quot;fa fa-check&quot; style=&quot;color: green&quot;&gt;&lt;/i&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;at least 50% of the portfolio must be &lt;strong&gt;UK&lt;/strong&gt; based
    &lt;ul&gt;
      &lt;li&gt;20% Ivesco + 20% National Grid + 5% Barclays + 3% Lloyd’s + 2% Taylor Wimpey &amp;gt;= 50% &lt;i class=&quot;fa fa-check&quot; style=&quot;color: green&quot;&gt;&lt;/i&gt;&lt;/li&gt;
      &lt;li&gt;Since it’s exactly 50% in this case, maybe this is a challenging condition to satisfy.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;at most 10% of the portfolio may be &lt;strong&gt;US&lt;/strong&gt; based
    &lt;ul&gt;
      &lt;li&gt;10% Microsoft &amp;lt;= 10% &lt;i class=&quot;fa fa-check&quot; style=&quot;color: green&quot;&gt;&lt;/i&gt;&lt;/li&gt;
      &lt;li&gt;Again, since it’s exactly on the threshold, it’s likely that more would have been invested in Microsoft if not for this specific constraint.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;at most 70% of the portfolio may be invested in &lt;strong&gt;equities&lt;/strong&gt;
    &lt;ul&gt;
      &lt;li&gt;20% National Grid + 10% Microsoft + 5% Barclays + 3% Lloyd’s + 2% Taylor Wimpey &amp;lt;= 70% &lt;i class=&quot;fa fa-check&quot; style=&quot;color: green&quot;&gt;&lt;/i&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;at least 30% of the portfolio must be invested in &lt;strong&gt;bonds&lt;/strong&gt;
    &lt;ul&gt;
      &lt;li&gt;20% Ivesco + 10% Vanguard &amp;gt;= 30% &lt;i class=&quot;fa fa-check&quot; style=&quot;color: green&quot;&gt;&lt;/i&gt;&lt;/li&gt;
      &lt;li&gt;Another one on the borderline&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;at most 30% of the portfolio may be invested in &lt;strong&gt;commodities&lt;/strong&gt;
    &lt;ul&gt;
      &lt;li&gt;30% Gold Bullion Securities + 10% L&amp;amp;G Commodities &amp;lt;= 30% &lt;i class=&quot;fa fa-check&quot; style=&quot;color: green&quot;&gt;&lt;/i&gt;&lt;/li&gt;
      &lt;li&gt;And, again, right on the borderline.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So, it meets all the constraints that were set and seems like it would have
done well if we had invested in such a portfolio for the last couple of years.&lt;/p&gt;

&lt;p&gt;As the saying goes, past performance is no guarantee of future success (but it’s all we’ve got).&lt;/p&gt;

&lt;p&gt;For those, like me, determined to pick their own investments, skfolio is
certainly a more rigorous approach to take over stock picking. I can still
control the universe of assets that I am willing to consider investing in, but
have some guard rails in terms of imposing sensible constraints on the overall
make-up of the portfolio, and some confidence that it will be balanced to
maximise returns without undue risk.&lt;/p&gt;

&lt;h1 id=&quot;full-script&quot;&gt;Full script&lt;/h1&gt;

&lt;p&gt;Finally, as promised, here is the full python script I used in this article.&lt;/p&gt;

&lt;p&gt;Remember, this is purely illustrative. I won’t even be using it myself in this
form. I would want to add a much larger asset universe to choose from, flip
the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DISCARD_SHORT_DATASETS&lt;/code&gt; flag to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;True&lt;/code&gt;, and probably take a bit less
history so that fewer assets get discarded, among other changes.&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;datetime&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;date&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;timedelta&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;datetime&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pathlib&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Path&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pprint&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pprint&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;skfolio&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;RiskMeasure&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;skfolio.moments&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;EmpiricalCovariance&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;skfolio.moments&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;EmpiricalMu&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;skfolio.optimization&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;MeanRisk&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ObjectiveFunction&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;skfolio.portfolio&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Portfolio&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;skfolio.preprocessing&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;prices_to_returns&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sklearn.model_selection&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;train_test_split&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;typing&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Optional&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Union&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;matplotlib.pyplot&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;plt&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;numpy&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;np&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pandas&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pd&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;yfinance&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;yf&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;RISK_FREE_RATE&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.02&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;TRADING_DAYS_PER_YEAR&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;252&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;HISTORY_YEARS&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;30&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# want at least one recession in the training data
&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# Generally want this to be True, but for this sample script that will discard too many
# assets and the optimiser won&apos;t be able to find a solution.
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;DISCARD_SHORT_DATASETS&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;False&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;MAX_PROPORTION_IN_ONE_ASSET&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.2&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;LINEAR_CONSTRAINTS&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;tech &amp;lt;= 0.2&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;uk &amp;gt;= 0.5&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;us &amp;lt;= 0.1&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;equity &amp;lt;= 0.7&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;bonds &amp;gt;= 0.3&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;commodity &amp;lt;= 0.3&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Management fees here are annual percentages, e.g. 1.0 represents a 1% annual fee.
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ASSETS&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# individual companies
&lt;/span&gt;    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;symbol&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;TW.L&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Taylor Wimpey&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;groups&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;uk&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;equity&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
        &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;management_fee&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;symbol&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;NG.L&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;National Grid&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;groups&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;uk&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;equity&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
        &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;management_fee&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;symbol&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;LLOY.L&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Lloyds Bank&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;groups&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;uk&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;equity&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
        &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;management_fee&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;symbol&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;BARC.L&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Barclays&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;groups&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;uk&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;equity&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
        &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;management_fee&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;symbol&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;MSFT&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Microsoft&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;groups&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;us&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;equity&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;tech&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
        &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;management_fee&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# commodities
&lt;/span&gt;    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;symbol&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;GBSS.L&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Gold Bullion Securities&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;groups&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;commodity&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
        &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;management_fee&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;symbol&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;BCOG.L&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;L&amp;amp;G All Commodities UCITS ETF&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;groups&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;commodity&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
        &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;management_fee&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.02&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# bonds
&lt;/span&gt;    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;symbol&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;0P0000KM23.L&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Vanguard Global Bond Index Fund&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;groups&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;world&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;bonds&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
        &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;management_fee&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.15&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;symbol&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;0P0000XBPM.L&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Invesco Corporate Bond Fund (UK) Z (Acc)&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;groups&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;uk&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;bonds&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
        &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;management_fee&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;


&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;get_adjusted_close&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;ticker&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;start_date&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Optional&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;date&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;end_date&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Optional&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;date&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;cache_dir&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Union&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;cache&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pd&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Series&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;end_date&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;end_date&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;date&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;today&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;start_date&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;start_date&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;end_date&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;timedelta&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;days&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;HISTORY_YEARS&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;365&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;cache_path&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cache_dir&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;cache_path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;mkdir&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;parents&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;exist_ok&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;start_str&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;start_date&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;isoformat&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;end_str&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;end_date&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;isoformat&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;cache_file&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cache_path&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;sa&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ticker&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;start_str&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;end_str&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;.pkl&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;

    &lt;span class=&quot;nf&quot;&gt;if &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;cache_file&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;exists&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
        &lt;span class=&quot;ow&quot;&gt;and&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;datetime&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;fromtimestamp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cache_file&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;stat&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;st_mtime&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;date&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;date&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;today&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sa&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Reading &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ticker&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; from cache.&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pd&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;read_pickle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cache_file&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sa&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Reading &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ticker&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; from Yahoo Finance.&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;yf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;download&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;ticker&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;start_str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;end_str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;repair&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# automatically cope with 100x switches between pounds and pence
&lt;/span&gt;        &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_pickle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cache_file&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;raise&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;RunTimeError&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sa&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Failed to download data for &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ticker&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;. Stopping.&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Adj Close&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Adj Close&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Close&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;


&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;optimise&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;prices&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pd&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;DataFrame&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;groups&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;dict&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]],&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;management_fees&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;dict&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;float&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# plot all the price data to check it&apos;s sensible
&lt;/span&gt;    &lt;span class=&quot;n&quot;&gt;prices&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;plot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;logy&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;DISCARD_SHORT_DATASETS&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;cols_to_drop&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;prices&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;columns&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;prices&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;iloc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;isna&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()]&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Dropping assets where the data doesn&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;t go back far enough:&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cols_to_drop&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;prices&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;prices&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;drop&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;columns&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cols_to_drop&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;X&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;prices_to_returns&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;prices&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;X_train&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;X_test&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;train_test_split&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;X&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;test_size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;0.33&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;shuffle&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;False&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;model&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;MeanRisk&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;risk_free_rate&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;RISK_FREE_RATE&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;TRADING_DAYS_PER_YEAR&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;objective_function&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ObjectiveFunction&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;MAXIMIZE_RATIO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;risk_measure&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;RiskMeasure&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;VARIANCE&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;min_weights&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;0.0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;max_weights&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;MAX_PROPORTION_IN_ONE_ASSET&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;groups&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;groups&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;linear_constraints&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;LINEAR_CONSTRAINTS&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;management_fees&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;management_fees&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;fit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;X_train&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;portfolio&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;predict&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;X_test&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;nf&quot;&gt;pprint&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;portfolio&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;summary&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;returns&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;portfolio&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;plot_cumulative_returns&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;returns&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;show&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

    &lt;span class=&quot;nf&quot;&gt;plot_correlation_matrix&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;prices&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;corr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;prices&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;composition&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;portfolio&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;plot_composition&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;composition&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;show&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;


&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;plot_correlation_matrix&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cov&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;np&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ndarray&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;labels&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;fig&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ax&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;plt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;subplots&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;figsize&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;6&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;cax&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ax&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;matshow&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cov&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cmap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;viridis&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;fig&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;colorbar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cax&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;labels&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;ax&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;set_xticks&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;labels&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)))&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;ax&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;set_yticks&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;labels&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)))&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;ax&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;set_xticklabels&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;labels&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rotation&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;90&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;ax&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;set_yticklabels&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;labels&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;ax&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;set_title&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Correlation Matrix&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pad&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;20&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;plt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;tight_layout&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;plt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;show&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;


&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;__name__&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;__main__&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;price_series&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;get_adjusted_close&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;asset&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;symbol&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;rename&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;columns&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;asset&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;symbol&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;asset&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]}&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;asset&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ASSETS&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;prices&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pd&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;concat&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;price_series&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;axis&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;series&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;prices&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sa&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Latest price for &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;series&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; is &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;prices&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;series&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;groups&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;asset&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;asset&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;groups&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;asset&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ASSETS&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;management_fees&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;asset&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;asset&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;management_fee&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;TRADING_DAYS_PER_YEAR&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;asset&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ASSETS&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;nf&quot;&gt;optimise&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;prices&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;groups&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;management_fees&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;edits&quot;&gt;Edits:&lt;/h2&gt;

&lt;p&gt;2025-04-09 Removed pointless second constraint for the US from the full code listing.&lt;/p&gt;

</description>
        <pubDate>Tue, 08 Apr 2025 09:00:00 +0100</pubDate>
        <link>http://matthewstrawbridge.com/content/2025/creating-a-balanced-investment-portfolio-with-skfolio-in-python/</link>
        <guid isPermaLink="true">http://matthewstrawbridge.com/content/2025/creating-a-balanced-investment-portfolio-with-skfolio-in-python/</guid>
        
        <category>software</category>
        
        <category>programming</category>
        
        <category>python</category>
        
        <category>finance</category>
        
        <category>skfolio</category>
        
        
        <category>software</category>
        
        <category>programming</category>
        
        <category>python</category>
        
        <category>finance</category>
        
        <category>skfolio</category>
        
      </item>
    
      <item>
        <title>Ghostly paragraphs in MkDocs</title>
        <description>&lt;p&gt;&lt;img src=&quot;/images/ghost-paragraph.png&quot; alt=&quot;A ghostly P shape&quot; style=&quot;float: right; margin-left: 10px; margin-bottom: 10px; width: 200px; border-radius: 5px;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I really like &lt;a href=&quot;https://www.mkdocs.org/&quot;&gt;MkDocs&lt;/a&gt; as a documentation tool. It’s
based on Markdown, looks great (especially with the popular &lt;a href=&quot;https://squidfunk.github.io/mkdocs-material/&quot;&gt;Material for
MkDocs&lt;/a&gt; theme), has a great
range of plug-ins, and can automatically rebuild and serve changes during
local development.&lt;/p&gt;

&lt;p&gt;Recently, I ran into a curious problem — here’s what happened, how I
recreated it, and how you can fix it.&lt;/p&gt;

&lt;h2 id=&quot;ghostly-goings-on&quot;&gt;Ghostly goings-on&lt;/h2&gt;

&lt;h3 id=&quot;background&quot;&gt;Background&lt;/h3&gt;

&lt;p&gt;Pages were mysteriously getting empty &amp;lt;p&amp;gt;&amp;lt;/p&amp;gt; HTML elements inserted, and I
couldn’t figure out why.&lt;/p&gt;

&lt;h3 id=&quot;the-set-up&quot;&gt;The set-up&lt;/h3&gt;

&lt;p&gt;Let’s go step by step through how to reproduce the problem I was seeing by
building an empty MkDocs project from scratch in a Docker container.&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;docker run &lt;span class=&quot;nt&quot;&gt;-it&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--rm&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-p&lt;/span&gt; 8000:8000 python:latest bash

&lt;span class=&quot;c&quot;&gt;# pip install mkdocs&lt;/span&gt;

...

&lt;span class=&quot;nb&quot;&gt;cd &lt;/span&gt;home
mkdocs new spooky
&lt;span class=&quot;nb&quot;&gt;cd &lt;/span&gt;spooky
mkdocs serve &lt;span class=&quot;nt&quot;&gt;--dev-addr&lt;/span&gt; 0.0.0.0:8000 &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; /dev/null 2&amp;gt;&amp;amp;1 &amp;amp;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Next, browse to http://127.0.0.1:8000/ on the host machine. You should see a
default page being served from inside the Docker container.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/mkdocs-default-page.png&quot; alt=&quot;MkDocs default page&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;reproducing-the-problem&quot;&gt;Reproducing the problem&lt;/h3&gt;

&lt;p&gt;Now, let’s replace the default content with a setup that demonstrates the
issue with “ghostly” empty elements. We want a paragraph of text, followed
by an HTML table where the content should be treated as Markdown. Although
slightly contrived, this example is based on a real problem seen in the wild.&lt;/p&gt;

&lt;p&gt;While Markdown’s own table syntax is simpler for basic tables,
using HTML tables offers greater flexibility, especially for
complex layouts. So that is what we will be using, with the
&lt;a href=&quot;https://python-markdown.github.io/extensions/md_in_html/&quot;&gt;md_in_html&lt;/a&gt;
extension to allow markdown content inside the tables. Let’s enable it.&lt;/p&gt;

&lt;p&gt;Press Ctrl+C to kill the mkdocs process, then update the MkDocs settings like this:&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;markdown_extensions:&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;  - md_in_html&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt; mkdocs.yml
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Next, we want to install an editor to make it easy to modify the Markdown
source file.&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;apt update

apt &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;vim
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;vim docs/index.html
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Change the content as follows. Reload the web page and note how the
corresponding HTML has been generated.&lt;/p&gt;

&lt;div class=&quot;language-markdown highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;gh&quot;&gt;# Example&lt;/span&gt;

Paragraph before.

&lt;span class=&quot;nt&quot;&gt;&amp;lt;div&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;outer&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;markdown=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;1&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&amp;lt;div&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;inner&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;markdown=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;1&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;Some &lt;span class=&quot;gs&quot;&gt;**bold**&lt;/span&gt; text.&lt;span class=&quot;nt&quot;&gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

Paragraph after.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div style=&quot;text-align: center; margin: 1em;&quot;&gt;&lt;i class=&quot;fa-solid fa-circle-down fa-2xl&quot;&gt;&lt;/i&gt;&lt;/div&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;h1&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;example&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;Example&lt;span class=&quot;nt&quot;&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Paragraph before.&lt;span class=&quot;nt&quot;&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;div&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;outer&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;p&amp;gt;&amp;lt;div&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;inner&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;markdown=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;1&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;Some &lt;span class=&quot;nt&quot;&gt;&amp;lt;strong&amp;gt;&lt;/span&gt;bold&lt;span class=&quot;nt&quot;&gt;&amp;lt;/strong&amp;gt;&lt;/span&gt; text.&lt;span class=&quot;nt&quot;&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;  ⮄ paragraph AROUND div 😈
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Paragraph after.&lt;span class=&quot;nt&quot;&gt;&amp;lt;/p&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Note the unexpected &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;p&amp;gt;&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;/p&amp;gt;&lt;/code&gt; tags.&lt;/p&gt;

&lt;p&gt;However, notice what happens if we add some line breaks. The same Markdown
content now produces different HTML output:&lt;/p&gt;

&lt;div class=&quot;language-markdown highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;gh&quot;&gt;# Example&lt;/span&gt;

Paragraph before.

&lt;span class=&quot;nt&quot;&gt;&amp;lt;div&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;outer&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;markdown=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;1&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;div&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;inner&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;markdown=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;1&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;Some &lt;span class=&quot;gs&quot;&gt;**bold**&lt;/span&gt; text.&lt;span class=&quot;nt&quot;&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

Paragraph after.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div style=&quot;text-align: center; margin: 1em;&quot;&gt;&lt;i class=&quot;fa-solid fa-circle-down fa-2xl&quot;&gt;&lt;/i&gt;&lt;/div&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;h1&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;example&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;Example&lt;span class=&quot;nt&quot;&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Paragraph before.&lt;span class=&quot;nt&quot;&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;div&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;outer&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;div&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;inner&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Some &lt;span class=&quot;nt&quot;&gt;&amp;lt;strong&amp;gt;&lt;/span&gt;bold&lt;span class=&quot;nt&quot;&gt;&amp;lt;/strong&amp;gt;&lt;/span&gt; text.&lt;span class=&quot;nt&quot;&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;  ⮄ paragraph INSIDE div 😇
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Paragraph after.&lt;span class=&quot;nt&quot;&gt;&amp;lt;/p&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now the content of the inner &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;div&lt;/code&gt; contains a paragraph of text, but there are
no &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;p&amp;gt;&amp;lt;/p&amp;gt;&lt;/code&gt; tags wrapping the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;div&lt;/code&gt;, which is reasonable behaviour.&lt;/p&gt;

&lt;p&gt;So, to clarify, the original looked like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;p&amp;gt;&amp;lt;div&amp;gt;...&amp;lt;/div&amp;gt;&amp;lt;/p&amp;gt;&lt;/code&gt; and, just
by modifying the line breaks in the Markdown, the HTML output changed to
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;div&amp;gt;&amp;lt;p&amp;gt;...&amp;lt;/p&amp;gt;&amp;lt;/div&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;You can take it a step further by specifying the inner &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;div&lt;/code&gt; as a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;span&lt;/code&gt;
element (i.e. inline rather than block-level) like this:&lt;/p&gt;

&lt;div class=&quot;language-markdown highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;gh&quot;&gt;# Example&lt;/span&gt;

Paragraph before.

&lt;span class=&quot;nt&quot;&gt;&amp;lt;div&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;outer&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;markdown=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;1&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;div&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;inner&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;markdown=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;span&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;  ⮄ note the span
Some &lt;span class=&quot;gs&quot;&gt;**bold**&lt;/span&gt; text.
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

Paragraph after.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div style=&quot;text-align: center; margin: 1em;&quot;&gt;&lt;i class=&quot;fa-solid fa-circle-down fa-2xl&quot;&gt;&lt;/i&gt;&lt;/div&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;h1&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;example&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;Example&lt;span class=&quot;nt&quot;&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Paragraph before.&lt;span class=&quot;nt&quot;&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;div&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;outer&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;div&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;inner&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
Some &lt;span class=&quot;nt&quot;&gt;&amp;lt;strong&amp;gt;&lt;/span&gt;bold&lt;span class=&quot;nt&quot;&gt;&amp;lt;/strong&amp;gt;&lt;/span&gt; text. ⮄ no paragraph tags at all 😇
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Paragraph after.&lt;span class=&quot;nt&quot;&gt;&amp;lt;/p&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;does-it-matter&quot;&gt;Does it matter?&lt;/h2&gt;

&lt;p&gt;What prompted this post was a “bug” where empty &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;p&amp;gt;&amp;lt;/p&amp;gt;&lt;/code&gt; appeared &lt;em&gt;before&lt;/em&gt;
a table with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;markdown=&quot;1&quot;&lt;/code&gt;. These ghost paragraphs seemed to come out of
nowhere. Even though my MkDocs theme was suppressing display of these empty
paragraphs, they were still affecting the CSS styling when printing because
my heading had “keep with next” styling: these empty paragraphs became the
next elements, so headings could still appear at the bottom of printed pages.&lt;/p&gt;

&lt;p&gt;The examples in this post are a simplified version (I had difficulty
reproducing the original issue fully). This issue took a lot of debugging and
head-scratching to resolve, and I found little help online. Hopefully, this
post will save someone else from the same frustration.&lt;/p&gt;

&lt;h2 id=&quot;so-whats-going-on&quot;&gt;So, what’s going on?&lt;/h2&gt;

&lt;p&gt;The &lt;a href=&quot;https://spec.commonmark.org/&quot;&gt;CommonMark spec&lt;/a&gt; attempts to
unambiguously define the syntax of Markdown. The &lt;a href=&quot;https://spec.commonmark.org/0.31.2/#html-blocks&quot;&gt;section about HTML
blocks&lt;/a&gt; goes into a lot of
detail about HTML in Markdown, including many examples.&lt;/p&gt;

&lt;p&gt;There are several layers of things happening here:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;Processing of HTML blocks in Markdown content is part of standard Markdown
processing. At this level, any Markdown syntax inside the
HTML content will be treated literally as HTML content.
    &lt;ul&gt;
      &lt;li&gt;The HTML content will just be identified and then passed through to the
 rendered page unchanged. It is not parsed, and need not be valid.&lt;/li&gt;
      &lt;li&gt;Blank lines will be treated as the end of an HTML block, and cause the
 Markdown processor to switch back to rendering Markdown (with unexpected
 consequences if you have blank lines inside the HTML block).&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;The second layer involves processing the Markdown content inside
HTML blocks inside Markdown. It is triggered by the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;markdown=&quot;...&quot;&lt;/code&gt;
attribute, which is supported by some markdown processors. In the
case of MkDocs, this is provided by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;python-markdown&lt;/code&gt;’s &lt;a href=&quot;https://python-markdown.github.io/extensions/md_in_html/&quot;&gt;md_in_html
extension&lt;/a&gt; (which we
explicitly enabled in the MkDocs settings above).
    &lt;ul&gt;
      &lt;li&gt;Note that using this extension will change the &lt;a href=&quot;https://python-markdown.github.io/extensions/md_in_html/#tag-n
 ormalization&quot;&gt;HTML tag
 processing&lt;/a&gt; slightly. Since the extension needs to parse the content
 anyway to differentiate between HTML and Markdown content, it treats the
 HTML less literally and will do things like automatically closing dangling
 HTML tags.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So, the spooky paragraphs are appearing for elements that are being
erroneously interpreted as block-level elements because of the intricacies of
the Markdown parsing. But, as demonstrated by these examples, you can control
this with careful formatting.&lt;/p&gt;

&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;

&lt;p&gt;When working with HTML blocks in MkDocs Markdown, follow these guidelines to
avoid unexpected issues:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Always include a blank line before and after each HTML block.&lt;/li&gt;
  &lt;li&gt;Avoid blank lines inside the HTML blocks.&lt;/li&gt;
  &lt;li&gt;Start each block-level HTML element (start and end tag) on a new line.&lt;/li&gt;
  &lt;li&gt;Avoid indenting any of the lines of the HTML block at all.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Additionally, when you want to render content as markdown inside those HTML blocks:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Mark the HTML element that wraps the markdown content with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;markdown=&quot;1&quot;&lt;/code&gt;.
Work outwards from there to each parent HTML element, adding the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;markdown&lt;/code&gt;
attribute there each time until you reach the outermost HTML element.&lt;/li&gt;
  &lt;li&gt;If you get unexpected block-level elements being created, this is probably
because something (probably the HTML element immediately wrapping the markdown
content) is being interpreted as a block-level element. To fix this, change
its &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;markdown=&quot;1&quot;&lt;/code&gt; attribute to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;markdown=&quot;span&quot;&lt;/code&gt; to ensure it’s treated as
inline content.&lt;/li&gt;
&lt;/ul&gt;

</description>
        <pubDate>Sat, 12 Oct 2024 13:00:00 +0100</pubDate>
        <link>http://matthewstrawbridge.com/content/2024/ghostly-paragraphs-in-mkdocs/</link>
        <guid isPermaLink="true">http://matthewstrawbridge.com/content/2024/ghostly-paragraphs-in-mkdocs/</guid>
        
        <category>software</category>
        
        <category>programming</category>
        
        <category>python</category>
        
        
        <category>software</category>
        
        <category>programming</category>
        
        <category>python</category>
        
      </item>
    
      <item>
        <title>Python logging: basic, better and best</title>
        <description>&lt;p&gt;In this post I’ll show several approaches to logging in Python.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;We’ll start off with the simplest, most basic approach: using &lt;strong&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;print&lt;/code&gt;
statements&lt;/strong&gt;. This is often looked down on, but it does have some real utility
and there are a few useful hints you can use to get the most of this approach.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Next, we will move onto a more formal approach of using Python’s built-in
&lt;strong&gt;logging&lt;/strong&gt; capabilities.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Finally, we’ll look at the &lt;strong&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;structlog&lt;/code&gt;&lt;/strong&gt; library, and talk about why I
consider it to be the best approach. We’ll also look at the pros and cons of
some other third-party logging libraries, and why I would choose structlog
over them.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;basic-print-statements&quot;&gt;Basic: print statements&lt;/h1&gt;

&lt;p&gt;Sometimes, a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;print&lt;/code&gt; statement is all you need to quickly verify your code’s
behaviour.&lt;/p&gt;

&lt;p&gt;In a pinch, you might only want confirmation that the code you are running
is entering a particular code block, such as the expected branch of an
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;if&lt;/code&gt; statement. For instance, I often use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;print(&quot;sanity&quot;)&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;print(&quot;got
here&quot;)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;To trace the path through some code, scattering print(“A”), print(“B”), etc.,
can help. This is especially effective if an exception is raised and you’re
having difficulty tracking down the cause.&lt;/p&gt;

&lt;p&gt;This method is quick and dirty, with quickness being the key. It’s not elegant
but can be effective and typically won’t hit source control.&lt;/p&gt;

&lt;p&gt;As an aside, using the f-string syntax with a variable name followed by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;=&lt;/code&gt;
is a useful shortcut. Instead of writing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;print(&quot;answer=&quot;, answer)&lt;/code&gt;, you can
simply do this:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;answer&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;42&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sa&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;answer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;answer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;42&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;I use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;print&lt;/code&gt;s for transient testing and debugging. For more permanent
solutions, logging is a better choice.&lt;/p&gt;

&lt;h1 id=&quot;better-logging&quot;&gt;Better: logging&lt;/h1&gt;

&lt;h2 id=&quot;base-logger&quot;&gt;Base logger&lt;/h2&gt;

&lt;p&gt;Python has a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;logging&lt;/code&gt; component in the standard library. This is
straightforward to use and configure, and well documented.&lt;/p&gt;

&lt;p&gt;It is the benchmark against which all third-party logging libraries must be
judged; each of them addresses aspects of the standard logging library that
their creators think can be improved.&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;logging&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;logging&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;basicConfig&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;level&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;logging&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;INFO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;%(asctime)s - %(levelname)s - %(message)s&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;logging&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;info&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Hello, world.&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;language-console highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;go&quot;&gt;2024-05-18 22:13:06,880 - INFO - Hello, world.
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;There are various logging levels you can use for messages, in increasing
order of severity:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;logger.debug(&apos;This is a debug message&apos;)&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;logger.info(&apos;This is an info message&apos;)&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;logger.warning(&apos;This is a warning message&apos;)&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;logger.error(&apos;This is an error message&apos;)&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;logger.critical(&apos;This is a critical message&apos;)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can adjust the logging level to control which messages are logged. For
instance, you might set a lower level when debugging to see more detailed
messages, and a higher logging level in production to log only warnings and
above.&lt;/p&gt;

&lt;h2 id=&quot;a-note-about-message-construction&quot;&gt;A note about message construction&lt;/h2&gt;

&lt;p&gt;When constructing log messages, there are different ways to include variable
data. Consider the following examples:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;widget&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;logger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;info&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sa&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Widget &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;widget&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; is out of space&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;logger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;info&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Widget %s is out of space&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;widget&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Both these &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;logger&lt;/code&gt; calls output &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Widget 3 is out of space&lt;/code&gt;. However, the
second format is preferable for a couple of reasons:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;The conversion of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;widget&lt;/code&gt; to a string and embedding it into the larger
message string will only happen if the log level is low enough for the
message to be output. This can save processing time and resources when logging
is disabled for that level.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Some formatters will be able to group together all the messages using the
same template. This is important if you use a log aggregation
service that you will later want to search through and generate statistics
from.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;custom-logger&quot;&gt;Custom logger&lt;/h2&gt;

&lt;p&gt;In full-fledged applications, it’s beneficial to have each Python module maintain
its own independent logger. This approach offers several benefits:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;You can set log levels and handler types independently in different parts of
your code.&lt;/li&gt;
  &lt;li&gt;Log messages can include the logger name to highlight which part of your
codebase they came from.&lt;/li&gt;
  &lt;li&gt;Your logging won’t interfere with the other code that calls into yours (for
example, if you are creating a library).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here is a typical example. It creates a logger with two handlers: one to log
to the console all messages at level &lt;strong&gt;INFO&lt;/strong&gt; and above, and one to log to a
file all messages at level &lt;strong&gt;DEBUG&lt;/strong&gt; and above.&lt;/p&gt;

&lt;p&gt;Note that the base logger itself is set to &lt;strong&gt;DEBUG&lt;/strong&gt;; if it were set to a
higher level then debug messages would be filtered out before getting to the
handlers, whatever their own logging levels were set to.&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;logging&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;logger&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;logging&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;getLogger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;__file__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;logger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;setLevel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;logging&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;DEBUG&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;console_handler&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;logging&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;StreamHandler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;file_handler&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;logging&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;FileHandler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;app.log&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;console_handler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;setLevel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;logging&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;INFO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;file_handler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;setLevel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;logging&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;DEBUG&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;formatter&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;logging&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Formatter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;%(asctime)s - %(name)s - %(levelname)s - %(message)s&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;console_handler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;setFormatter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;formatter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;file_handler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;setFormatter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;formatter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;logger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;addHandler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;console_handler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;logger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;addHandler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;file_handler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;logger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;debug&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Debug message to file&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;logger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;info&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Info message to console and file&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;language-console highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;gp&quot;&gt;matthew@grug ~/p/2024-05-15&amp;gt;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;python3 main_logging.py
&lt;span class=&quot;go&quot;&gt;2024-05-18 22:56:16,249 - /home/matthew/permatmp/2024-05-15/main_logging.py - INFO - Info message to console and file
&lt;/span&gt;&lt;span class=&quot;gp&quot;&gt;matthew@grug ~/p/2024-05-15&amp;gt;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;cat &lt;/span&gt;app.log
&lt;span class=&quot;go&quot;&gt;2024-05-18 22:56:16,249 - /home/matthew/permatmp/2024-05-15/main_logging.py - DEBUG - Debug message to file
2024-05-18 22:56:16,249 - /home/matthew/permatmp/2024-05-15/main_logging.py - INFO - Info message to console and file
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Typically, you’d write a utility function to apply your preferred setup to
each logger.&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;setupLogging&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;log_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;logger&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;logging&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;getLogger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;log_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;logger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;setLevel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;logging&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;DEBUG&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;console_handler&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;logging&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;StreamHandler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;file_handler&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;logging&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;FileHandler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;app.log&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;console_handler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;setLevel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;logging&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;INFO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;file_handler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;setLevel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;logging&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;DEBUG&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;formatter&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;logging&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Formatter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;%(asctime)s - %(name)s - %(levelname)s - %(message)s&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;console_handler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;setFormatter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;formatter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;file_handler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;setFormatter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;formatter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;logger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;addHandler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;console_handler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;logger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;addHandler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;file_handler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;logger&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Then you would set up the logs in your modules by calling it:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;utils&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;setupLogging&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;logger&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;setupLogging&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;__file__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;best-structlog&quot;&gt;Best: structlog&lt;/h1&gt;

&lt;p&gt;&lt;a href=&quot;https://www.structlog.org/&quot;&gt;structlog&lt;/a&gt; is a powerful and flexible logging
library that goes above and beyond the functionality of Python’s built-in
logging. It focuses on structured logging, where
simple log messages (events) accompanied by value-key metadata (context),
making them easy for other tools to parse later.&lt;/p&gt;

&lt;p&gt;Structured logging separates the context from the message, making the logs
easier to write and parse. While you can still use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;%s&lt;/code&gt; placeholders (as
described above), structlog’s approach simplifies adding context to your
logs.&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;structlog&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;get_logger&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;logger&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;get_logger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;widget&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;logger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;info&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Widget is out of space&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;widget&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;widget&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;
&lt;div class=&quot;highlight&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;
&lt;code style=&quot;color:white&quot;&gt;
&lt;span style=&quot;color:grey&quot;&gt;2024-06-02 21:46:31 &lt;/span&gt;[&lt;span style=&quot;color:lightgreen&quot;&gt;info&lt;/span&gt;     ] &lt;span style=&quot;font-weight:bold !important;&quot;&gt;Widget is out of space&lt;/span&gt;         &lt;span style=&quot;color:cyan&quot;&gt;widget&lt;/span&gt;=&lt;span style=&quot;color:#AF66AF&quot;&gt;3&lt;/span&gt;
&lt;/code&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;

&lt;h2 id=&quot;binding-values&quot;&gt;Binding values&lt;/h2&gt;

&lt;p&gt;One of the powerful features of structlog is the ability to &lt;a href=&quot;https://www.structlog.org/en/stable/bound-loggers.html&quot;&gt;bind
values&lt;/a&gt; to loggers.
This means you can attach extra context, such as a session ID, to a logger,
and these values will automatically be included in all subsequent log
messages.&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;structlog&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;get_logger&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;logger&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;get_logger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bound_logger&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;logger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;bind&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;session&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;123&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bound_logger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;info&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;hello&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;
&lt;div class=&quot;highlight&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;
&lt;code style=&quot;color:white&quot;&gt;
&lt;span style=&quot;color:grey&quot;&gt;2024-06-04 11:37:49 &lt;/span&gt;[&lt;span style=&quot;color:lightgreen&quot;&gt;info&lt;/span&gt;     ] &lt;span style=&quot;font-weight:bold !important;&quot;&gt;Hello&lt;/span&gt;                          &lt;span style=&quot;color:cyan&quot;&gt;session&lt;/span&gt;=&lt;span style=&quot;color:#AF66AF&quot;&gt;123&lt;/span&gt;
&lt;/code&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;

&lt;h2 id=&quot;exceptions&quot;&gt;Exceptions&lt;/h2&gt;

&lt;p&gt;When debugging locally, the more quickly you can get to the source of the
problem the better. Structlog’s display of stack traces is clear and helpful,
making it easier to diagnose issues.&lt;/p&gt;

&lt;p&gt;Here’s an example of a stack trace generated by structlog:&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;
&lt;div class=&quot;highlight&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;
&lt;code style=&quot;color:white&quot;&gt;
&lt;span style=&quot;color: #aa0000&quot;&gt;╭─&lt;/span&gt;&lt;span style=&quot;color: #aa0000&quot;&gt;──────────────────────────────&lt;/span&gt;&lt;span style=&quot;color: #aa0000&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-weight: bold; color: #aa0000&quot;&gt;Traceback &lt;/span&gt;&lt;span style=&quot;font-weight: lighter; color: #aa0000&quot;&gt;(most recent call last)&lt;/span&gt;&lt;span style=&quot;color: #aa0000&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #aa0000&quot;&gt;───────────────────────────────&lt;/span&gt;&lt;span style=&quot;color: #aa0000&quot;&gt;─╮&lt;/span&gt;
&lt;span style=&quot;color: #aa0000&quot;&gt;│&lt;/span&gt; &lt;span style=&quot;font-weight: lighter; color: #aa5500&quot;&gt;/home/matthew/permatmp/2024-05-15/&lt;/span&gt;&lt;span style=&quot;font-weight: bold; color: #aa5500&quot;&gt;main_exception.py&lt;/span&gt;:&lt;span style=&quot;color: #5c5cff&quot;&gt;4&lt;/span&gt; in &lt;span style=&quot;color: #00ff00&quot;&gt;&amp;lt;module&amp;gt;&lt;/span&gt;                                &lt;span style=&quot;color: #aa0000&quot;&gt;│&lt;/span&gt;
&lt;span style=&quot;color: #aa0000&quot;&gt;│&lt;/span&gt;                                                                                                  &lt;span style=&quot;color: #aa0000&quot;&gt;│&lt;/span&gt;
&lt;span style=&quot;color: #aa0000&quot;&gt;│&lt;/span&gt;   &lt;span style=&quot;font-weight: lighter&quot;&gt;1 &lt;/span&gt;&lt;span style=&quot;color: #5c5cff&quot;&gt;from&lt;/span&gt; &lt;span style=&quot;text-decoration: underline; color: #00ffff&quot;&gt;structlog&lt;/span&gt; &lt;span style=&quot;color: #5c5cff&quot;&gt;import&lt;/span&gt; get_logger                                                             &lt;span style=&quot;color: #aa0000&quot;&gt;│&lt;/span&gt;
&lt;span style=&quot;color: #aa0000&quot;&gt;│&lt;/span&gt;   &lt;span style=&quot;font-weight: lighter&quot;&gt;2 &lt;/span&gt;logger = get_logger()                                                                        &lt;span style=&quot;color: #aa0000&quot;&gt;│&lt;/span&gt;
&lt;span style=&quot;color: #aa0000&quot;&gt;│&lt;/span&gt;   &lt;span style=&quot;font-weight: lighter&quot;&gt;3 &lt;/span&gt;&lt;span style=&quot;color: #5c5cff&quot;&gt;try&lt;/span&gt;:                                                                                         &lt;span style=&quot;color: #aa0000&quot;&gt;│&lt;/span&gt;
&lt;span style=&quot;color: #aa0000&quot;&gt;│&lt;/span&gt; &lt;span style=&quot;color: #aa0000&quot;&gt;❱ &lt;/span&gt;4 &lt;span style=&quot;font-weight: lighter&quot;&gt;│   &lt;/span&gt;div_by_zero = &lt;span style=&quot;color: #5c5cff&quot;&gt;1&lt;/span&gt; / &lt;span style=&quot;color: #5c5cff&quot;&gt;0&lt;/span&gt;                                                                      &lt;span style=&quot;color: #aa0000&quot;&gt;│&lt;/span&gt;
&lt;span style=&quot;color: #aa0000&quot;&gt;│&lt;/span&gt;   &lt;span style=&quot;font-weight: lighter&quot;&gt;5 &lt;/span&gt;&lt;span style=&quot;color: #5c5cff&quot;&gt;except&lt;/span&gt; &lt;span style=&quot;color: #00ffff&quot;&gt;BaseException&lt;/span&gt; &lt;span style=&quot;color: #5c5cff&quot;&gt;as&lt;/span&gt; e:                                                                   &lt;span style=&quot;color: #aa0000&quot;&gt;│&lt;/span&gt;
&lt;span style=&quot;color: #aa0000&quot;&gt;│&lt;/span&gt;   &lt;span style=&quot;font-weight: lighter&quot;&gt;6 &lt;/span&gt;&lt;span style=&quot;font-weight: lighter&quot;&gt;│   &lt;/span&gt;logger.exception(e)                                                                      &lt;span style=&quot;color: #aa0000&quot;&gt;│&lt;/span&gt;
&lt;span style=&quot;color: #aa0000&quot;&gt;│&lt;/span&gt;   &lt;span style=&quot;font-weight: lighter&quot;&gt;7 &lt;/span&gt;                                                                                             &lt;span style=&quot;color: #aa0000&quot;&gt;│&lt;/span&gt;
&lt;span style=&quot;color: #aa0000&quot;&gt;│&lt;/span&gt;                                                                                                  &lt;span style=&quot;color: #aa0000&quot;&gt;│&lt;/span&gt;
&lt;span style=&quot;color: #aa0000&quot;&gt;│&lt;/span&gt; &lt;span style=&quot;color: #aa5500&quot;&gt;╭─&lt;/span&gt;&lt;span style=&quot;color: #aa5500&quot;&gt;──────────────────────────────────────────&lt;/span&gt;&lt;span style=&quot;color: #aa5500&quot;&gt; locals &lt;/span&gt;&lt;span style=&quot;color: #aa5500&quot;&gt;──────────────────────────────────────────&lt;/span&gt;&lt;span style=&quot;color: #aa5500&quot;&gt;─╮&lt;/span&gt; &lt;span style=&quot;color: #aa0000&quot;&gt;│&lt;/span&gt;
&lt;span style=&quot;color: #aa0000&quot;&gt;│&lt;/span&gt; &lt;span style=&quot;color: #aa5500&quot;&gt;│&lt;/span&gt;          e = &lt;span style=&quot;font-weight: bold; color: #E850A8&quot;&gt;ZeroDivisionError&lt;/span&gt;&lt;span style=&quot;font-weight: bold&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #aa5500&quot;&gt;&apos;division by zero&apos;&lt;/span&gt;&lt;span style=&quot;font-weight: bold&quot;&gt;)&lt;/span&gt;                                           &lt;span style=&quot;color: #aa5500&quot;&gt;│&lt;/span&gt; &lt;span style=&quot;color: #aa0000&quot;&gt;│&lt;/span&gt;
&lt;span style=&quot;color: #aa0000&quot;&gt;│&lt;/span&gt; &lt;span style=&quot;color: #aa5500&quot;&gt;│&lt;/span&gt; get_logger = &lt;span style=&quot;font-weight: bold&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;font-weight: bold; color: #ff00ff&quot;&gt;function&lt;/span&gt; get_logger at &lt;span style=&quot;color: #5c5cff&quot;&gt;0x75c44db8dd80&lt;/span&gt;&lt;span style=&quot;font-weight: bold&quot;&gt;&amp;gt;&lt;/span&gt;                                         &lt;span style=&quot;color: #aa5500&quot;&gt;│&lt;/span&gt; &lt;span style=&quot;color: #aa0000&quot;&gt;│&lt;/span&gt;
&lt;span style=&quot;color: #aa0000&quot;&gt;│&lt;/span&gt; &lt;span style=&quot;color: #aa5500&quot;&gt;│&lt;/span&gt;     logger = &lt;span style=&quot;font-weight: bold&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;font-weight: bold; color: #E850A8&quot;&gt;BoundLoggerLazyProxy&lt;/span&gt;&lt;span style=&quot;font-weight: bold&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #aa5500&quot;&gt;logger&lt;/span&gt;=&lt;span style=&quot;color: #5c5cff&quot;&gt;None&lt;/span&gt;, &lt;span style=&quot;color: #aa5500&quot;&gt;wrapper_class&lt;/span&gt;=&lt;span style=&quot;color: #5c5cff&quot;&gt;None&lt;/span&gt;, &lt;span style=&quot;color: #aa5500&quot;&gt;processors&lt;/span&gt;=&lt;span style=&quot;color: #5c5cff&quot;&gt;None&lt;/span&gt;,         &lt;span style=&quot;color: #aa5500&quot;&gt;│&lt;/span&gt; &lt;span style=&quot;color: #aa0000&quot;&gt;│&lt;/span&gt;
&lt;span style=&quot;color: #aa0000&quot;&gt;│&lt;/span&gt; &lt;span style=&quot;color: #aa5500&quot;&gt;│&lt;/span&gt;              &lt;span style=&quot;color: #aa5500&quot;&gt;context_class&lt;/span&gt;=&lt;span style=&quot;color: #5c5cff&quot;&gt;None&lt;/span&gt;, &lt;span style=&quot;color: #aa5500&quot;&gt;initial_values&lt;/span&gt;=&lt;span style=&quot;font-weight: bold&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;font-weight: bold&quot;&gt;}&lt;/span&gt;, &lt;span style=&quot;color: #aa5500&quot;&gt;logger_factory_args&lt;/span&gt;=&lt;span style=&quot;font-weight: bold&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;font-weight: bold&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;font-weight: bold&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;font-weight: bold&quot;&gt;&amp;gt;&lt;/span&gt;                 &lt;span style=&quot;color: #aa5500&quot;&gt;│&lt;/span&gt; &lt;span style=&quot;color: #aa0000&quot;&gt;│&lt;/span&gt;
&lt;span style=&quot;color: #aa0000&quot;&gt;│&lt;/span&gt; &lt;span style=&quot;color: #aa5500&quot;&gt;╰──────────────────────────────────────────────────────────────────────────────────────────────╯&lt;/span&gt; &lt;span style=&quot;color: #aa0000&quot;&gt;│&lt;/span&gt;
&lt;span style=&quot;color: #aa0000&quot;&gt;╰──────────────────────────────────────────────────────────────────────────────────────────────────╯&lt;/span&gt;
&lt;span style=&quot;font-weight: bold; color: #ff0000&quot;&gt;ZeroDivisionError: &lt;/span&gt;division by zero
&lt;/code&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;As you can see, structlog highlights the problematic line with a red arrow,
along with some context around it. It also lists all the local variables
(&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;locals&lt;/code&gt;), which can be useful for diagnosing the issue.&lt;/p&gt;

&lt;p&gt;Aside: this colourful HTML was generated by piping into
&lt;a href=&quot;https://github.com/pycontribs/ansi2html/tree/main&quot;&gt;ansi2html&lt;/a&gt; with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--partial
--inline&lt;/code&gt; parameters set.&lt;/p&gt;

&lt;h2 id=&quot;typical-structlog-config&quot;&gt;Typical structlog config&lt;/h2&gt;

&lt;p&gt;Structlog’s configuration is incredibly flexible. The documentation is
thorough but fragmented, so here I’ve consolidated guidance into a realistic
configuration that includes the following features:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Optimised for human readability when running locally; optimised for
aggregation and machine readability when running in production.&lt;/li&gt;
  &lt;li&gt;Adds the file name and line number to log messages, which isn’t included by
default but is generally useful information.&lt;/li&gt;
  &lt;li&gt;Uses a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LOG_LEVEL&lt;/code&gt; environment variable to set the logging level; log
entries at lower levels will be treated as no-ops.&lt;/li&gt;
  &lt;li&gt;Uses the &lt;a href=&quot;https://github.com/ijl/orjson&quot;&gt;orjson&lt;/a&gt; library for JSON
generation in production, for ultra-fast JSON creation (straight to bytes).&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;logging&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;os&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sys&lt;/span&gt;

&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;orjson&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;structlog&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;structlog.contextvars&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;merge_contextvars&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;structlog.dev&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ConsoleRenderer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;set_exc_info&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;structlog.processors&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;CallsiteParameter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;CallsiteParameterAdder&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;EventRenamer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;JSONRenderer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;StackInfoRenderer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;TimeStamper&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;add_log_level&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;dict_tracebacks&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;shared_processors&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# This list is based on _BUILTIN_DEFAULT_PROCESSORS.
&lt;/span&gt;    &lt;span class=&quot;c1&quot;&gt;# See https://github.com/hynek/structlog/blob/main/src/structlog/_config.py
&lt;/span&gt;    &lt;span class=&quot;n&quot;&gt;merge_contextvars&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;add_log_level&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;StackInfoRenderer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;set_exc_info&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# Extras not in the defaults
&lt;/span&gt;    &lt;span class=&quot;nc&quot;&gt;CallsiteParameterAdder&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;CallsiteParameter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;MODULE&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;CallsiteParameter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;LINENO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]),&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Approach based on https://www.structlog.org/en/stable/logging-best-practices.html
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;logger_factory&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# use the default
&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sys&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;stderr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;isatty&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# Pretty printing when we run in a terminal session.
&lt;/span&gt;    &lt;span class=&quot;c1&quot;&gt;# Automatically prints pretty tracebacks when &quot;rich&quot; is installed
&lt;/span&gt;    &lt;span class=&quot;n&quot;&gt;processors&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;shared_processors&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;TimeStamper&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fmt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;%Y-%m-%d %H:%M:%S&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;utc&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;False&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;ConsoleRenderer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# Print JSON when we run, e.g., in a Docker container.
&lt;/span&gt;    &lt;span class=&quot;c1&quot;&gt;# Also print structured tracebacks.
&lt;/span&gt;    &lt;span class=&quot;n&quot;&gt;processors&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;shared_processors&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;# slightly less readable and more easily processed, e.g. `2024-06-02T23:04:00.000000Z`.
&lt;/span&gt;        &lt;span class=&quot;nc&quot;&gt;TimeStamper&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fmt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;iso&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;utc&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;EventRenamer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;msg&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# rename &quot;event&quot; key to &quot;msg&quot;
&lt;/span&gt;        &lt;span class=&quot;n&quot;&gt;dict_tracebacks&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;JSONRenderer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;serializer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;orjson&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dumps&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# Note that orjson generates bytes not strings
&lt;/span&gt;    &lt;span class=&quot;c1&quot;&gt;# logger_factory=structlog.BytesLoggerFactory()  # for outputting JSON to console
&lt;/span&gt;    &lt;span class=&quot;n&quot;&gt;logger_factory&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;structlog&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;BytesLoggerFactory&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;nb&quot;&gt;file&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;output.log&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;ab&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# for outputting JSON to file
&lt;/span&gt;    &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;log_level_raw&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;os&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;environ&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;LOG_LEVEL&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;INFO&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;upper&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;log_level&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;logging&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;getLevelName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;log_level_raw&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;isinstance&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;log_level&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;raise&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ValueError&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sa&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Invalid log level: &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;log_level_raw&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;


&lt;span class=&quot;n&quot;&gt;structlog&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;configure&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;cache_logger_on_first_use&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;wrapper_class&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;structlog&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;make_filtering_bound_logger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;log_level&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;processors&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;processors&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;logger_factory&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;logger_factory&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The configuration above should be run once in your application. Then, in the
modules where you use logging, you can set up and use the logger as follows:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;structlog&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;logger&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;structlog&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;get_logger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;widgets&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;logger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;debug&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Too low a level -- will be skipped&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;logger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;info&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Test info&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;widgets&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;widgets&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;logger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;warning&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Test warning&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;widgets&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;widgets&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;logger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Test error&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;widgets&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;widgets&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;logger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;critical&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Test critical&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;widgets&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;widgets&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;
&lt;div class=&quot;highlight&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;
&lt;code style=&quot;color:white&quot;&gt;
&lt;span style=&quot;color:grey&quot;&gt;2024-06-02 23:04:00 &lt;/span&gt;[&lt;span style=&quot;color:lightgreen&quot;&gt;info&lt;/span&gt;     ] &lt;span style=&quot;font-weight:bold !important;&quot;&gt;Test info&lt;/span&gt;                      &lt;span style=&quot;color:cyan&quot;&gt;lineno&lt;/span&gt;=&lt;span style=&quot;color:#AF66AF&quot;&gt;5&lt;/span&gt; &lt;span style=&quot;color:cyan&quot;&gt;module&lt;/span&gt;=&lt;span style=&quot;color:#AF66AF&quot;&gt;my_module&lt;/span&gt; &lt;span style=&quot;color:cyan&quot;&gt;widgets&lt;/span&gt;=&lt;span style=&quot;color:#AF66AF&quot;&gt;3&lt;/span&gt;
&lt;span style=&quot;color:grey&quot;&gt;2024-06-02 23:04:00 &lt;/span&gt;[&lt;span style=&quot;color:yellow&quot;&gt;warning&lt;/span&gt;  ] &lt;span style=&quot;font-weight:bold !important;&quot;&gt;Test info&lt;/span&gt;                      &lt;span style=&quot;color:cyan&quot;&gt;lineno&lt;/span&gt;=&lt;span style=&quot;color:#AF66AF&quot;&gt;5&lt;/span&gt; &lt;span style=&quot;color:cyan&quot;&gt;module&lt;/span&gt;=&lt;span style=&quot;color:#AF66AF&quot;&gt;my_module&lt;/span&gt; &lt;span style=&quot;color:cyan&quot;&gt;widgets&lt;/span&gt;=&lt;span style=&quot;color:#AF66AF&quot;&gt;3&lt;/span&gt;
&lt;span style=&quot;color:grey&quot;&gt;2024-06-02 23:04:00 &lt;/span&gt;[&lt;span style=&quot;color:red&quot;&gt;error&lt;/span&gt;    ] &lt;span style=&quot;font-weight:bold !important;&quot;&gt;Test info&lt;/span&gt;                      &lt;span style=&quot;color:cyan&quot;&gt;lineno&lt;/span&gt;=&lt;span style=&quot;color:#AF66AF&quot;&gt;5&lt;/span&gt; &lt;span style=&quot;color:cyan&quot;&gt;module&lt;/span&gt;=&lt;span style=&quot;color:#AF66AF&quot;&gt;my_module&lt;/span&gt; &lt;span style=&quot;color:cyan&quot;&gt;widgets&lt;/span&gt;=&lt;span style=&quot;color:#AF66AF&quot;&gt;3&lt;/span&gt;
&lt;span style=&quot;color:grey&quot;&gt;2024-06-02 23:04:00 &lt;/span&gt;[&lt;span style=&quot;color:red&quot;&gt;critical&lt;/span&gt; ] &lt;span style=&quot;font-weight:bold !important;&quot;&gt;Test info&lt;/span&gt;                      &lt;span style=&quot;color:cyan&quot;&gt;lineno&lt;/span&gt;=&lt;span style=&quot;color:#AF66AF&quot;&gt;5&lt;/span&gt; &lt;span style=&quot;color:cyan&quot;&gt;module&lt;/span&gt;=&lt;span style=&quot;color:#AF66AF&quot;&gt;my_module&lt;/span&gt; &lt;span style=&quot;color:cyan&quot;&gt;widgets&lt;/span&gt;=&lt;span style=&quot;color:#AF66AF&quot;&gt;3&lt;/span&gt;
&lt;/code&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;Or, if outputting to a file:&lt;/p&gt;

&lt;div class=&quot;language-console highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;gp&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;cat &lt;/span&gt;output.log
&lt;span class=&quot;go&quot;&gt;{&quot;widgets&quot;:3,&quot;level&quot;:&quot;info&quot;,&quot;module&quot;:&quot;main_structlog2&quot;,&quot;lineno&quot;:75,&quot;timestamp&quot;:&quot;2024-06-04T09:20:57.636807Z&quot;,&quot;msg&quot;:&quot;Test info&quot;}
{&quot;widgets&quot;:3,&quot;level&quot;:&quot;warning&quot;,&quot;module&quot;:&quot;main_structlog2&quot;,&quot;lineno&quot;:76,&quot;timestamp&quot;:&quot;2024-06-04T09:20:57.637064Z&quot;,&quot;msg&quot;:&quot;Test warning&quot;}
{&quot;widgets&quot;:3,&quot;level&quot;:&quot;error&quot;,&quot;module&quot;:&quot;main_structlog2&quot;,&quot;lineno&quot;:77,&quot;timestamp&quot;:&quot;2024-06-04T09:20:57.637170Z&quot;,&quot;msg&quot;:&quot;Test error&quot;}
{&quot;widgets&quot;:3,&quot;level&quot;:&quot;critical&quot;,&quot;module&quot;:&quot;main_structlog2&quot;,&quot;lineno&quot;:78,&quot;timestamp&quot;:&quot;2024-06-04T09:20:57.637254Z&quot;,&quot;msg&quot;:&quot;Test critical&quot;}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;a-note-about-async-logging&quot;&gt;A note about async logging&lt;/h2&gt;

&lt;p&gt;Structlog also supports async logging, which can be useful in scenarios where
logging is particularly slow and frequent, such as logging to a database. The
async function calls use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;a&lt;/code&gt; prefixes, like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;await logger.ainfo(&quot;...&quot;)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Async logging can be beneficial when logging involves complex pipelines or
expensive-to-write syncs. However, it’s important to combine this with
batching, saving up log entries and writing them in bulk periodically.
Otherwise, the overhead of async call machinery might outweigh the benefits of
asynchronous writing, potentially resulting in worse performance.&lt;/p&gt;

&lt;p&gt;As with most performance tweaks, you should profile the code to check that it
works in practice before adopting this wholesale.&lt;/p&gt;

&lt;h1 id=&quot;other-options&quot;&gt;Other options&lt;/h1&gt;

&lt;h2 id=&quot;loguru&quot;&gt;Loguru&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/Delgan/loguru/&quot;&gt;Loguru&lt;/a&gt; is a good alternative logging
framework that simplifies the logging setup in Python.&lt;/p&gt;

&lt;p&gt;Pros:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Much easier configuration that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;structlog&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;Faster than the standard Python logging module.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Cons:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Uses string-based logging with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;{}&lt;/code&gt; placeholders; I would prefer full
structured logging, and this form is not drop-in compatible with existing
standard logging implementations using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;%s&lt;/code&gt; style placeholders.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;logbook&quot;&gt;Logbook&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/getlogbook/logbook&quot;&gt;Logbook&lt;/a&gt; is another interesting Python
logging library that offers unique features and improvements over the standard
logging module.&lt;/p&gt;

&lt;p&gt;Pros:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Faster than the standard Python logging module.&lt;/li&gt;
  &lt;li&gt;Includes esoteric handlers for logging directly to some unusual syncs. For
example, you can log to Twitter or ZeroMQ, or send critical errors to an mail
eaddress.&lt;/li&gt;
  &lt;li&gt;Trivially easy to redirect standard &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;logging&lt;/code&gt; calls to Logbook with a
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;redirect_logging()&lt;/code&gt; call.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Cons:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Does not support full structured logging.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;sentry&quot;&gt;Sentry&lt;/h2&gt;

&lt;p&gt;There is a Python API for the
&lt;a href=&quot;https://docs.sentry.io/platforms/python/integrations/logging/&quot;&gt;Sentry&lt;/a&gt;
platform, which might be of interest if you want to aggregate all your logs
into Sentry.&lt;/p&gt;

&lt;h2 id=&quot;python-json-logger&quot;&gt;python-json-logger&lt;/h2&gt;

&lt;p&gt;The &lt;a href=&quot;https://github.com/madzak/python-json-logger&quot;&gt;python-json-logger&lt;/a&gt;
library adds JSON-formatted output to the standard logging module. However,
it doesn’t appear to be under active maintenance.&lt;/p&gt;

&lt;h1 id=&quot;wrap-up&quot;&gt;Wrap-up&lt;/h1&gt;

&lt;p&gt;Structlog offers some welcome extra functionality over that provided by the
standard library’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;logging&lt;/code&gt; component. The coloured output makes it easier
to parse console output; in particular, the formatting of logged exceptions
makes it simpler to track down problems.&lt;/p&gt;

&lt;p&gt;Logging is one of those things that you set up once in a project and then
rarely have to tweak the configuration again. While structlog’s flexibility
can make its documentation challenging to follow, this initial setup effort
pays off with simplified maintenance in the long run.&lt;/p&gt;

&lt;p&gt;Although many other logging libraries are easier to configure, they primarily
focus on filling in templated message strings rather than providing
JSON-formatted output. As demonstrated in this post, structlog makes it
possible to achieve both easy-to-read logging locally and machine-friendly
output in production.&lt;/p&gt;

</description>
        <pubDate>Tue, 04 Jun 2024 13:00:00 +0100</pubDate>
        <link>http://matthewstrawbridge.com/content/2024/python-logging-basic-better-best/</link>
        <guid isPermaLink="true">http://matthewstrawbridge.com/content/2024/python-logging-basic-better-best/</guid>
        
        <category>software</category>
        
        <category>programming</category>
        
        <category>python</category>
        
        <category>logging</category>
        
        
        <category>software</category>
        
        <category>programming</category>
        
        <category>python</category>
        
        <category>logging</category>
        
      </item>
    
      <item>
        <title>Cloudflare DNS and Site44</title>
        <description>&lt;h1 id=&quot;the-problem-could-not-load-this-blog-without-the-www-prefix&quot;&gt;The problem: could not load this blog without the www. prefix&lt;/h1&gt;

&lt;p&gt;This blog is hosted on &lt;a href=&quot;https://www.site44.com&quot;&gt;site44&lt;/a&gt;. This is a very
convenient way of running a static website – simply update your static
content in Dropbox and Site44 will serve it up as a site for you. It’s
reasonably priced and I’ve been using it for various things for years.&lt;/p&gt;

&lt;p&gt;One long-standing quirk has been that you have to specify the URL when
setting up the site. I chose &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;www.matthewstrawbridge.com&lt;/code&gt;, which meant
that while you could browse to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http://www.matthewstrawbridge.com&lt;/code&gt; or
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;https://www.matthewstrawbridge.com&lt;/code&gt; and everything would work, &lt;strong&gt;omitting the
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;www.&lt;/code&gt; prefix would just load Site44’s 404 error page:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/site44_404.png&quot; alt=&quot;Screenshot of 404&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Note that part of the complication is that Site44 requires a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CNAME&lt;/code&gt; record
in your DNS to do the redirection, rather than simply having an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;A&lt;/code&gt; record
pointing at your site.&lt;/p&gt;

&lt;p&gt;See &lt;a href=&quot;https://www.site44.com/custom-domains&quot;&gt;this snippet&lt;/a&gt; from their custom domain help page:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;CNAME aliases do not apply to “naked” domains like example.com—they only
apply to subdomains like www.example.com or blog.example.com. Site44 only
supports subdomains; use “forwarding” (see below) to handle naked domains.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is a record of how I solved this problem.&lt;/p&gt;

&lt;h1 id=&quot;the-solution-adding-a-cloudflare-redirect-rule&quot;&gt;The solution: adding a CloudFlare redirect rule&lt;/h1&gt;

&lt;p&gt;&lt;a href=&quot;https://www.cloudflare.com/&quot;&gt;CloudFlare&lt;/a&gt; is renowned for providing the
infrastructure and know-how to keep large sites up and available online,
with a good reputation for preventing denial-of-service (DOS) attacks. But
alongside this commercial clout, they also offer a free plan which includes a
DNS service.&lt;/p&gt;

&lt;p&gt;There are three things necessary to get everything working.&lt;/p&gt;

&lt;h2 id=&quot;1-a-cname-link-from-the-custom-domain-to-site44&quot;&gt;1. A &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CNAME&lt;/code&gt; link from the custom domain to Site44&lt;/h2&gt;

&lt;p&gt;This was already present on my previous DNS host, and CloudFlare imported it
and set it up automatically. This is a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CNAME&lt;/code&gt; entry from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;www&lt;/code&gt; to site44.
Once this is in place (and propagated) you can navigate to the site when using
the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;www.&lt;/code&gt; prefix.&lt;/p&gt;

&lt;p&gt;Note that this has the proxying option OFF, which seems to be necessary for Site44.&lt;/p&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CNAME&lt;/code&gt; entry is shown on the bottom line in this screenshot:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/dns.png&quot; alt=&quot;Screenshot of DNS settings&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;2-an-a-record-entry-for-the-top-level-host&quot;&gt;2. An &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;A&lt;/code&gt; record entry for the top-level host&lt;/h2&gt;

&lt;p&gt;In theory this shouldn’t be necessary, but CloudFlare’s redirection rules for
the top-level site (see next step) don’t seem to work without it.&lt;/p&gt;

&lt;p&gt;This is the other record shown in the screenshot above; an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;A&lt;/code&gt; DNS record
to the top-level site (you can just enter &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@&lt;/code&gt; into the relevant box, and
Cloudflare will fill in the host – in this case &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;matthewstrawbridge.com&lt;/code&gt; –
automatically.&lt;/p&gt;

&lt;p&gt;The content that it’s redirecting to shouldn’t really matter, because the
redirection rule we create will kick in before anything ends up at that IP
address. I chose to use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;192.0.2.1&lt;/code&gt;, which is a dummy address used in lots of
examples.&lt;/p&gt;

&lt;p&gt;Note that this record &lt;em&gt;can&lt;/em&gt; have CloudFlare’s automatic proxying turned on
(shown by the orange icon).&lt;/p&gt;

&lt;h2 id=&quot;3-create-the-redirection-rule&quot;&gt;3. Create the redirection rule&lt;/h2&gt;

&lt;p&gt;Now we want any attempts to access the top-level host to be automatically
redirected to the equivalent URLs with the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;www.&lt;/code&gt; prefix.&lt;/p&gt;

&lt;p&gt;This was a bit more complex.&lt;/p&gt;

&lt;p&gt;From the Cloudflare menu, select &lt;strong&gt;Rules&lt;/strong&gt; &amp;gt; &lt;strong&gt;Redirect Rules&lt;/strong&gt;. This is
preferable to using page rules, which I understand to be deprecated.&lt;/p&gt;

&lt;p&gt;Then you can create a redirection rule that looks like this:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/custom-redirect-rule.png&quot; alt=&quot;Custom redirect rule&quot; /&gt;&lt;/p&gt;

&lt;p&gt;It uses a custom filter expression as follows:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;when the hostname in the incoming request is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;matthewstrawbridge.com&lt;/code&gt;
(rather than, say, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;www.matthewstrawbridge.com&lt;/code&gt;) then&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;respond with a permanent redirect (301 HTTP status code) to the alternative
URL that you get by concatenating &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;www.matthewstrawbridge.com&lt;/code&gt; with the rest
of the path from the original URL (e.g. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/some-article/&lt;/code&gt;) and preserving the
query string (e.g. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;?search=wibble&lt;/code&gt;) if there is one.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h1&gt;

&lt;p&gt;All this is a little bit more long-winded than I’d like, but at least it’s a
one-off cost.&lt;/p&gt;

&lt;p&gt;As always with DNS things, it can take a long time for the settings to
propagate.&lt;/p&gt;

&lt;p&gt;I guess the popular alternative these days for a technical blog such as this
would be to host it on GitHub pages, but I’ve been happy with Site44 and this
fixes the main niggle that I’ve had with it.&lt;/p&gt;

</description>
        <pubDate>Tue, 07 May 2024 13:00:00 +0100</pubDate>
        <link>http://matthewstrawbridge.com/content/2024/cloudflare-dns-and-site44/</link>
        <guid isPermaLink="true">http://matthewstrawbridge.com/content/2024/cloudflare-dns-and-site44/</guid>
        
        <category>software</category>
        
        <category>blog</category>
        
        <category>dns</category>
        
        <category>infrastructure</category>
        
        
        <category>software</category>
        
        <category>blog</category>
        
        <category>dns</category>
        
        <category>infrastructure</category>
        
      </item>
    
      <item>
        <title>Thoughts on writing code well</title>
        <description>&lt;h1 id=&quot;about-this-post&quot;&gt;About this post&lt;/h1&gt;

&lt;p&gt;I had intended to write a post about &lt;a href=&quot;http://manifesto.softwarecraftsmanship.org/&quot;&gt;software
craftsmanship&lt;/a&gt; in general.
Instead, the topic of test-driven development/design (TDD) seems to have taken
over.&lt;/p&gt;

&lt;p&gt;The irony isn’t lost on me. You don’t always know at the outset of creating
something what form the final product will take. TDD is a good fit for this:
you’re not guessing what you &lt;em&gt;might&lt;/em&gt; need, just creating the next small thing
you &lt;em&gt;know&lt;/em&gt; you need in the simplest way possible. The final product can look
after itself.&lt;/p&gt;

&lt;h1 id=&quot;test-driven-design-tdd&quot;&gt;Test-driven design (TDD)&lt;/h1&gt;

&lt;h2 id=&quot;degrees-of-hatred-and-fandom&quot;&gt;Degrees of hatred and fandom&lt;/h2&gt;

&lt;p&gt;How do you feel about unit testing? It seems to be a topic that divides
developers.&lt;/p&gt;

&lt;p&gt;I’ve always been a fan of having at least some unit tests, but over the last
few years I’ve moved from “use common sense and write just enough good tests
afterwards” to “use strict test-driven design”.&lt;/p&gt;

&lt;p&gt;Everyone fits somewhere on the scale between “writing any tests at all is a
complete waste of time” to “all code should be written using TDD”. The purpose
of this article is to encourage you to nudge to the right on this scale; at
least try the next step and see how good a fit it is for the projects you work
on.&lt;/p&gt;

&lt;h2 id=&quot;definitions&quot;&gt;Definitions&lt;/h2&gt;

&lt;p&gt;Uncle Bob Martin defines &lt;a href=&quot;http://www.butunclebob.com/ArticleS.UncleBob.TheThreeRulesOfTdd&quot;&gt;the Three Laws of
TDD&lt;/a&gt; as
follows:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;You are not allowed to write any production code unless it is to make a
failing unit test pass.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;You are not allowed to write any more of a unit test than is sufficient to
fail; and compilation failures are failures.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;You are not allowed to write any more production code than is sufficient to
pass the one failing unit test.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;His &lt;a href=&quot;https://cleancoders.com/landing&quot;&gt;Clean Coders videos&lt;/a&gt; give a good and
entertaining introduction to TDD and other important topics in this area. The
key point is that you are constantly in a tight loop: red (failing test),
green (passing tests), refactor.&lt;/p&gt;

&lt;p&gt;The three rules above are simple but they are not &lt;em&gt;simplistic&lt;/em&gt;. It takes
practice to write code in this way. If you’ve been writing code for years,
following these rules seems unintuitive, even patronising. It’s almost as if
they were &lt;em&gt;designed&lt;/em&gt; to slow you down.&lt;/p&gt;

&lt;p&gt;But you get all sorts of benefits if you apply this strictly. Ten
years ago (so this isn’t exactly a new idea), Tim King wrote &lt;a href=&quot;http://sd.jtimothyking.com/2006/07/11/twelve-benefits-of-writing-unit-tests-first/&quot;&gt;an
article&lt;/a&gt;
listing a dozen of these&lt;sup id=&quot;fnref:1&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:1&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;

&lt;!-- Don&apos;t wrap --&gt;

&lt;p&gt;If you only do manual testing, or no testing at all, then you are doomed to
a lifetime of chasing bugs in production code. You’re climbing a mountain
without a safety rope.&lt;/p&gt;

&lt;p&gt;Seriously, if you think TDD is a waste of time and effort, or something that’s
only useful for newbie programmers, I’d encourage you to try it out on your
next pet project. (If you don’t have a suitable project coming up, try &lt;a href=&quot;http://osherove.com/tdd-kata-1/&quot;&gt;Roy
Osherove’s String Calculator TDD kata&lt;/a&gt;.) I
think you’ll be pleasantly surprised by the positive effect it has on the
code under test and how many times you’ll find you’ve uncovered a bug during
development that would otherwise have made it into the wild. However, it does
require a degree of humility to get started; a willingness to accept that,
just maybe, there’s a better approach than the one you’ve been using for
years.&lt;/p&gt;

&lt;h1 id=&quot;killing-disease-at-the-point-of-contraction&quot;&gt;Killing disease at the point of contraction&lt;/h1&gt;

&lt;h2 id=&quot;how-unit-tests-stop-bugs&quot;&gt;How unit tests stop bugs&lt;/h2&gt;

&lt;p&gt;It’s all very well saying that TDD helps guide the design of the product, but
you probably care more about what it can do to help prevent bugs. After all,
the tests are there to check behaviour and uncover bugs.&lt;/p&gt;

&lt;p&gt;TDD has a dual role when it comes to finding bugs. Firstly, as part of the
process, you’re continually writing new tests for behaviour that doesn’t yet
exist, then modifying the code until the test passes. Sometimes you’ll run the
new test, expecting it to pass, but it fails. This is a genuine bug; because
you’ve caught it at the point of injection, it’s probably easy to fix. Wipe
your brow and move on, but remember to be thankful that the test was there.
Just think how long it might have taken to track the problem down from a
customer’s patchy bug report six months after deploying the code!&lt;/p&gt;

&lt;p&gt;The second role TDD plays is in the construction and frequent running of a
comprehensive suite of regression tests. If you break something, you should
find out straight away. That’s very powerful.&lt;/p&gt;

&lt;h2 id=&quot;avoiding-bugs-altogether-where-you-can&quot;&gt;Avoiding bugs altogether where you can&lt;/h2&gt;

&lt;p&gt;Testing isn’t a silver bullet. All useful code has a non-zero bug density.&lt;/p&gt;

&lt;p&gt;But &lt;em&gt;code that doesn’t exist does have zero bugs&lt;/em&gt;. You just can’t get better
than that! It’s why principles such as DRY (don’t repeat yourself) and YAGNI
(you aren’t gonna need it) are important: having code once is better than
having it twice; having dead code rotting alongside living code is harmful and
confusing.&lt;/p&gt;

&lt;p&gt;Poor developers write buggy code. Better developers write fewer bugs. Great
developers avoid writing the code at all.&lt;/p&gt;

&lt;h2 id=&quot;if-there-are-bugs-finding-them-early&quot;&gt;If there are bugs, finding them early&lt;/h2&gt;

&lt;p&gt;Use automated methods to catch simple problems first. Then use code review to
catch even more. There are some fine static analysis tools that you can run
over your code to find problems early, and to avoid code reviewers from being
bogged down with finding issues that could have been caught by a tool:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;.NET
    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;https://www.jetbrains.com/resharper/&quot;&gt;Resharper&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;https://msdn.microsoft.com/en-us/library/bb429476.aspx&quot;&gt;FxCop&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;https://stylecop.codeplex.com/&quot;&gt;StyleCop&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;http://www.ndepend.com/&quot;&gt;NDepend&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;JavaScript
    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;https://github.com/douglascrockford/JSLint&quot;&gt;JSLint&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Finding bugs early is also why I prefer strong typing over dynamic typing. I’d
rather find bugs at compile-time than at run-time, and I’ve never wanted to
redefine a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;string&lt;/code&gt; as an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;int&lt;/code&gt; in mid flow.&lt;/p&gt;

&lt;h1 id=&quot;simplicity&quot;&gt;Simplicity&lt;/h1&gt;

&lt;p&gt;It all comes back to simplicity.&lt;/p&gt;

&lt;p&gt;Source code has a natural tendency to become more complicated over time. Hacks
and other special cases will be added. Documentation will get out of sync with
the code. A dozen people will edit the same file, applying subtly different
coding styles to it each time. Classes will grow ever bigger and take on more
and more responsibilities. Some code will become uncallable or uncalled and
start to rot.&lt;/p&gt;

&lt;p&gt;It takes energy to fight against this complication.&lt;/p&gt;

&lt;p&gt;TDD can help. Constant, safe refactoring is a force for keeping the code
simple. Making just one focused change at a time is working in a simple way.&lt;/p&gt;

&lt;p&gt;Static code analysis can also help. It helps to enforce good practice across
the code base, which makes its maintenance more straightforward. Some level of
automated analysis eases the burden on code reviewers, so they can concentrate
on the more complex issues that can’t be found automatically.&lt;/p&gt;

&lt;p&gt;I believe that these three quality gates – the use of TDD to guide design
and create a regression framework, static analysis to find some problems
automatically and code review to find still more – all help to minimise the
number of bugs escaping into the wild. Which leaves developers with more time
to do useful work and less time chasing their tails fixing bugs in production
code.&lt;/p&gt;

&lt;div style=&quot;margin-top: 12em&quot;&gt;&lt;/div&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;strong&gt;Footnotes&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;footnotes&quot; role=&quot;doc-endnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:1&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;* Unit tests prove that your code actually works * You get a low-level regression-test suite * You can improve the design without breaking it * It’s more fun to code with them than without * They demonstrate concrete progress * Unit tests are a form of sample code * Test-first forces you to plan before you code * Test-first reduces the cost of bugs * It’s even better than code inspections * It virtually eliminates coder’s block * Unit tests make better designs * It’s faster than writing code without tests! &lt;a href=&quot;#fnref:1&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;
</description>
        <pubDate>Sun, 24 Jan 2016 12:00:00 +0000</pubDate>
        <link>http://matthewstrawbridge.com/content/2016/thoughts-on-writing-code-well/</link>
        <guid isPermaLink="true">http://matthewstrawbridge.com/content/2016/thoughts-on-writing-code-well/</guid>
        
        <category>software</category>
        
        <category>programming</category>
        
        <category>craftsmanship</category>
        
        
        <category>software</category>
        
        <category>programming</category>
        
        <category>craftsmanship</category>
        
      </item>
    
      <item>
        <title>The first software I installed on my new Windows laptop</title>
        <description>&lt;p&gt;After many years service my Windows laptop gave up the ghost. It was literally
starting to fall apart (the plastic near the hinge was breaking and it would
occasionally freeze up completely). So, as I had to reinstall the software
I use on my new laptop, I thought it would be interesting to record what
I installed and in which order.&lt;/p&gt;

&lt;p&gt;This is a useful &lt;em&gt;aide memoire&lt;/em&gt; for me, and might flag up a few interesting
tools that you might not know about.&lt;/p&gt;

&lt;h1 id=&quot;1-chrome&quot;&gt;1. Chrome&lt;/h1&gt;

&lt;p&gt;There are only two good uses for Internet Explorer: downloading another
browser and IE compatibility testing.&lt;/p&gt;

&lt;p&gt;So the first thing I did after powering on my new laptop was to open IE and
use it to install &lt;a href=&quot;http://www.google.com/chrome/index.html&quot;&gt;Google Chrome&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;As well as the browser itself, I created virtual application shortcuts –
&lt;img src=&quot;..\..\..\images\hamburger.png&quot; alt=&quot;=&quot; /&gt; | &lt;strong&gt;More tools&lt;/strong&gt; | &lt;strong&gt;Create application
shortcuts…&lt;/strong&gt; – for some of the sites I access most often:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;GMail&lt;/li&gt;
  &lt;li&gt;eBay&lt;/li&gt;
  &lt;li&gt;Facebook&lt;/li&gt;
  &lt;li&gt;Twitter&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I also set Chrome to start with a blank page and to use
&lt;a href=&quot;https://duckduckgo.com/&quot;&gt;DuckDuckGo&lt;/a&gt; as my default search engine.&lt;/p&gt;

&lt;h1 id=&quot;2-paintnet&quot;&gt;2. Paint.NET&lt;/h1&gt;

&lt;p&gt;The next application I installed was
&lt;a href=&quot;http://www.getpaint.net/index.html&quot;&gt;Paint.NET&lt;/a&gt;. It’s always useful to have
a free graphics manipulation package, and I find Paint.NET a lot simpler to
work with than &lt;a href=&quot;http://www.gimp.org/&quot;&gt;GIMP&lt;/a&gt;.&lt;/p&gt;

&lt;h1 id=&quot;3-dropbox&quot;&gt;3. Dropbox&lt;/h1&gt;

&lt;p&gt;I love &lt;a href=&quot;http://www.dropbox.com&quot;&gt;Dropbox&lt;/a&gt;. It just sits there quietly ticking
away, making sure all my important files are backed up.&lt;/p&gt;

&lt;p&gt;Just by installing Dropbox and letting it pull down my files, I ended up much
more at home in my new laptop’s environment.&lt;/p&gt;

&lt;h1 id=&quot;4-vim&quot;&gt;4. Vim&lt;/h1&gt;

&lt;p&gt;Next came &lt;a href=&quot;http://www.vim.org&quot;&gt;Vim&lt;/a&gt;, the text editor I couldn’t be without.&lt;/p&gt;

&lt;p&gt;I copied my &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_vimrc&lt;/code&gt; file out of Dropbox and immediately had a text editor set
up just the way I like it.&lt;/p&gt;

&lt;h1 id=&quot;5-conemu&quot;&gt;5. ConEmu&lt;/h1&gt;

&lt;p&gt;&lt;a href=&quot;https://conemu.github.io/&quot;&gt;ConEmu&lt;/a&gt; is a console emulator. It’s got lots of
bells and whistles, but the main benefits for me are that you can freely
resize the window, and you can group multiple consoles together as tabs within
a single application. Having a split window is also useful on occasion.&lt;/p&gt;

&lt;h1 id=&quot;6-inconsolata-font&quot;&gt;6. Inconsolata font&lt;/h1&gt;

&lt;p&gt;My favourite fixed-width font is
&lt;a href=&quot;http://levien.com/type/myfonts/inconsolata.html&quot;&gt;Inconsolata&lt;/a&gt; – in
particular, the &lt;a href=&quot;http://nodnod.net/2009/feb/12/adding-straight-single-and-double-quotes-inconsola/&quot;&gt;tweaked version with straight
quotes&lt;/a&gt;.
This is the font I always use in Vim on my own machines.&lt;/p&gt;

&lt;h1 id=&quot;7-linqpad&quot;&gt;7. LinqPad&lt;/h1&gt;

&lt;p&gt;&lt;a href=&quot;http://www.linqpad.net/&quot;&gt;LinqPad&lt;/a&gt; is a strange little application. It
describes itself as &lt;em&gt;The .NET Programmers Playground&lt;/em&gt;. I find it’s really
handy when I just want to try something out in C#. It’s quicker to open
LinqPad than to create a temporary solution in Visual Studio, and the rich
formatting of the &lt;a href=&quot;http://www.linqpad.net/#dump&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.Dump()&lt;/code&gt; function&lt;/a&gt; that it
adds to all objects makes it easy to see what’s going on. It’s particularly
useful when messing about with regular expressions.&lt;/p&gt;

&lt;h1 id=&quot;8-beyond-compare&quot;&gt;8. Beyond Compare&lt;/h1&gt;

&lt;p&gt;Not free, but cheap enough and good enough that it’s a no-brainer: &lt;a href=&quot;http://www.scootersoftware.com/&quot;&gt;Beyond
Compare&lt;/a&gt; does one thing and does it well –
comparing files.&lt;/p&gt;

&lt;p&gt;Specifically, it has some novel features:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;Explorer integration, so you can right-click a file and specify it as the
&lt;em&gt;left file to compare&lt;/em&gt;, then navigate somewhere else entirely, right-click
that file and &lt;em&gt;compare with left&lt;/em&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Built-in support for comparing images and spreadsheets.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;A text compare view where you can &lt;em&gt;paste in&lt;/em&gt; snippets of text from wherever
you want to grab them from. This is insanely useful and I find myself using
it all the time (e.g. for resolving merge conflicts in source control).&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;9-directory-opus&quot;&gt;9. Directory Opus&lt;/h1&gt;

&lt;p&gt;Just as ConEmu is a command window replacement, &lt;a href=&quot;http://www.gpsoft.com.au/&quot;&gt;Directory
Opus&lt;/a&gt; is a Windows Explorer replacement.&lt;/p&gt;

&lt;p&gt;It does all the stuff you’d expect, and it does it well.&lt;/p&gt;

&lt;h1 id=&quot;10-scrivener&quot;&gt;10. Scrivener&lt;/h1&gt;

&lt;p&gt;&lt;a href=&quot;http://www.literatureandlatte.com/scrivener.php&quot;&gt;Scrivener&lt;/a&gt; is the best
application for working with fiction.&lt;/p&gt;

&lt;h1 id=&quot;11-process-explorer&quot;&gt;11. Process Explorer&lt;/h1&gt;

&lt;p&gt;Once you’ve tried &lt;a href=&quot;https://technet.microsoft.com/en-us/sysinternals/bb896653&quot;&gt;Process
Explorer&lt;/a&gt; the
standard Windows Task Manager just won’t cut it any more.&lt;/p&gt;

&lt;p&gt;Originally developed by SysInternals, it’s now been subsumed into Microsoft.&lt;/p&gt;

&lt;h1 id=&quot;12-visual-studio&quot;&gt;12. Visual Studio&lt;/h1&gt;

&lt;p&gt;Speaking of Microsoft, I also installed Visual Studio…&lt;/p&gt;

&lt;h1 id=&quot;13-office&quot;&gt;13. Office&lt;/h1&gt;

&lt;p&gt;… and Microsoft Office.&lt;/p&gt;

&lt;h1 id=&quot;14-adblock-plus&quot;&gt;14. Adblock plus&lt;/h1&gt;

&lt;p&gt;There’s only so long you can use Chrome before you remember you haven’t got
&lt;a href=&quot;https://adblockplus.org/&quot;&gt;Adblock Plus&lt;/a&gt; installed. I remembered, so
I installed it.&lt;/p&gt;

&lt;p&gt;Seriously, bad marketeers are their own worst enemies! I left the “allow
unobtrusive ads” option enabled – I wish the whole fiasco wasn’t necessary.&lt;/p&gt;

&lt;h1 id=&quot;15-foxit-reader&quot;&gt;15. FoxIt Reader&lt;/h1&gt;

&lt;p&gt;If you need to read PDFs but don’t need the bloat and incessant updates that
you get with Adobe Reader then &lt;a href=&quot;https://www.foxitsoftware.com/products/pdf-reader/&quot;&gt;FoxIt
Reader&lt;/a&gt; is a lightweight
alternative.&lt;/p&gt;

&lt;h1 id=&quot;16-vlc-player&quot;&gt;16. VLC Player&lt;/h1&gt;

&lt;p&gt;Similarly, &lt;a href=&quot;https://www.videolan.org/vlc/&quot;&gt;VLC&lt;/a&gt; is a media player that can
handle almost any file format while being fairly unobtrusive.&lt;/p&gt;

&lt;h1 id=&quot;17-flux&quot;&gt;17. F.lux&lt;/h1&gt;

&lt;p&gt;&lt;a href=&quot;https://justgetflux.com/&quot;&gt;F.lux&lt;/a&gt; is something I’ve only discovered fairly
recently. It puts an overlay on your display to change the colour temperature
to better suit artificial lighting in the evening. It’s an interesting idea.&lt;/p&gt;

&lt;h1 id=&quot;18-7-zip&quot;&gt;18. 7-Zip&lt;/h1&gt;

&lt;p&gt;For archiving I use &lt;a href=&quot;http://www.7-zip.org/&quot;&gt;7-Zip&lt;/a&gt;. Not only does it produce
really tiny files in its own &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.7z&lt;/code&gt; format, but it can also produce smaller
standard &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.zip&lt;/code&gt; files than most other programs.&lt;/p&gt;

&lt;h1 id=&quot;19-cygwin&quot;&gt;19. Cygwin&lt;/h1&gt;

&lt;p&gt;There are a few Unix commands I don’t want to be without on Windows, so
I installed &lt;a href=&quot;https://cygwin.com/&quot;&gt;CygWin&lt;/a&gt; and its
&lt;a href=&quot;https://github.com/transcode-open/apt-cyg&quot;&gt;apt-cyg&lt;/a&gt; package manager.&lt;/p&gt;

&lt;p&gt;Here are some worthwhile commands that aren’t installed by default but are
worth getting:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;wget&lt;/li&gt;
  &lt;li&gt;whois&lt;/li&gt;
  &lt;li&gt;par&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;20-github-for-windows&quot;&gt;20. GitHub for Windows&lt;/h1&gt;

&lt;p&gt;It used to be really painful to work with GitHub from a Windows PC. You had to
install all sorts of third-party software and manage your own private key.&lt;/p&gt;

&lt;p&gt;These days, the &lt;a href=&quot;https://windows.github.com/&quot;&gt;GitHub for Windows&lt;/a&gt; handles all
that fluff and makes it easy to stay in sync.&lt;/p&gt;

&lt;p&gt;It actually includes both MySys Git and PoshGit, which will be in the path as
long as you open a shell from the application, so there’s nothing else to
install.&lt;/p&gt;

&lt;p&gt;Except…&lt;/p&gt;

&lt;h1 id=&quot;21-tortoisegit&quot;&gt;21. TortoiseGit&lt;/h1&gt;

&lt;p&gt;Okay, sometimes it’s useful to be able to interact with Git from an Explorer
(or Directory Opus) window instead of from the command line.
&lt;a href=&quot;https://code.google.com/p/tortoisegit/&quot;&gt;TortoiseGit&lt;/a&gt; isn’t &lt;em&gt;quite&lt;/em&gt; as
polished as TortoiseSVN, but it does a decent job.&lt;/p&gt;

&lt;p&gt;Rather than installing another MySys Git, you can just type &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;which git&lt;/code&gt; from
a command window opened up via GitHub for Windows and specify that path. If
you check the path, Tortoise will complain because &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git --version&lt;/code&gt; doesn’t
contain “mysys”, but that’s just because GitHub have rebranded it slightly.
It’ll still work fine.&lt;/p&gt;

&lt;p&gt;I ended up with this:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;C:\Users\Matthew\AppData\Local\GitHub\PortableGit_c2ba306e536fdf878271f7fe636a147ff37326ad\bin
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;22-resharper&quot;&gt;22. Resharper&lt;/h1&gt;

&lt;p&gt;Last, but not least, I find it increasingly frustrating to try to use a Visual
Studio environment without &lt;a href=&quot;https://www.jetbrains.com/resharper/&quot;&gt;Resharper&lt;/a&gt;
installed (for example, when pairing with someone else). If you use Visual
Studio you owe it to yourself to use Resharper.&lt;/p&gt;

&lt;p&gt;Yes, it slows things down slightly. No, I don’t care, because it gives back
much, much more time in improved productivity.&lt;/p&gt;

&lt;h1 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h1&gt;

&lt;p&gt;So there we have it. A smorgasbord of quality software – some mainstays, and
a few you might not have heard of. Many of them are free or inexpensive. All
of them improve my day-to-day productivity.&lt;/p&gt;

&lt;p&gt;I hope you found something new that you’d like to investigate. Is there
something missing that you couldn’t imaging being without on your own PC? If
so, drop me a line on Twitter and let me know.&lt;/p&gt;

</description>
        <pubDate>Sun, 19 Jul 2015 13:00:00 +0100</pubDate>
        <link>http://matthewstrawbridge.com/content/2015/the-first-software-to-install/</link>
        <guid isPermaLink="true">http://matthewstrawbridge.com/content/2015/the-first-software-to-install/</guid>
        
        <category>software</category>
        
        <category>tools</category>
        
        <category>utilities</category>
        
        <category>windows</category>
        
        <category>programming</category>
        
        
        <category>software</category>
        
        <category>tools</category>
        
        <category>utilities</category>
        
        <category>windows</category>
        
        <category>programming</category>
        
      </item>
    
      <item>
        <title>Migrating from Drupal to Jekyll</title>
        <description>&lt;h1 id=&quot;whats-this-blog-post-about&quot;&gt;What’s this blog post about?&lt;/h1&gt;

&lt;p&gt;The content of this blog has been migrated from one platform to
another several times now. Back in the day I had an account on
&lt;a href=&quot;http://philoxenic.blogspot.co.uk/&quot;&gt;Blogger&lt;/a&gt;. It looks like it’s still there,
which probably isn’t doing wonders for the SEO on this site with the copied
content.&lt;/p&gt;

&lt;p&gt;After that I started making blog posts on &lt;a href=&quot;www.swref.com&quot;&gt;Software Reference&lt;/a&gt;,
my publishing company’s site. The posts were technical things that interested
me, but weren’t necessarily relevant to the topics of the books we published
(i.e. they tended to be about programming rather than netiquette or Microsoft
Word 2007).&lt;/p&gt;

&lt;p&gt;Next the content moved to its own domain:
&lt;a href=&quot;www.matthewstrawbridge.com&quot;&gt;matthewstrawbridge.com&lt;/a&gt;. Until now it’s been a
Drupal site. This post is about migrating that content from a dynamic Drupal
site to a static one generated using Jekyll… why I wanted to change and how
I did it.&lt;/p&gt;

&lt;h2 id=&quot;whats-wrong-with-drupal&quot;&gt;What’s wrong with Drupal?&lt;/h2&gt;

&lt;h3 id=&quot;first-things-first-what-is-drupal&quot;&gt;First things first, what &lt;em&gt;is&lt;/em&gt; Drupal&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;https://www.drupal.org/&quot;&gt;Drupal&lt;/a&gt; is a content management system
along the same lines as &lt;a href=&quot;http://www.joomla.org/&quot;&gt;Joomla&lt;/a&gt; and
&lt;a href=&quot;https://wordpress.org/&quot;&gt;WordPress&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I started using Drupal because it seemed to be the most powerful of these
options: it gives you low-level control of your site without tying you into a
particular way of doing things, and there is a good selection of plug-ins for
further extending it.&lt;/p&gt;

&lt;p&gt;There’s nothing inherently wrong with Drupal. It does what it sets out to do.
Once you’ve got a site set up you can post new content and it takes care of
the rest. If you’ve got some hosting space you can get it set up and
configured fairly easily.&lt;/p&gt;

&lt;h3 id=&quot;whats-the-catch&quot;&gt;What’s the catch?&lt;/h3&gt;

&lt;p&gt;However, the &lt;strong&gt;elephant in the room&lt;/strong&gt; is the way it handles updates.&lt;/p&gt;

&lt;p&gt;How I would like Drupal to handle updates:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;You log in as the administrator and click a button labelled &lt;em&gt;Update&lt;/em&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;How you actually handle updates:&lt;/p&gt;

&lt;p data-pullquote=&quot;I mean, seriously, this is 2014!&quot;&gt;&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Take the site offline.&lt;/li&gt;
  &lt;li&gt;Manually download the latest version.&lt;/li&gt;
  &lt;li&gt;Remind yourself how to install it.&lt;/li&gt;
  &lt;li&gt;Go through a &lt;strong&gt;painful&lt;/strong&gt; patching process.&lt;/li&gt;
  &lt;li&gt;Put the site back online again.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That’s the abridged version. See the
&lt;a href=&quot;https://api.drupal.org/api/drupal/UPGRADE.txt&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UPGRADE.txt&lt;/code&gt;&lt;/a&gt; file if you’ve
got time to burn.&lt;/p&gt;

&lt;p&gt;All of which means that, unless you spend your life administering Drupal
websites professionally, your site is probably going to lag behind the latest
version for a while until you can summon up the energy to go through the pain
of updating it. I’d go as far as to guess that &lt;em&gt;most&lt;/em&gt; Drupal websites are not
up to date most of the time.&lt;/p&gt;

&lt;p&gt;Even with resources such as &lt;a href=&quot;https://github.com/drush-ops/drush/&quot;&gt;Drush&lt;/a&gt;
and &lt;a href=&quot;http://fuerstnet.de/en/drupal-upgrade-easier&quot;&gt;Bernhard Fürst’s version
patches&lt;/a&gt;, the whole process is
orders of magnitude more painful than I’d like it to be.&lt;/p&gt;

&lt;h3 id=&quot;the-final-nail-in-the-coffin&quot;&gt;The final nail in the coffin&lt;/h3&gt;

&lt;p&gt;So what is the tipping point? What has finally nudged me to abandon Drupal for
good?&lt;/p&gt;

&lt;p&gt;It’s &lt;a href=&quot;https://www.drupal.org/SA-CORE-2014-005&quot;&gt;this security advisory&lt;/a&gt;
(which I somehow missed at the time) and its
&lt;a href=&quot;https://www.drupal.org/PSA-2014-003&quot;&gt;follow-up&lt;/a&gt; (which I almost missed too).&lt;/p&gt;

&lt;p&gt;Basically, if you were using Drupal and hadn’t updated the software then your
site was wide open to hackers and there was a good chance that it had already
been compromised by a wide-scale automated attack. Why constantly fight to
keep installing security patch on security patch when you can just serve it
all up as static content?&lt;/p&gt;

&lt;h2 id=&quot;whats-good-about-jekyll&quot;&gt;What’s good about Jekyll?&lt;/h2&gt;

&lt;h3 id=&quot;so-what-is-jekyll&quot;&gt;So what is Jekyll?&lt;/h3&gt;

&lt;p&gt;&lt;img src=&quot;/images/jekyll-logo.png&quot; alt=&quot;Jekyll logo&quot; class=&quot;float&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Jekyll essentially knows about markdown and blogs, and how to turn one into
the other. It’s the engine that powers &lt;a href=&quot;https://pages.github.com/&quot;&gt;Github
Pages&lt;/a&gt;, so it’s got a good pedigree.&lt;/p&gt;

&lt;p&gt;Once it’s installed, you can just start writing posts as markdown files. You
can set Jekyll’s local server running and watching for changes and view the
blog in a web browser pointed at &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;localhost&lt;/code&gt; (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http://localhost:4000&lt;/code&gt; by
default).&lt;/p&gt;

&lt;p&gt;From there it’s just a matter of copying the static content to your server.
I’m using &lt;a href=&quot;http://www.site44.com&quot;&gt;site44&lt;/a&gt; so deployment is as simple as
copying it from one Dropbox folder to another and waiting for it to sync.&lt;/p&gt;

&lt;h3 id=&quot;what-else-did-i-consider&quot;&gt;What else did I consider?&lt;/h3&gt;

&lt;h4 id=&quot;octopress&quot;&gt;Octopress&lt;/h4&gt;

&lt;p&gt;Octopress (http://octopress.org/) is built on top of Jekyll, so one is worthy
of consideration if you’re thinking of using the other. In theory Octopress
simplifies working with Jekyll by providing various templates and helper
scripts. However, after reading various posts online from people switching
from Octopress back to plain Jekyll – for less complexity and more control –
I decided to plump for Jeckyll on its own.&lt;/p&gt;

&lt;p&gt;If you’ve written HTML and CSS in the past then editing the HTML templates and
sass/scss styles used by Jekyll is actually quite pleasant, so I’m not sure
Octopress would add much apart from complexity.&lt;/p&gt;

&lt;h4 id=&quot;squarespace&quot;&gt;SquareSpace&lt;/h4&gt;

&lt;p&gt;&lt;a href=&quot;http://www.squarespace.com&quot;&gt;SquareSpace&lt;/a&gt; would take away all the pain of
administering you own server by providing that for you. I’ve used it before
for various projects.&lt;/p&gt;

&lt;p&gt;It is a fine option if you don’t already have hosting space that you’re paying
for. If you &lt;em&gt;do&lt;/em&gt;, then it’s just a little too expensive, especially if you
don’t need more than one user to be able to post updates.&lt;/p&gt;

&lt;p&gt;They’d do well to have an inexpensive cut-down option with very low storage
and bandwidth to encourage people into their ecosystem.&lt;/p&gt;

&lt;h1 id=&quot;getting-up-and-running-with-jekyll-on-windows&quot;&gt;Getting up and running with Jekyll on Windows&lt;/h1&gt;

&lt;p&gt;Jekyll’s really easy to use on Linux or a Mac. On Windows it’s a bit
more fiddly to set up: some combinations of the various components
work better than others. There are basically two ways to go: follow
the excellent &lt;a href=&quot;http://jekyll-windows.juthilo.com/&quot;&gt;instructions here&lt;/a&gt;
or download the whole lot pre-configured from
&lt;a href=&quot;http://www.madhur.co.in/blog/2013/07/20/buildportablejekyll.html&quot;&gt;PortableJekyll&lt;/a&gt;.
I went the latter route.&lt;/p&gt;

&lt;p&gt;Go to &lt;a href=&quot;https://github.com/madhur/PortableJekyll&quot;&gt;https://github.com/madhur/PortableJekyll&lt;/a&gt;
and fork it.&lt;/p&gt;

&lt;p&gt;Open &lt;em&gt;Github for Windows&lt;/em&gt; and click &lt;strong&gt;+&lt;/strong&gt;, &lt;strong&gt;Clone&lt;/strong&gt;, &lt;strong&gt;Portable Jekyll&lt;/strong&gt;,
&lt;strong&gt;Tick&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Select a folder to clone it into. I chose the folder &lt;em&gt;Software&lt;/em&gt; in my Dropbox.
t takes quite a while to fetch the content, so go off and do something else
while you’re waiting. You end up with a &lt;em&gt;PortableJekyll&lt;/em&gt; folder beneath the
folder you selected. (You could just download the latest snapshot from GitHub
and unpack it onto disk without the repository, but doing it via Git should
make updating to future versions slightly easier.)&lt;/p&gt;

&lt;h1 id=&quot;creating-a-blog&quot;&gt;Creating a blog&lt;/h1&gt;

&lt;h2 id=&quot;creating-an-empty-jekyll-project&quot;&gt;Creating an empty Jekyll project&lt;/h2&gt;

&lt;p&gt;In a console, change to the parent folder of where you want to create your
site.&lt;/p&gt;

&lt;p&gt;Run the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;setpath.cmd&lt;/code&gt; file from the folder you saved Portable Jekyll into:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;c:\Users\Matthew\Dropbox\Software\PortableJekyll\setpath.cmd
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This adds the components of Portable Jekyll to the path.&lt;/p&gt;

&lt;p&gt;Now use the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jekyll new&lt;/code&gt; command to construct an empty Jekyll project:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;c:\Users\Matthew\Dropbox\Web&amp;gt;jekyll new MatthewStrawbridge.com&lt;/p&gt;

  &lt;p&gt;New jekyll site installed in c:/Users/Matthew/Dropbox/Web/MatthewStrawbridge.com.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This creates various files and folders to form a very simple website.&lt;/p&gt;

&lt;p&gt;Now is a good opportunity to add the empty site as a baseline to Git:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;cd MatthewStrawbridge.com

git init

git add .

git commit -m &quot;baseline&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;setting-up-syntax-highlighting&quot;&gt;Setting up syntax highlighting&lt;/h2&gt;

&lt;p&gt;Jekyll ships with a couple of syntax highlighters:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;Pygments is the default. It’s written in Python and supports syntax
highlighting in lots of languages.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Rouge is written in Ruby and supports fewer languages, but still has all the
ones I’m likely to need.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I had issues getting Pygments to run so I added the following to my site’s
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_config.yml&lt;/code&gt; file to switch it to use Rouge:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;highlighter: rouge
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;building-and-viewing-the-site&quot;&gt;Building and viewing the site&lt;/h2&gt;

&lt;p&gt;Now we can try building the new empty site.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;jekyll build
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If you don’t get any red text output to the console then it has probably
worked.&lt;/p&gt;

&lt;p&gt;So we can now try the site:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;jekyll serve --force-polling
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;(You might need to allow the Windows firewall to let Ruby do its thing, if you
get asked.)&lt;/p&gt;

&lt;p&gt;It looks like you need to use the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--force_polling&lt;/code&gt; option on Windows. Without
this, once watch was triggered by a change, it tried to regenerate the whole
site once per second even when there were no further changes! But with this
option it sits there quite happily, only springing into action when a genuine
change has been made.&lt;/p&gt;

&lt;p&gt;This starts a local webserver and watches your project for changes. To see the
site, just go to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http://localhost:4000/&lt;/code&gt; in a web browser.&lt;/p&gt;

&lt;p&gt;You should see something like this:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/jekyll-default-site.png&quot; alt=&quot;Jekyll default site&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Clicking on the title of the default post which has been created for you gives
you an idea of the format you can expect, as well as providing some basic
instructions about what lives where:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/jekyll-default-post.png&quot; alt=&quot;Jekyll default post&quot; class=&quot;standout&quot; /&gt;&lt;/p&gt;

&lt;p&gt;This is a good point to tweak the various settings in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_config.xml&lt;/code&gt; to make
the site feel more your own. The server will continue to run in the background
so once you save the file, give it a second or two and you can reload it in
your browser.&lt;/p&gt;

&lt;p&gt;You might need to kill the Jekyll server and restart it to detect that the
config file has changed.&lt;/p&gt;

&lt;h1 id=&quot;adding-posts&quot;&gt;Adding posts&lt;/h1&gt;

&lt;p&gt;Adding a new post is as simple as creating a new markdown (or HTML if you
prefer) file in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_posts&lt;/code&gt; directory. If you use the default naming
convention of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;yyyy-mm-dd-title&lt;/code&gt; then you don’t have to specify the post’s
date and title in the metadata section at the top of the file.&lt;/p&gt;

&lt;p&gt;As an example, the top of the markdown file for this post looks like this:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-markdown&quot; data-lang=&quot;markdown&quot;&gt;&lt;span class=&quot;s&quot;&gt;---&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;
&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;layout&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;     &lt;span class=&quot;s&quot;&gt;post&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;
&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;      &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Migrating&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Drupal&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;to&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Jekyll&quot;&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;
&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;date&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;       &lt;span class=&quot;s&quot;&gt;2014-11-08 23:30:00&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;
&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;published&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;  &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;
&lt;/span&gt;
&lt;span class=&quot;s&quot;&gt;---&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;
&lt;/span&gt;

&lt;span class=&quot;gh&quot;&gt;# What&apos;s this blog post about?
&lt;/span&gt;

The content of this blog has been migrated from one platform to
another several times now. Back in the day I had an account on
&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;Blogger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;](&lt;/span&gt;&lt;span class=&quot;sx&quot;&gt;http://philoxenic.blogspot.co.uk/&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;. It looks like it&apos;s still there,
which probably isn&apos;t doing wonders for the SEO on this site with the copied
content.&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;You can read more about Markdown formatting
&lt;a href=&quot;http://daringfireball.net/projects/markdown/&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h1 id=&quot;summing-up&quot;&gt;Summing up&lt;/h1&gt;

&lt;p&gt;So that’s it. I’ve moved from a fairly heavyweight content management system
to lightweight HTML. It’s just as easy to write posts in Markdown as it was
to use Drupal’s editing system, and publishing new posts is simply a matter
of running a script to copy the generated site from one part of my Dropbox
hierarchy to another.&lt;/p&gt;

&lt;p&gt;If you’re looking for a way to publish a blog or other content as HTML but
prefer writing in Markdown then give Jekyll a try.&lt;/p&gt;

</description>
        <pubDate>Sat, 08 Nov 2014 23:30:00 +0000</pubDate>
        <link>http://matthewstrawbridge.com/content/2014/migrating-from-drupal-to-jekyll/</link>
        <guid isPermaLink="true">http://matthewstrawbridge.com/content/2014/migrating-from-drupal-to-jekyll/</guid>
        
        <category>drupal</category>
        
        <category>jekyll</category>
        
        <category>update</category>
        
        <category>markdown</category>
        
        
        <category>drupal</category>
        
        <category>jekyll</category>
        
        <category>update</category>
        
        <category>markdown</category>
        
      </item>
    
      <item>
        <title>Smarter Classes with Less Code (Part 2 of 2)</title>
        <description>&lt;p&gt;&lt;a href=&quot;/content/2014/smarter-classes-less-code-part-1-2&quot;&gt;Last time&lt;/a&gt; we
took a step towards adding useful functionality to C# types to automatically
add some of the features you get for free in functional languages, such as
immutability and equality comparisons based on properties.&lt;/p&gt;

&lt;p&gt;This time we’ll look at how to use &lt;a href=&quot;http://www.postsharp.net/&quot;&gt;PostSharp&lt;/a&gt; to inject the functionality
we’re interested in based on custom attributes. Not only will that be more
efficient at runtime than relying on .NET reflection, it will allow us to
derive our types from others if we wish.&lt;/p&gt;

&lt;h2&gt;PostSharp immutable threading pattern&lt;/h2&gt;

&lt;p&gt;As I was trying to write the code for this
article, I came across this article: &lt;a href=&quot;http://www.postsharp.net/blog/post/New-in-PostSharp-32-Immutable-and-Fre ezable-Done-Well&quot;&gt;New in PostSharp 3.2: Immutable and Freezable Done Well&lt;/a&gt;.
This discusses some new attributes available only in recent versions of
PostSharp, so you need to install it via NuGet with the “Include Prerelease”
option selected. You also need to install the pre-release version of the
PostSharp Threading Pattern Library.&lt;/p&gt;

&lt;p&gt;This means you can define the value class with a simple &lt;tt&gt;[Immutable]&lt;/tt&gt;
attribute like this:&lt;/p&gt;

&lt;style type=&quot;text/css&quot;&gt;
&lt;!--
pre { font-family: monospace; color: #c0c0c0; background-color: #000040; }
* { font-size: 1em; }
.Constant { color: #ffa0a0; }
.Statement { color: #ffff60; }
.Type { color: #60ff60; }
--&gt;
&lt;/style&gt;

&lt;pre id=&quot;vimCodeElement&quot;&gt;
&lt;span class=&quot;Statement&quot;&gt;using&lt;/span&gt; PostSharp.Patterns.Threading;

&lt;span class=&quot;Type&quot;&gt;namespace&lt;/span&gt; ValueClasses
{
    [Immutable]
    &lt;span class=&quot;Type&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;Type&quot;&gt;class&lt;/span&gt; Item
    {
        &lt;span class=&quot;Type&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;Type&quot;&gt;int&lt;/span&gt; Id { get; &lt;span class=&quot;Type&quot;&gt;private&lt;/span&gt; set; }
        &lt;span class=&quot;Type&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;Type&quot;&gt;string&lt;/span&gt; Description { get; &lt;span class=&quot;Type&quot;&gt;private&lt;/span&gt; set; }

        &lt;span class=&quot;Type&quot;&gt;public&lt;/span&gt; Item(&lt;span class=&quot;Type&quot;&gt;int&lt;/span&gt; id, &lt;span class=&quot;Type&quot;&gt;string&lt;/span&gt; description)
        {
            Id = id;
            Description = description;
        }

        &lt;span class=&quot;Type&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;Type&quot;&gt;void&lt;/span&gt; ChangeDescription()
        {
            Description = &lt;span class=&quot;Constant&quot;&gt;&amp;quot;changed&amp;quot;&lt;/span&gt;;
        }
    }
}
&lt;/pre&gt;

&lt;p&gt;And then if we run a program like this:&lt;/p&gt;

&lt;pre id=&quot;vimCodeElement&quot;&gt;
&lt;span class=&quot;Statement&quot;&gt;using&lt;/span&gt; System;
&lt;span class=&quot;Statement&quot;&gt;using&lt;/span&gt; ValueClasses;

&lt;span class=&quot;Type&quot;&gt;namespace&lt;/span&gt; DDD2
{
    &lt;span class=&quot;Type&quot;&gt;class&lt;/span&gt; Program
    {
        &lt;span class=&quot;Type&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;Type&quot;&gt;void&lt;/span&gt; Main()
        {
            var item = &lt;span class=&quot;Statement&quot;&gt;new&lt;/span&gt; Item(&lt;span class=&quot;Constant&quot;&gt;123&lt;/span&gt;, &lt;span class=&quot;Constant&quot;&gt;&amp;quot;My Description&amp;quot;&lt;/span&gt;);
            Console.WriteLine(item);
            item.ChangeDescription();
            Console.WriteLine(item);
        }
    }
}
&lt;/pre&gt;

&lt;p&gt;… then we get an exception thrown from the &lt;tt&gt;ChangeDescription&lt;/tt&gt; call.&lt;/p&gt;

&lt;p&gt;That’s pretty awesome! PostSharp is preventing the class’s own method from
changing its property’s backing field after construction. That’s a level of
protection (and bug prevention) you can’t get through private setters.&lt;/p&gt;

&lt;p&gt;Decompiling the resultant assembly with JetBrains’ free stand-alone tool &lt;a href=&quot;http://www.jetbrains.com/decompiler/&quot;&gt;dotPeek&lt;/a&gt; shows just how much
extra code PostSharp has injected to get this to work:&lt;/p&gt;

&lt;pre id=&quot;vimCodeElement&quot;&gt;
&lt;span class=&quot;Statement&quot;&gt;using&lt;/span&gt; PostSharp.Aspects;
&lt;span class=&quot;Statement&quot;&gt;using&lt;/span&gt; PostSharp.Aspects.Advices;
&lt;span class=&quot;Statement&quot;&gt;using&lt;/span&gt; PostSharp.Aspects.Internals;
&lt;span class=&quot;Statement&quot;&gt;using&lt;/span&gt; PostSharp.Extensibility;
&lt;span class=&quot;Statement&quot;&gt;using&lt;/span&gt; PostSharp.Patterns.Model;
&lt;span class=&quot;Statement&quot;&gt;using&lt;/span&gt; PostSharp.Patterns.Threading;
&lt;span class=&quot;Statement&quot;&gt;using&lt;/span&gt; PostSharp.Reflection;
&lt;span class=&quot;Statement&quot;&gt;using&lt;/span&gt; System;
&lt;span class=&quot;Statement&quot;&gt;using&lt;/span&gt; System.Diagnostics;
&lt;span class=&quot;Statement&quot;&gt;using&lt;/span&gt; System.Runtime.CompilerServices;

&lt;span class=&quot;Type&quot;&gt;namespace&lt;/span&gt; ValueClasses
{
  [HasInheritedAttribute(&lt;span class=&quot;Statement&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;Type&quot;&gt;long&lt;/span&gt;[] {})]
  [Immutable(AttributeId = &lt;span class=&quot;Constant&quot;&gt;881225518374429271L&lt;/span&gt;, AttributePriority = &lt;span class=&quot;Constant&quot;&gt;268435456&lt;/span&gt;)]
  [HasOnInstanceInitialized]
  &lt;span class=&quot;Type&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;Type&quot;&gt;class&lt;/span&gt; Item : IAttachable, IImmutable
  {
    &lt;span class=&quot;Type&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;Type&quot;&gt;int&lt;/span&gt; \u003CId\u003Ek__OriginalField;
    &lt;span class=&quot;Type&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;Type&quot;&gt;string&lt;/span&gt; \u003CDescription\u003Ek__OriginalField;
    [NonSerialized]
    &lt;span class=&quot;Type&quot;&gt;private&lt;/span&gt; ImmutableAttribute \u003C\u003Ez__aspect0;
    [NonSerialized]
    &lt;span class=&quot;Type&quot;&gt;private&lt;/span&gt; AggregatableAttribute \u003C\u003Ez__aspect1;

    &lt;span class=&quot;Type&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;Type&quot;&gt;int&lt;/span&gt; Id
    {
      &lt;span class=&quot;Statement&quot;&gt;get&lt;/span&gt;
      {
        &lt;span class=&quot;Statement&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;Statement&quot;&gt;this&lt;/span&gt;.get_\u003CId\u003Ek__BackingField();
      }
      &lt;span class=&quot;Type&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;Statement&quot;&gt;set&lt;/span&gt;
      {
        &lt;span class=&quot;Statement&quot;&gt;this&lt;/span&gt;.set_\u003CId\u003Ek__BackingField(&lt;span class=&quot;Statement&quot;&gt;value&lt;/span&gt;);
      }
    }

    &lt;span class=&quot;Type&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;Type&quot;&gt;string&lt;/span&gt; Description
    {
      &lt;span class=&quot;Statement&quot;&gt;get&lt;/span&gt;
      {
        &lt;span class=&quot;Statement&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;Statement&quot;&gt;this&lt;/span&gt;.get_\u003CDescription\u003Ek__BackingField();
      }
      &lt;span class=&quot;Type&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;Statement&quot;&gt;set&lt;/span&gt;
      {
        &lt;span class=&quot;Statement&quot;&gt;this&lt;/span&gt;.set_\u003CDescription\u003Ek__BackingField(&lt;span class=&quot;Statement&quot;&gt;value&lt;/span&gt;);
      }
    }

    [CompilerGenerated]
    &lt;span class=&quot;Type&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;Type&quot;&gt;int&lt;/span&gt; \u003CId\u003Ek__BackingField
    {
      [DebuggerNonUserCode] &lt;span class=&quot;Statement&quot;&gt;get&lt;/span&gt;
      {
        LocationInterceptionArgsImpl&amp;lt;&lt;span class=&quot;Type&quot;&gt;int&lt;/span&gt;&amp;gt; interceptionArgsImpl = &lt;span class=&quot;Statement&quot;&gt;new&lt;/span&gt; LocationInterceptionArgsImpl&amp;lt;&lt;span class=&quot;Type&quot;&gt;int&lt;/span&gt;&amp;gt;((&lt;span class=&quot;Type&quot;&gt;object&lt;/span&gt;) &lt;span class=&quot;Statement&quot;&gt;this&lt;/span&gt;, Arguments.Empty);
        interceptionArgsImpl.DeclarationIdentifier = &lt;span class=&quot;Statement&quot;&gt;new&lt;/span&gt; DeclarationIdentifier(&lt;span class=&quot;Constant&quot;&gt;881225515594350592L&lt;/span&gt;);
        interceptionArgsImpl.TypedBinding = (LocationBinding&amp;lt;&lt;span class=&quot;Type&quot;&gt;int&lt;/span&gt;&amp;gt;) Item.\u003CId\u003Ec__Binding0.singleton;
        &lt;span class=&quot;Statement&quot;&gt;this&lt;/span&gt;.\u003C\u003Ez__aspect0.OnFieldGet((LocationInterceptionArgs) interceptionArgsImpl);
        &lt;span class=&quot;Statement&quot;&gt;return&lt;/span&gt; interceptionArgsImpl.TypedValue;
      }
      [DebuggerNonUserCode] &lt;span class=&quot;Statement&quot;&gt;set&lt;/span&gt;
      {
        LocationInterceptionArgsImpl&amp;lt;&lt;span class=&quot;Type&quot;&gt;int&lt;/span&gt;&amp;gt; interceptionArgsImpl = &lt;span class=&quot;Statement&quot;&gt;new&lt;/span&gt; LocationInterceptionArgsImpl&amp;lt;&lt;span class=&quot;Type&quot;&gt;int&lt;/span&gt;&amp;gt;((&lt;span class=&quot;Type&quot;&gt;object&lt;/span&gt;) &lt;span class=&quot;Statement&quot;&gt;this&lt;/span&gt;, Arguments.Empty);
        interceptionArgsImpl.DeclarationIdentifier = &lt;span class=&quot;Statement&quot;&gt;new&lt;/span&gt; DeclarationIdentifier(&lt;span class=&quot;Constant&quot;&gt;881225515594350592L&lt;/span&gt;);
        interceptionArgsImpl.TypedBinding = (LocationBinding&amp;lt;&lt;span class=&quot;Type&quot;&gt;int&lt;/span&gt;&amp;gt;) Item.\u003CId\u003Ec__Binding.singleton;
        interceptionArgsImpl.TypedValue = &lt;span class=&quot;Statement&quot;&gt;value&lt;/span&gt;;
        &lt;span class=&quot;Statement&quot;&gt;this&lt;/span&gt;.\u003C\u003Ez__aspect0.OnFieldSet((LocationInterceptionArgs) interceptionArgsImpl);
      }
    }

    [CompilerGenerated]
    &lt;span class=&quot;Type&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;Type&quot;&gt;string&lt;/span&gt; \u003CDescription\u003Ek__BackingField
    {
      [DebuggerNonUserCode] &lt;span class=&quot;Statement&quot;&gt;get&lt;/span&gt;
      {
        LocationInterceptionArgsImpl&amp;lt;&lt;span class=&quot;Type&quot;&gt;string&lt;/span&gt;&amp;gt; interceptionArgsImpl = &lt;span class=&quot;Statement&quot;&gt;new&lt;/span&gt; LocationInterceptionArgsImpl&amp;lt;&lt;span class=&quot;Type&quot;&gt;string&lt;/span&gt;&amp;gt;((&lt;span class=&quot;Type&quot;&gt;object&lt;/span&gt;) &lt;span class=&quot;Statement&quot;&gt;this&lt;/span&gt;, Arguments.Empty);
        interceptionArgsImpl.DeclarationIdentifier = &lt;span class=&quot;Statement&quot;&gt;new&lt;/span&gt; DeclarationIdentifier(&lt;span class=&quot;Constant&quot;&gt;881225515594350593L&lt;/span&gt;);
        interceptionArgsImpl.TypedBinding = (LocationBinding&amp;lt;&lt;span class=&quot;Type&quot;&gt;string&lt;/span&gt;&amp;gt;) Item.\u003CDescription\u003Ec__Binding0.singleton;
        &lt;span class=&quot;Statement&quot;&gt;this&lt;/span&gt;.\u003C\u003Ez__aspect0.OnFieldGet((LocationInterceptionArgs) interceptionArgsImpl);
        &lt;span class=&quot;Statement&quot;&gt;return&lt;/span&gt; interceptionArgsImpl.TypedValue;
      }
      [DebuggerNonUserCode] &lt;span class=&quot;Statement&quot;&gt;set&lt;/span&gt;
      {
        LocationInterceptionArgsImpl&amp;lt;&lt;span class=&quot;Type&quot;&gt;string&lt;/span&gt;&amp;gt; interceptionArgsImpl = &lt;span class=&quot;Statement&quot;&gt;new&lt;/span&gt; LocationInterceptionArgsImpl&amp;lt;&lt;span class=&quot;Type&quot;&gt;string&lt;/span&gt;&amp;gt;((&lt;span class=&quot;Type&quot;&gt;object&lt;/span&gt;) &lt;span class=&quot;Statement&quot;&gt;this&lt;/span&gt;, Arguments.Empty);
        interceptionArgsImpl.DeclarationIdentifier = &lt;span class=&quot;Statement&quot;&gt;new&lt;/span&gt; DeclarationIdentifier(&lt;span class=&quot;Constant&quot;&gt;881225515594350593L&lt;/span&gt;);
        interceptionArgsImpl.TypedBinding = (LocationBinding&amp;lt;&lt;span class=&quot;Type&quot;&gt;string&lt;/span&gt;&amp;gt;) Item.\u003CDescription\u003Ec__Binding.singleton;
        interceptionArgsImpl.TypedValue = &lt;span class=&quot;Statement&quot;&gt;value&lt;/span&gt;;
        &lt;span class=&quot;Statement&quot;&gt;this&lt;/span&gt;.\u003C\u003Ez__aspect0.OnFieldSet((LocationInterceptionArgs) interceptionArgsImpl);
      }
    }

    &lt;span class=&quot;Type&quot;&gt;protected&lt;/span&gt; Item(&lt;span class=&quot;Type&quot;&gt;int&lt;/span&gt; id, &lt;span class=&quot;Type&quot;&gt;string&lt;/span&gt; description, ConstructorDepth __depth)
    {
      &lt;span class=&quot;Comment&quot;&gt;// ISSUE: reference to a compiler-generated method&lt;/span&gt;
      &lt;span class=&quot;Statement&quot;&gt;this&lt;/span&gt;.\u003C\u003Ez__InitializeAspects(AspectInitializationReason.Constructor);
      &lt;span class=&quot;Statement&quot;&gt;this&lt;/span&gt;.Id = id;
      &lt;span class=&quot;Statement&quot;&gt;this&lt;/span&gt;.Description = description;
      &lt;span class=&quot;Statement&quot;&gt;if&lt;/span&gt; (!__depth.IsZero)
        &lt;span class=&quot;Statement&quot;&gt;return&lt;/span&gt;;
      &lt;span class=&quot;Comment&quot;&gt;// ISSUE: reference to a compiler-generated method&lt;/span&gt;
      &lt;span class=&quot;Statement&quot;&gt;this&lt;/span&gt;.OnInstanceInitialized();
    }

    &lt;span class=&quot;Type&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;Type&quot;&gt;void&lt;/span&gt; ChangeDescription()
    {
      &lt;span class=&quot;Statement&quot;&gt;this&lt;/span&gt;.Description = &lt;span class=&quot;Constant&quot;&gt;&amp;quot;changed&amp;quot;&lt;/span&gt;;
    }

    &lt;span class=&quot;Type&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;Type&quot;&gt;virtual&lt;/span&gt; &lt;span class=&quot;Type&quot;&gt;void&lt;/span&gt; OnChildDetached(&lt;span class=&quot;Type&quot;&gt;object&lt;/span&gt; child, ChildInfo childInfo)
    {
      &lt;span class=&quot;Statement&quot;&gt;this&lt;/span&gt;.\u003C\u003Ez__aspect1.OnChildDetached(child, childInfo);
    }

    &lt;span class=&quot;Type&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;Type&quot;&gt;virtual&lt;/span&gt; &lt;span class=&quot;Type&quot;&gt;void&lt;/span&gt; OnParentChanged()
    {
      &lt;span class=&quot;Statement&quot;&gt;this&lt;/span&gt;.\u003C\u003Ez__aspect0.OnParentChanged();
    }

    &lt;span class=&quot;Type&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;Type&quot;&gt;virtual&lt;/span&gt; &lt;span class=&quot;Type&quot;&gt;void&lt;/span&gt; OnChildAttached(&lt;span class=&quot;Type&quot;&gt;object&lt;/span&gt; child, ChildInfo childInfo)
    {
      &lt;span class=&quot;Statement&quot;&gt;this&lt;/span&gt;.\u003C\u003Ez__aspect0.OnChildAttached(child, childInfo);
    }

    &lt;span class=&quot;Type&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;Type&quot;&gt;virtual&lt;/span&gt; &lt;span class=&quot;Type&quot;&gt;void&lt;/span&gt; OnAncestorChanged(AncestorChangedEventArgs args)
    {
      &lt;span class=&quot;Statement&quot;&gt;this&lt;/span&gt;.\u003C\u003Ez__aspect1.OnAncestorChanged(args);
    }

    &lt;span class=&quot;Type&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;Type&quot;&gt;virtual&lt;/span&gt; &lt;span class=&quot;Type&quot;&gt;bool&lt;/span&gt; VisitChildren(ChildVisitor visitor, ChildVisitorOptions options)
    {
      &lt;span class=&quot;Statement&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;Statement&quot;&gt;this&lt;/span&gt;.\u003C\u003Ez__aspect1.VisitChildren(visitor, options);
    }

    &lt;span class=&quot;Type&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;Type&quot;&gt;void&lt;/span&gt; \u003COnChildAttached\u003Ez__(&lt;span class=&quot;Type&quot;&gt;object&lt;/span&gt; child, ChildInfo childInfo)
    {
      &lt;span class=&quot;Statement&quot;&gt;this&lt;/span&gt;.\u003C\u003Ez__aspect1.OnChildAttached(child, childInfo);
    }

    &lt;span class=&quot;Type&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;Type&quot;&gt;void&lt;/span&gt; \u003COnParentChanged\u003Ez__()
    {
      &lt;span class=&quot;Statement&quot;&gt;this&lt;/span&gt;.\u003C\u003Ez__aspect1.OnParentChanged();
    }
  }
}
&lt;/pre&gt;

&lt;p&gt;So, yeah… you wouldn’t really want to write that lot by hand to achieve the
same thing.&lt;/p&gt;

&lt;h3&gt;Other aspects&lt;/h3&gt;

&lt;p&gt;As well as the &lt;tt&gt;[Immutable]&lt;/tt&gt; aspect, there’s also a
&lt;tt&gt;[Freezable]&lt;/tt&gt; model. This is similar but you start with a fully
mutable object that you can make changes to and at some point you call its
&lt;tt&gt;Freeze()&lt;/tt&gt; method and after that you can no longer change it.&lt;/p&gt;

&lt;h2&gt;Adding Equals and GetHashCode&lt;/h2&gt;

&lt;p&gt;So that’s a summary of the type of thing PostSharp can do. What about adding
&lt;tt&gt;Equals&lt;/tt&gt; and &lt;tt&gt;GetHashCode&lt;/tt&gt; like the previous article?&lt;/p&gt;

&lt;p&gt;I couldn’t work out how to do this myself so I &lt;a href=&quot;http://stackoverflow.com/questions/26005468/&quot;&gt;asked the question on
Stack Overflow&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Here’s some code based on the answer I got there:&lt;/p&gt;

&lt;pre id=&quot;vimCodeElement&quot;&gt;
&lt;span class=&quot;Statement&quot;&gt;using&lt;/span&gt; System;
&lt;span class=&quot;Statement&quot;&gt;using&lt;/span&gt; System.Collections.Generic;
&lt;span class=&quot;Statement&quot;&gt;using&lt;/span&gt; System.Reflection;
&lt;span class=&quot;Statement&quot;&gt;using&lt;/span&gt; PostSharp.Aspects;
&lt;span class=&quot;Statement&quot;&gt;using&lt;/span&gt; PostSharp.Aspects.Advices;
&lt;span class=&quot;Statement&quot;&gt;using&lt;/span&gt; PostSharp.Reflection;
&lt;span class=&quot;Statement&quot;&gt;using&lt;/span&gt; PostSharp.Serialization;

&lt;span class=&quot;Type&quot;&gt;namespace&lt;/span&gt; ValueClasses
{
    [PSerializable]
    &lt;span class=&quot;Type&quot;&gt;class&lt;/span&gt; AutoEqAttribute : InstanceLevelAspect, IAdviceProvider
    {
        &lt;span class=&quot;Comment&quot;&gt;///&lt;/span&gt;&lt;span class=&quot;Comment&quot;&gt; &lt;/span&gt;&lt;span class=&quot;Identifier&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;Statement&quot;&gt;summary&lt;/span&gt;&lt;span class=&quot;Identifier&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;Comment&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;Comment&quot;&gt;///&lt;/span&gt;&lt;span class=&quot;Comment&quot;&gt; The fields from the class this attribute has been added to&lt;/span&gt;
&lt;span class=&quot;Comment&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;Comment&quot;&gt;///&lt;/span&gt;&lt;span class=&quot;Comment&quot;&gt; &lt;/span&gt;&lt;span class=&quot;Identifier&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&quot;Statement&quot;&gt;summary&lt;/span&gt;&lt;span class=&quot;Identifier&quot;&gt;&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;Comment&quot;&gt;///&lt;/span&gt;&lt;span class=&quot;Comment&quot;&gt; &lt;/span&gt;&lt;span class=&quot;Identifier&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;Statement&quot;&gt;remarks&lt;/span&gt;&lt;span class=&quot;Identifier&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;Comment&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;Comment&quot;&gt;///&lt;/span&gt;&lt;span class=&quot;Comment&quot;&gt; Automatically populated by PostSharp with the fields identified by&lt;/span&gt;
&lt;span class=&quot;Comment&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;Comment&quot;&gt;///&lt;/span&gt;&lt;span class=&quot;Comment&quot;&gt; &lt;/span&gt;&lt;span class=&quot;Identifier&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;Statement&quot;&gt;see&lt;/span&gt;&lt;span class=&quot;Identifier&quot;&gt; &lt;/span&gt;&lt;span class=&quot;Type&quot;&gt;cref&lt;/span&gt;=&lt;span class=&quot;Constant&quot;&gt;&amp;quot;ProvideAdvices&amp;quot;&lt;/span&gt;&lt;span class=&quot;Identifier&quot;&gt;/&amp;gt;&lt;/span&gt;
&lt;span class=&quot;Comment&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;Comment&quot;&gt;///&lt;/span&gt;&lt;span class=&quot;Comment&quot;&gt; &lt;/span&gt;&lt;span class=&quot;Identifier&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&quot;Statement&quot;&gt;remarks&lt;/span&gt;&lt;span class=&quot;Identifier&quot;&gt;&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;Type&quot;&gt;public&lt;/span&gt; List&amp;lt;ILocationBinding&amp;gt; Fields;

        &lt;span class=&quot;Comment&quot;&gt;/////////&lt;/span&gt;
        &lt;span class=&quot;Comment&quot;&gt;// Equals&lt;/span&gt;

        &lt;span class=&quot;Comment&quot;&gt;// Grab a reference to the base class&apos;s Equals method&lt;/span&gt;

        [ImportMember(&lt;span class=&quot;Constant&quot;&gt;&amp;quot;Equals&amp;quot;&lt;/span&gt;, IsRequired = &lt;span class=&quot;Constant&quot;&gt;true&lt;/span&gt;, Order = ImportMemberOrder.BeforeIntroductions)]
        &lt;span class=&quot;Type&quot;&gt;public&lt;/span&gt; Func&amp;lt;&lt;span class=&quot;Type&quot;&gt;object&lt;/span&gt;, &lt;span class=&quot;Type&quot;&gt;bool&lt;/span&gt;&amp;gt; EqualsBaseMethod;

        &lt;span class=&quot;Comment&quot;&gt;// Add a new Equals method to the class which this attribute has been added to&lt;/span&gt;

        [IntroduceMember(IsVirtual = &lt;span class=&quot;Constant&quot;&gt;true&lt;/span&gt;, OverrideAction = MemberOverrideAction.OverrideOrFail)]
        &lt;span class=&quot;Type&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;Statement&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;Type&quot;&gt;bool&lt;/span&gt; Equals(&lt;span class=&quot;Type&quot;&gt;object&lt;/span&gt; other)
        {
            &lt;span class=&quot;Comment&quot;&gt;// &lt;/span&gt;&lt;span class=&quot;Todo&quot;&gt;TODO&lt;/span&gt;&lt;span class=&quot;Comment&quot;&gt;: Define a smarter way to determine if base.Equals should be invoked.&lt;/span&gt;
            &lt;span class=&quot;Statement&quot;&gt;if&lt;/span&gt; (EqualsBaseMethod.Method.DeclaringType != &lt;span class=&quot;Statement&quot;&gt;typeof&lt;/span&gt;(&lt;span class=&quot;Type&quot;&gt;object&lt;/span&gt;))
            {
                &lt;span class=&quot;Statement&quot;&gt;if&lt;/span&gt; (!EqualsBaseMethod(other))
                    &lt;span class=&quot;Statement&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;Constant&quot;&gt;false&lt;/span&gt;;
            }

            &lt;span class=&quot;Type&quot;&gt;object&lt;/span&gt; instance = Instance;
            &lt;span class=&quot;Statement&quot;&gt;foreach&lt;/span&gt; (ILocationBinding binding &lt;span class=&quot;Statement&quot;&gt;in&lt;/span&gt; Fields)
            {
                &lt;span class=&quot;Comment&quot;&gt;// The following code is inefficient because it boxes all fields.&lt;/span&gt;
                &lt;span class=&quot;Comment&quot;&gt;// There is currently no workaround.&lt;/span&gt;
                &lt;span class=&quot;Type&quot;&gt;object&lt;/span&gt; thisFieldValue = binding.GetValue(&lt;span class=&quot;Statement&quot;&gt;ref&lt;/span&gt; instance, Arguments.Empty);
                &lt;span class=&quot;Type&quot;&gt;object&lt;/span&gt; otherFieldValue = binding.GetValue(&lt;span class=&quot;Statement&quot;&gt;ref&lt;/span&gt; other, Arguments.Empty);

                &lt;span class=&quot;Statement&quot;&gt;if&lt;/span&gt; (!&lt;span class=&quot;Type&quot;&gt;object&lt;/span&gt;.Equals(thisFieldValue, otherFieldValue))
                    &lt;span class=&quot;Statement&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;Constant&quot;&gt;false&lt;/span&gt;;
            }

            &lt;span class=&quot;Statement&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;Constant&quot;&gt;true&lt;/span&gt;;
        }

        &lt;span class=&quot;Comment&quot;&gt;////////////&lt;/span&gt;&lt;span class=&quot;Comment&quot;&gt;//&lt;/span&gt;
        &lt;span class=&quot;Comment&quot;&gt;// GetHashCode&lt;/span&gt;

        [IntroduceMember(IsVirtual = &lt;span class=&quot;Constant&quot;&gt;true&lt;/span&gt;, OverrideAction = MemberOverrideAction.OverrideOrFail)]
        &lt;span class=&quot;Type&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;Statement&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;Type&quot;&gt;int&lt;/span&gt; GetHashCode()
        {
            &lt;span class=&quot;Type&quot;&gt;object&lt;/span&gt; instance = Instance;

            &lt;span class=&quot;Statement&quot;&gt;unchecked&lt;/span&gt;
            {
                &lt;span class=&quot;Type&quot;&gt;int&lt;/span&gt; hashcode = &lt;span class=&quot;Constant&quot;&gt;11&lt;/span&gt;;
                &lt;span class=&quot;Statement&quot;&gt;foreach&lt;/span&gt; (ILocationBinding binding &lt;span class=&quot;Statement&quot;&gt;in&lt;/span&gt; Fields)
                {
                    &lt;span class=&quot;Type&quot;&gt;object&lt;/span&gt; thisFieldValue = binding.GetValue(&lt;span class=&quot;Statement&quot;&gt;ref&lt;/span&gt; instance, Arguments.Empty);
                    &lt;span class=&quot;Type&quot;&gt;int&lt;/span&gt; thisHash = thisFieldValue == &lt;span class=&quot;Constant&quot;&gt;null&lt;/span&gt; ? &lt;span class=&quot;Constant&quot;&gt;0&lt;/span&gt; : thisFieldValue.GetHashCode();
                    hashcode = (hashcode * &lt;span class=&quot;Constant&quot;&gt;397&lt;/span&gt;) ^ thisHash;
                }

                &lt;span class=&quot;Statement&quot;&gt;return&lt;/span&gt; hashcode;
            }
        }

        &lt;span class=&quot;Comment&quot;&gt;///&lt;/span&gt;&lt;span class=&quot;Comment&quot;&gt; &lt;/span&gt;&lt;span class=&quot;Identifier&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;Statement&quot;&gt;summary&lt;/span&gt;&lt;span class=&quot;Identifier&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;Comment&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;Comment&quot;&gt;///&lt;/span&gt;&lt;span class=&quot;Comment&quot;&gt; Provide advices for setting up &lt;/span&gt;&lt;span class=&quot;Identifier&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;Statement&quot;&gt;see&lt;/span&gt;&lt;span class=&quot;Identifier&quot;&gt; &lt;/span&gt;&lt;span class=&quot;Type&quot;&gt;cref&lt;/span&gt;=&lt;span class=&quot;Constant&quot;&gt;&amp;quot;Fields&amp;quot;&lt;/span&gt;&lt;span class=&quot;Identifier&quot;&gt;/&amp;gt;&lt;/span&gt;
&lt;span class=&quot;Comment&quot;&gt;        &lt;/span&gt;&lt;span class=&quot;Comment&quot;&gt;///&lt;/span&gt;&lt;span class=&quot;Comment&quot;&gt; &lt;/span&gt;&lt;span class=&quot;Identifier&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&quot;Statement&quot;&gt;summary&lt;/span&gt;&lt;span class=&quot;Identifier&quot;&gt;&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;Comment&quot;&gt;///&lt;/span&gt;&lt;span class=&quot;Comment&quot;&gt; &lt;/span&gt;&lt;span class=&quot;Identifier&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;Statement&quot;&gt;param&lt;/span&gt;&lt;span class=&quot;Identifier&quot;&gt; &lt;/span&gt;&lt;span class=&quot;Type&quot;&gt;name&lt;/span&gt;=&lt;span class=&quot;Constant&quot;&gt;&amp;quot;targetElement&amp;quot;&lt;/span&gt;&lt;span class=&quot;Identifier&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;Comment&quot;&gt;The type to which this attribute has been added&lt;/span&gt;&lt;span class=&quot;Identifier&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&quot;Statement&quot;&gt;param&lt;/span&gt;&lt;span class=&quot;Identifier&quot;&gt;&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;Comment&quot;&gt;///&lt;/span&gt;&lt;span class=&quot;Comment&quot;&gt; &lt;/span&gt;&lt;span class=&quot;Identifier&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;Statement&quot;&gt;returns&lt;/span&gt;&lt;span class=&quot;Identifier&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;Comment&quot;&gt;A sequence of advice instances&lt;/span&gt;&lt;span class=&quot;Identifier&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&quot;Statement&quot;&gt;returns&lt;/span&gt;&lt;span class=&quot;Identifier&quot;&gt;&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;Type&quot;&gt;public&lt;/span&gt; IEnumerable&amp;lt;AdviceInstance&amp;gt; ProvideAdvices(&lt;span class=&quot;Type&quot;&gt;object&lt;/span&gt; targetElement)
        {
            var targetType = (Type)targetElement;
            FieldInfo bindingField = GetType().GetField(&lt;span class=&quot;Constant&quot;&gt;&amp;quot;Fields&amp;quot;&lt;/span&gt;);

            &lt;span class=&quot;Type&quot;&gt;const&lt;/span&gt; BindingFlags bindingFlags =
                BindingFlags.DeclaredOnly |
                BindingFlags.Instance |
                BindingFlags.Public |
                BindingFlags.NonPublic;

            FieldInfo[] fields = targetType.GetFields(bindingFlags);

            &lt;span class=&quot;Statement&quot;&gt;foreach&lt;/span&gt; (FieldInfo field &lt;span class=&quot;Statement&quot;&gt;in&lt;/span&gt; fields)
            {
                &lt;span class=&quot;Statement&quot;&gt;yield&lt;/span&gt; &lt;span class=&quot;Statement&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;Statement&quot;&gt;new&lt;/span&gt; ImportLocationAdviceInstance(bindingField, &lt;span class=&quot;Statement&quot;&gt;new&lt;/span&gt; LocationInfo(field));
            }
        }
    }
}

&lt;/pre&gt;

&lt;p&gt;Then you can just add a &lt;tt&gt;[AutoEq]&lt;/tt&gt; attribute to your simple class and
get &lt;tt&gt;Equals&lt;/tt&gt; and &lt;tt&gt;GetHashCode&lt;/tt&gt; automatically implemented.&lt;/p&gt;

&lt;h2&gt;Other options&lt;/h2&gt;

&lt;h3&gt;T4 templates&lt;/h3&gt;

&lt;p&gt;While writing this article I read &lt;a href=&quot;http://stackoverflow.com/questions/13051000/&quot;&gt;this Stack
Overflow question&lt;/a&gt; that suggests doing the same thing by using &lt;a href=&quot;http://msdn.microsoft.com/en-us/library/bb126445.aspx&quot;&gt;T4 templates&lt;/a&gt;
(specifically the &lt;a href=&quot;https://t4ctorgenerator.codeplex.com/&quot;&gt;T4 C#
Constructor Generator project&lt;/a&gt;) to add this functionality via partial
classes.&lt;/p&gt;

&lt;h3&gt;Resharper&lt;/h3&gt;

&lt;p data-pullquote=&quot;All three options are worth knowing about and have their
strengths and weaknesses.&quot;&gt;&lt;/p&gt;

&lt;p&gt;In this particular example, perhaps the most pragmatic option is to use
Resharper’s menu option &lt;strong&gt;Resharper | Edit | Generate Code | Equality
Members&lt;/strong&gt;. You can just pick which fields and properties should
contribute, say whether you want to implement &lt;tt&gt;IEquatable&amp;lt;T&amp;gt;&lt;/tt&gt; as
well, and leave Resharper to generate the boilerplate code for you.&lt;/p&gt;

&lt;p&gt;All three options are worth knowing about and have their strengths and
weaknesses. Postsharp is good for adding cross-cutting aspects to your code,
logging being the canonical example. T4 templates look to be useful for
automatically generating textual content, including source code, but aren’t
widely used. Finally, the fact that Resharper could do the heavy lifting all
along is a reminder that it’s always worth investing the time to understand
the capabilities of the tools you already have before you start looking
further afield.&lt;/p&gt;

</description>
        <pubDate>Sun, 21 Sep 2014 21:14:00 +0100</pubDate>
        <link>http://matthewstrawbridge.com/content/2014/smarter-classes-less-code-part-2-2/</link>
        <guid isPermaLink="true">http://matthewstrawbridge.com/content/2014/smarter-classes-less-code-part-2-2/</guid>
        
        <category>C#</category>
        
        <category>design</category>
        
        <category>architecture</category>
        
        <category>PostSharp</category>
        
        <category>AOP</category>
        
        
        <category>C#</category>
        
        <category>design</category>
        
        <category>architecture</category>
        
        <category>PostSharp</category>
        
        <category>AOP</category>
        
      </item>
    
      <item>
        <title>Smarter Classes with Less Code (Part 1 of 2)</title>
        <description>&lt;p&gt;Last Saturday I went to the Developer Developer Developer conference in Cambridge (&lt;a href=&quot;http://www.dddeastanglia.com/&quot;&gt;DDD East Anglia&lt;/a&gt;), which was both very good and free to attend.&lt;/p&gt;

&lt;p&gt;As often happens at such conferences, it’s a throw-away remark or a discussion in the bar afterwards that has the most lasting impact rather than the subjects of the talks themselves.&lt;/p&gt;

&lt;p&gt;In this case it was the talk by &lt;a href=&quot;https://twitter.com/amelchiori&quot;&gt;Alessandro Melchiori&lt;/a&gt; entitled &lt;em&gt;Functional DDD&lt;/em&gt; that created the spark.&lt;/p&gt;

&lt;h2&gt;The traditional hand-coded approach&lt;/h2&gt;

&lt;p&gt;So, for example if you have some simple code like this, with an &lt;tt&gt;Item&lt;/tt&gt; class that has a couple of properties, then its &lt;tt&gt;ToString&lt;/tt&gt; isn’t very helpful:&lt;/p&gt;

&lt;style type=&quot;text/css&quot;&gt;
&lt;!--
pre { font-family: monospace; color: #c0c0c0; background-color: #000040; }
.Constant { color: #ffa0a0; }
.Statement { color: #ffff60; }
.Type { color: #60ff60; }
--&gt;
&lt;/style&gt;

&lt;pre id=&quot;vimCodeElement&quot;&gt;
&lt;span class=&quot;Statement&quot;&gt;using&lt;/span&gt; System;

&lt;span class=&quot;Type&quot;&gt;namespace&lt;/span&gt; DDD1
{
    &lt;span class=&quot;Type&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;Type&quot;&gt;class&lt;/span&gt; Item
    {
        &lt;span class=&quot;Type&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;Type&quot;&gt;int&lt;/span&gt; Id { get; set; }
        &lt;span class=&quot;Type&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;Type&quot;&gt;string&lt;/span&gt; Description { get; set; }
    }

    &lt;span class=&quot;Type&quot;&gt;class&lt;/span&gt; Program
    {
        &lt;span class=&quot;Type&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;Type&quot;&gt;void&lt;/span&gt; Main()
        {
            var item = &lt;span class=&quot;Statement&quot;&gt;new&lt;/span&gt; Item { Id = &lt;span class=&quot;Constant&quot;&gt;123&lt;/span&gt;, Description = &lt;span class=&quot;Constant&quot;&gt;&amp;quot;My item&amp;quot;&lt;/span&gt; };
            Console.WriteLine(item);
        }
    }
}
&lt;/pre&gt;

&lt;p&gt;The output you get is as follows:&lt;/p&gt;

&lt;pre&gt;
DDD1.Item
&lt;/pre&gt;

&lt;p&gt;So all you know is that you’re dealing with an &lt;tt&gt;Item&lt;/tt&gt;, but you know nothing about its contents.&lt;/p&gt;

&lt;p&gt;Traditionally you’d have to code up an override of &lt;tt&gt;ToString&lt;/tt&gt; that knows about the properties of &lt;tt&gt;Item&lt;/tt&gt;, something like this:&lt;/p&gt;

&lt;pre id=&quot;vimCodeElement&quot;&gt;
        &lt;span class=&quot;Type&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;Type&quot;&gt;override&lt;/span&gt; &lt;span class=&quot;Type&quot;&gt;string&lt;/span&gt; ToString()
        {
            &lt;span class=&quot;Statement&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;Type&quot;&gt;string&lt;/span&gt;.Format(&lt;span class=&quot;Constant&quot;&gt;&amp;quot;Item {&lt;span /&gt;{ Id = {0}, Description = {1} }&lt;span /&gt;}&amp;quot;&lt;/span&gt;, Id, Description);
        }
 &lt;/pre&gt;

&lt;p&gt;Which would give you this more useful output:&lt;/p&gt;

&lt;pre&gt;
Item { Id = 123, Description = My item }
&lt;/pre&gt;

&lt;h2&gt;The JSON trick&lt;/h2&gt;

&lt;p&gt;In his talk, Alessandro Melchiori happened to mention using the &lt;a href=&quot;http://james.newtonking.com/json&quot;&gt;Json.NET&lt;/a&gt; library’s &lt;a href=&quot;http://james.newtonking.com/json/help/index.html?topic=html/T_Newtonsoft_Json_JsonConvert.htm&quot;&gt;JsonConvert class&lt;/a&gt; as an easy way of implementing &lt;tt&gt;ToString&lt;/tt&gt; in your classes.&lt;/p&gt;

&lt;p&gt;You can install Json.NET into your solution in a few seconds using NuGet. Then it’s just a case of adding a &lt;tt&gt;using&lt;/tt&gt; statement and changing your &lt;tt&gt;ToString&lt;/tt&gt; to use &lt;tt&gt;JsonConvert.SerializeObject&lt;/tt&gt;:&lt;/p&gt;

&lt;pre id=&quot;vimCodeElement&quot;&gt;
&lt;span class=&quot;Statement&quot;&gt;using&lt;/span&gt; System;
&lt;span class=&quot;Statement&quot;&gt;using&lt;/span&gt; Newtonsoft.Json;

&lt;span class=&quot;Type&quot;&gt;namespace&lt;/span&gt; DDD1
{
    &lt;span class=&quot;Type&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;Type&quot;&gt;class&lt;/span&gt; Item
    {
        &lt;span class=&quot;Type&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;Type&quot;&gt;int&lt;/span&gt; Id { get; set; }
        &lt;span class=&quot;Type&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;Type&quot;&gt;string&lt;/span&gt; Description { get; set; }

        &lt;span class=&quot;Type&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;Type&quot;&gt;override&lt;/span&gt; &lt;span class=&quot;Type&quot;&gt;string&lt;/span&gt; ToString()
        {
            &lt;span class=&quot;Statement&quot;&gt;return&lt;/span&gt; JsonConvert.SerializeObject(&lt;span class=&quot;Statement&quot;&gt;this&lt;/span&gt;);
        }
    }

    &lt;span class=&quot;Type&quot;&gt;class&lt;/span&gt; Program
    {
        &lt;span class=&quot;Type&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;Type&quot;&gt;void&lt;/span&gt; Main()
        {
            var item = &lt;span class=&quot;Statement&quot;&gt;new&lt;/span&gt; Item { Id = &lt;span class=&quot;Constant&quot;&gt;123&lt;/span&gt;, Description = &lt;span class=&quot;Constant&quot;&gt;&amp;quot;My item&amp;quot;&lt;/span&gt; };
            Console.WriteLine(item);
        }
    }
}
&lt;/pre&gt;

&lt;p&gt;This gives the following output:&lt;/p&gt;

&lt;pre&gt;
{&quot;Id&quot;:123,&quot;Description&quot;:&quot;My item&quot;}
&lt;/pre&gt;

&lt;p&gt;Now, that might not seem like a big deal.&lt;/p&gt;

&lt;p&gt;The key point is that this one line of code inside the &lt;tt&gt;ToString&lt;/tt&gt; will work however many properties we’ve got in our class, and will automatically be kept in sync as we add or remove properties. For most types it can provide really useful output for minimal coding effort.&lt;/p&gt;

&lt;p&gt;Furthermore, if we stick that functionality in a base class then all you need to do to add this useful behaviour is derive from that class.&lt;/p&gt;

&lt;h2&gt;Adding a ValueBase class&lt;/h2&gt;

&lt;p&gt;If we’re creating a base class we can dispense with the Json.NET trick and use standard reflection to do clever things with our properties.&lt;/p&gt;

&lt;p&gt;The following example makes it easy to create immutable types (notice that &lt;tt&gt;Item&lt;/tt&gt;’s properties are read-only) by providing a generic constructor. It also includes a &lt;tt&gt;ToString&lt;/tt&gt; and some sensible default implementations of &lt;tt&gt;Equals&lt;/tt&gt; and &lt;tt&gt;GetHashCode&lt;/tt&gt; too.&lt;/p&gt;

&lt;pre id=&quot;vimCodeElement&quot;&gt;
&lt;span class=&quot;Statement&quot;&gt;using&lt;/span&gt; System;
&lt;span class=&quot;Statement&quot;&gt;using&lt;/span&gt; System.Linq;
&lt;span class=&quot;Statement&quot;&gt;using&lt;/span&gt; System.Reflection;
&lt;span class=&quot;Statement&quot;&gt;using&lt;/span&gt; System.Text;

&lt;span class=&quot;Type&quot;&gt;namespace&lt;/span&gt; DDD1
{
    &lt;span class=&quot;Comment&quot;&gt;///&lt;/span&gt;&lt;span class=&quot;Comment&quot;&gt; &lt;/span&gt;&lt;span class=&quot;Identifier&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;Statement&quot;&gt;summary&lt;/span&gt;&lt;span class=&quot;Identifier&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;Comment&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;Comment&quot;&gt;///&lt;/span&gt;&lt;span class=&quot;Comment&quot;&gt; Base class for simple classes with properties&lt;/span&gt;
&lt;span class=&quot;Comment&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;Comment&quot;&gt;///&lt;/span&gt;&lt;span class=&quot;Comment&quot;&gt; &lt;/span&gt;&lt;span class=&quot;Identifier&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&quot;Statement&quot;&gt;summary&lt;/span&gt;&lt;span class=&quot;Identifier&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;Type&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;Type&quot;&gt;abstract&lt;/span&gt; &lt;span class=&quot;Type&quot;&gt;class&lt;/span&gt; ValueBase
    {
        &lt;span class=&quot;Type&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;Type&quot;&gt;override&lt;/span&gt; &lt;span class=&quot;Type&quot;&gt;string&lt;/span&gt; ToString()
        {
            Type type = GetType();

            var result = &lt;span class=&quot;Statement&quot;&gt;new&lt;/span&gt; StringBuilder();
            result.AppendFormat(&lt;span class=&quot;Constant&quot;&gt;&amp;quot;{0}:&lt;/span&gt;&lt;span class=&quot;Special&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;Constant&quot;&gt;&amp;quot;&lt;/span&gt;, type.Name);

            var props = type.GetProperties();
            &lt;span class=&quot;Statement&quot;&gt;foreach&lt;/span&gt; (var prop &lt;span class=&quot;Statement&quot;&gt;in&lt;/span&gt; props)
            {
                result.AppendFormat(&lt;span class=&quot;Constant&quot;&gt;&amp;quot;    {0}: {1}&lt;/span&gt;&lt;span class=&quot;Special&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;Constant&quot;&gt;&amp;quot;&lt;/span&gt;, prop.Name, prop.GetValue(&lt;span class=&quot;Statement&quot;&gt;this&lt;/span&gt;));
            }

            &lt;span class=&quot;Statement&quot;&gt;return&lt;/span&gt; result.ToString();
        }

        &lt;span class=&quot;Type&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;Type&quot;&gt;override&lt;/span&gt; &lt;span class=&quot;Type&quot;&gt;bool&lt;/span&gt; Equals(&lt;span class=&quot;Type&quot;&gt;object&lt;/span&gt; other)
        {
            Type type = GetType();

            &lt;span class=&quot;Statement&quot;&gt;if&lt;/span&gt; (other == &lt;span class=&quot;Constant&quot;&gt;null&lt;/span&gt; || !type.IsInstanceOfType(other))
            {
                &lt;span class=&quot;Statement&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;Constant&quot;&gt;false&lt;/span&gt;;
            }

            &lt;span class=&quot;Statement&quot;&gt;foreach&lt;/span&gt; (var prop &lt;span class=&quot;Statement&quot;&gt;in&lt;/span&gt; type.GetProperties())
            {
                var thisProp = prop.GetValue(&lt;span class=&quot;Statement&quot;&gt;this&lt;/span&gt;);
                var thatProp = prop.GetValue(other);

                &lt;span class=&quot;Statement&quot;&gt;if&lt;/span&gt; (!thisProp.Equals(thatProp))
                {
                    &lt;span class=&quot;Statement&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;Constant&quot;&gt;false&lt;/span&gt;;
                }
            }

            &lt;span class=&quot;Statement&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;Constant&quot;&gt;true&lt;/span&gt;;
        }

        &lt;span class=&quot;Type&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;Type&quot;&gt;override&lt;/span&gt; &lt;span class=&quot;Type&quot;&gt;int&lt;/span&gt; GetHashCode()
        {
            Type type = GetType();
            var props = type.GetProperties();
            PropertyInfo firstProp = props.FirstOrDefault();
            &lt;span class=&quot;Statement&quot;&gt;return&lt;/span&gt; firstProp == &lt;span class=&quot;Constant&quot;&gt;null&lt;/span&gt; ? &lt;span class=&quot;Constant&quot;&gt;0&lt;/span&gt; : firstProp.GetValue(&lt;span class=&quot;Statement&quot;&gt;this&lt;/span&gt;).GetHashCode();
        }

        &lt;span class=&quot;Type&quot;&gt;protected&lt;/span&gt; ValueBase(&lt;span class=&quot;Type&quot;&gt;object&lt;/span&gt; properties)
        {
            Type type = GetType();
            var props = type.GetProperties();

            var otherProps = properties.GetType().GetProperties();

            &lt;span class=&quot;Statement&quot;&gt;foreach&lt;/span&gt; (PropertyInfo otherProp &lt;span class=&quot;Statement&quot;&gt;in&lt;/span&gt; otherProps)
            {
                props.Single(p =&amp;gt; p.Name.Equals(otherProp.Name)).SetMethod.Invoke(&lt;span class=&quot;Statement&quot;&gt;this&lt;/span&gt;, &lt;span class=&quot;Statement&quot;&gt;new&lt;/span&gt;[] { otherProp.GetValue(properties) });
            }
        }
    }

    &lt;span class=&quot;Type&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;Type&quot;&gt;class&lt;/span&gt; Item : ValueBase
    {
        &lt;span class=&quot;Type&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;Type&quot;&gt;int&lt;/span&gt; Id { get; &lt;span class=&quot;Type&quot;&gt;private&lt;/span&gt; set; }
        &lt;span class=&quot;Type&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;Type&quot;&gt;string&lt;/span&gt; Description { get; &lt;span class=&quot;Type&quot;&gt;private&lt;/span&gt; set; }

        &lt;span class=&quot;Type&quot;&gt;public&lt;/span&gt; Item(&lt;span class=&quot;Type&quot;&gt;object&lt;/span&gt; properties) : &lt;span class=&quot;Statement&quot;&gt;base&lt;/span&gt;(properties) { }
    }

    &lt;span class=&quot;Type&quot;&gt;class&lt;/span&gt; Program
    {
        &lt;span class=&quot;Type&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;Type&quot;&gt;void&lt;/span&gt; Main()
        {
            var item = &lt;span class=&quot;Statement&quot;&gt;new&lt;/span&gt; Item(&lt;span class=&quot;Statement&quot;&gt;new&lt;/span&gt; { Id = &lt;span class=&quot;Constant&quot;&gt;123&lt;/span&gt;, Description = &lt;span class=&quot;Constant&quot;&gt;&amp;quot;My item&amp;quot;&lt;/span&gt; });
            Console.WriteLine(item);
        }
    }
}
&lt;/pre&gt;

&lt;p&gt;Notice how simple the &lt;tt&gt;Item&lt;/tt&gt; class is. Just by deriving it from the base class we’ve got a nice immutable package of properties with useful &lt;tt&gt;ToString&lt;/tt&gt; output and sane equality comparisons (by property values rather than object references). If you had lots of small types like this then it would be easy to add this behaviour without having to write lots of boilerplate code each time.&lt;/p&gt;

&lt;p&gt;If this all sounds familiar, it’s the kind of thing you typically get for free in functional languages such as Haskell and F#. Once you create an object it’s safely immutable and equality is automatically based on its content rather than its location in memory. That’s often useful behaviour in C# too, but there are normally lots of hoops to jump through to get there.&lt;/p&gt;

&lt;p&gt;There are a couple of downsides to the &lt;tt&gt;ValueBase&lt;/tt&gt; approach, however. First, we’re using reflection to save us coding effort, so the resulting code will be slower than the custom version you’d normally have. Second, we still have to derive from a base class and hit problems because C# doesn’t allow multiple inheritance. We can overcome both issues by switching to a solution using aspect-oriented programming (AOP) with PostSharp. I’ll cover that in the next article.&lt;/p&gt;

</description>
        <pubDate>Sun, 21 Sep 2014 00:00:00 +0100</pubDate>
        <link>http://matthewstrawbridge.com/content/2014/smarter-classes-less-code-part-1-2/</link>
        <guid isPermaLink="true">http://matthewstrawbridge.com/content/2014/smarter-classes-less-code-part-1-2/</guid>
        
        <category>C#</category>
        
        <category>F#</category>
        
        <category>design</category>
        
        <category>architecture</category>
        
        
        <category>C#</category>
        
        <category>F#</category>
        
        <category>design</category>
        
        <category>architecture</category>
        
      </item>
    
  </channel>
</rss>
