<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">

 <title>Reasonable Deviations</title>
 <link href="https://reasonabledeviations.com/atom.xml" rel="self"/>
 <link href="https://reasonabledeviations.com/"/>
 <updated>2023-07-25T20:49:47+00:00</updated>
 <id>https://reasonabledeviations.com</id>
 <author>
   <name>Robert Andrew Martin</name>
   <email>martin.robertandrew@gmail.com</email>
 </author>

 
 <entry>
   <title>Man and machine: GPT for second brains</title>
   <link href="https://reasonabledeviations.com/2023/02/05/gpt-for-second-brain/"/>
   <updated>2023-02-05T00:00:00+00:00</updated>
   <id>https://reasonabledeviations.com/2023/02/05/gpt-for-second-brain</id>
   <content type="html">&lt;p&gt;In this post, I discuss how I used GPT embeddings to build a smart search tool for my second brain note-taking system.
&lt;!--more--&gt;&lt;/p&gt;

&lt;center&gt;
&lt;video width=&quot;60%&quot; controls=&quot;&quot;&gt;
  &lt;source src=&quot;/assets/images/nmr/nmr_demo.mp4&quot; type=&quot;video/mp4&quot; /&gt;

&lt;/video&gt;
 &lt;figcaption&gt;Try &lt;a href=&quot;https://twitter.com/robertmartin88/status/1622036336558358528?s=20&quot;&gt;this&lt;/a&gt; if the video isn't working for you&lt;/figcaption&gt;
&lt;/center&gt;

&lt;h2 id=&quot;introduction&quot;&gt;Introduction&lt;/h2&gt;

&lt;p&gt;I spend a lot of time thinking about how to improve my processes for learning and knowledge management; I’ve &lt;a href=&quot;/2022/04/18/molecular-notes-part-1/&quot;&gt;written&lt;/a&gt; &lt;a href=&quot;/2022/06/12/molecular-notes-part-2/&quot;&gt;extensively&lt;/a&gt; about my second-brain note-taking system, Molecular Notes.&lt;/p&gt;

&lt;p&gt;I use Molecular Notes on a daily basis but am still constantly on the lookout for improvements. As my collection of notes grows, I have recently been running into a retrieval issue: sometimes I’m trying to find an idea I know I’ve written about but I can’t remember the name of the concept or where I came across it.&lt;/p&gt;

&lt;p&gt;For example, let’s say I remember making some notes on failure modes in large organisations, but I can’t remember any specific names or the source. I can try searching for relevant keywords in Obsidian search, but this doesn’t help much. The problem is that Obsidian’s default search is &lt;em&gt;lexical&lt;/em&gt; – it tries to match words in the search query with words in the documents. It would be generous to call this “hit or miss”: as you can see below, when I search for “failure mode”, it matches anything with the string “mode”, including words like “model” which are not relevant. I could do a literal search using double quotes, but that only returns exact matches.&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/nmr/failure_mode_search.png&quot; style=&quot;width:30%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;(Molecular Notes, through its emphasis on linked notes, provides other means of finding content; in this case I could look at the local graph of notes connected to a relevant topic, like Management, and hopefully find what I’m looking for that way).&lt;/p&gt;

&lt;p&gt;I was in dire need of &lt;em&gt;semantic&lt;/em&gt; search, where the search tool understands the content of my second brain enough to return notes that are similar in meaning rather than just having the same words.&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/nmr/failure_modes.png&quot; style=&quot;width:80%;&quot; /&gt;
&lt;/center&gt;

&lt;h2 id=&quot;basics-of-gpt&quot;&gt;Basics of GPT&lt;/h2&gt;

&lt;p&gt;I don’t claim to have a good understanding of the internals of large language models. So here is my “black box” understanding of GPT, which emphasises the inputs and outputs rather than the internal function.&lt;/p&gt;

&lt;p&gt;GPT (which stands for “Generative Pre-trained Transformer”) is a Large Language Model (LLM) – a fancy deep learning model suited to a variety of language tasks. GPT-3 is the third iteration of GPT, with 10x the number of parameters of v2, and presumably some multiplier of the linguistic power.&lt;/p&gt;

&lt;p&gt;The main task of GPT-3 is text completion – feed it some input text and it tries to return the most appropriate output text. This is why we’ve seen amusing screenshots of GPT saying things that are completely wrong – it has no inbuilt truth-seeking, it’s only trying to make stuff sound right (in fairness, I don’t think humans are very different on this point).&lt;/p&gt;

&lt;p&gt;Here is an overview of the different tools OpenAI has built on top of GPT-3:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;ChatGPT: a variant of GPT-3 optimised for dialogue. It abstracts away the text completion aspect by making it feel like GPT is answering your questions, and wraps it in a nice UI.&lt;/li&gt;
  &lt;li&gt;Text completions API: feed in text programmatically and get back a completion. For example, check out &lt;a href=&quot;https://gpt3demo.com/apps/google-sheets&quot;&gt;this demo&lt;/a&gt; where the completions API  is used to fill in a spreadsheet based on the surrounding cells. You could use the text completions API to build your own version of ChatGPT.&lt;/li&gt;
  &lt;li&gt;DALL-E: an image generator based on GPT-3, which spits out images instead of text completions in response to some text prompt&lt;/li&gt;
  &lt;li&gt;Embeddings API: this is what I used in the project. It is less intuitive than the others, so I’ll explain more below.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;embeddings&quot;&gt;Embeddings&lt;/h2&gt;

&lt;p&gt;The core idea of embeddings is to represent a block of text as a point in space (specifically, a vector in high dimensional space).&lt;/p&gt;

&lt;p&gt;We mostly want to do this because once text has some spatial location, we can derive useful information based on its location. For example, we might be able to see that “dog” and “cat” are spatially closer than “dog” and “trumpet”.&lt;/p&gt;

&lt;p&gt;Also, vectors are easier to mathematically manipulate than blocks of text: adding and subtracting vectors is just a matter of following arrows. So once we’ve represented words as vectors, we can more rigorously say things like:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;king = queen + man&lt;/li&gt;
  &lt;li&gt;tiger = cat + stripes&lt;/li&gt;
  &lt;li&gt;slower - slow + fast = faster&lt;/li&gt;
&lt;/ul&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/nmr/word2vec.png&quot; style=&quot;width:80%;&quot; /&gt;
&lt;figcaption&gt;Samy Zafrany’s word2vec tutorial.&lt;/figcaption&gt;
&lt;/center&gt;

&lt;p&gt;None of this is particularly new – word2vec was published in 2013. But the hope is that with LLMs like GPT-3, the embeddings can become even more expressive, encoding paragraphs or documents rather than just words and identifying abstract relationships between text blocks.&lt;/p&gt;

&lt;h2 id=&quot;gpt-embeddings-for-semantic-search&quot;&gt;GPT Embeddings for semantic search&lt;/h2&gt;

&lt;p&gt;The core workflow for semantic search is very straightforward – it’s basically the “hello world” of GPT projects.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Take a bunch of text blocks and feed them to the OpenAI embeddings API. This returns a 1536-dimensional vector for each text block.&lt;/li&gt;
  &lt;li&gt;Likewise, embed the search query using the same API to get a query vector.&lt;/li&gt;
  &lt;li&gt;Find the most similar vectors among my notes. There are several options to compute the similarity between vectors, but most people default to cosine distance, which measures how closely two vectors align.&lt;/li&gt;
  &lt;li&gt;Return the top n notes by similarity.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here’s ChatGPT’s explanation, which is a bit more detailed.&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/nmr/gpt_embedding_explanation.png&quot; style=&quot;width:80%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;The main nuance is deciding what constitutes a text block. There were two main considerations here:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;The OpenAI embeddings API limits the token size to 8191 (~6000 words). So each text block has to be smaller than that.&lt;/li&gt;
  &lt;li&gt;What output do I want to be returned? It’s not super useful if the search points me to a large note as I’d still have to scroll through that to find what I’m after.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Fortunately, my long notes are written in a fairly structured way, separated by headings. So I just fed in each section as a text block.&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/nmr/chunking.png&quot; style=&quot;width:80%;&quot; /&gt;
&lt;/center&gt;

&lt;h2 id=&quot;technical-implementation&quot;&gt;Technical implementation&lt;/h2&gt;

&lt;p&gt;The code is on &lt;a href=&quot;https://github.com/robertmartin8/MolecularNotes/blob/master/_scripts/gpt_search.py&quot;&gt;GitHub&lt;/a&gt;, though the implementation is tied quite closely to my particular system and I don’t intend to put in any effort to generalise it.&lt;/p&gt;

&lt;h3 id=&quot;processing-input&quot;&gt;Processing input&lt;/h3&gt;

&lt;p&gt;Because Obsidian notes are flat files, it’s simple to write a python script to walk through the Obsidian vault to get all the notes into python. I asked ChatGPT to do it for me:&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/nmr/gpt_code_explanation.png&quot; style=&quot;width:80%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;The result of this step is a dictionary representation of my entire Obsidian vault, mapping the note name and chapter to the note text.&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;notes = {('Some Podcast', 'Episode 1'): 'text',
         ('Some Podcast', 'Episode 2'): 'other text',
         ('Some book', 'Chapter 1'): 'my notes'} 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;For optimal embedding performance,  I cleaned some of the text:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Removed some of the metadata I use in each note (e.g. author tags, metadata).&lt;/li&gt;
  &lt;li&gt;Remove newlines and special characters&lt;/li&gt;
  &lt;li&gt;Remove URLs and just keep the name of the link&lt;/li&gt;
  &lt;li&gt;Removed Obsidian backlinks (double square brackets).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I left mathematical notation untouched, because ideally I want the search engine to understand maths. It seems to have some understanding – when I query “expectation of a function versus the function of an expectation”, it does suggest Jensen’s inequality, even though my note on Jensen’s inequality doesn’t mention the word “expectation” except in mathematical terms. But I can’t be sure, perhaps it’s just picking up on the word “function”.&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/nmr/jensen.png&quot; style=&quot;width:60%;&quot; /&gt;
&lt;/center&gt;

&lt;h3 id=&quot;embeddings-1&quot;&gt;Embeddings&lt;/h3&gt;

&lt;p&gt;This is the easiest part of the project: OpenAI does the heavy lifting. The embedding function (which I took from one of their &lt;a href=&quot;https://github.com/openai/openai-cookbook/blob/main/examples/Embedding_long_inputs.ipynb&quot;&gt;cookbooks&lt;/a&gt;) takes in a text block and outputs a vector.&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;get_embedding&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;block&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;-&amp;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;float&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;openai&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Embedding&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
                    &lt;span class=&quot;nb&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;block&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;s&quot;&gt;&quot;text-embeding-ada-002&quot;&lt;/span&gt;
                &lt;span class=&quot;p&quot;&gt;)[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;data&quot;&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;s&quot;&gt;&quot;embedding&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;p&gt;I simply iterate through the dictionary of notes, applying the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;get_embedding&lt;/code&gt; function and storing the vectors in a pandas dataframe – this is almost certainly not the best structure but I’m familiar with them and the data is small enough where performance isn’t an issue.&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;embed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;notes&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;str&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;-&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;DataFrame&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# Embeds the notes into openAI and returns a dataframe containing the vectors.
&lt;/span&gt;    &lt;span class=&quot;n&quot;&gt;res&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;showfunc&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;lambda&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;n&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;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;n&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;mi&quot;&gt;0&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;s&quot;&gt; &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;n&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;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;s&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;n&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;&quot;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;click&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;progressbar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;notes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;items&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;item_show_func&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;showfunc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;note_items&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;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;note&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;section&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;text&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;note_items&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;block&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;section&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;. &quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;text&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;n&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;num_tokens_from_string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;block&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;# Truncate if too long
&lt;/span&gt;            &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;n&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;EMBEDDING_CTX_LENGTH&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;warnings&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;warn&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;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;note&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;section&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; exceeded token limit. Truncating.&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;block&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;truncate_text_tokens&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;block&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;embedding&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;get_embedding&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;block&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;except&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Exception&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;k&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;s&quot;&gt;&quot;Error for &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;note&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;section&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;continue&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;res&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;note&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;section&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;embedding&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sleep&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;0.1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;df&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;n&quot;&gt;DataFrame&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;res&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;df&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;I wrote a utility function to compute the cost of embedding a list of notes and requesting user confirmation before executing. Training on 1000+ notes only cost 7 cents!&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/nmr/build_preview.png&quot; style=&quot;width:70%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;I then used the same &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;get_embedding&lt;/code&gt; function to process the query string, before computing the cosine distance against the note embeddings in the dataframe:&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;qvec&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;get_embedding&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;query_string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;cos_sim&lt;/span&gt; &lt;span class=&quot;o&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;apply_along_axis&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;lambda&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;cosine_similarity&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;qvec&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;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;arr&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;df&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;results&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;n&quot;&gt;Series&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cos_sim&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;index&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;df&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;sort_values&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ascending&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;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Finally, I sorted the resulting notes by similarity and returned them.&lt;/p&gt;

&lt;h3 id=&quot;user-interface&quot;&gt;User interface&lt;/h3&gt;

&lt;p&gt;I built a CLI (command-line interface) using python’s &lt;a href=&quot;https://click.palletsprojects.com/en/8.1.x/&quot;&gt;click&lt;/a&gt; library. This allows for easy creation of user confirmation prompts, progress bars, and styling.&lt;/p&gt;

&lt;p&gt;Best of all, with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;click.launch(link)&lt;/code&gt; one can actually launch Obsidian, though it took some effort to get the link in the right format.&lt;/p&gt;

&lt;p&gt;Firstly, I installed the Advanced URI extension in Obsidian, which allows you to link to text blocks within notes rather than just the note itself (a URI is like a URL but can link to things other than web pages). I then reverse-engineered the format of the URI string, allowing me to convert my &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(note_title, chapter_title)&lt;/code&gt; pairs (which also keys the dataframe of embeddings vectors) into a proper URI. In the CLI, when I enter the note index, the URI is built and passed to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;click.launch&lt;/code&gt; which opens Obsidian.&lt;/p&gt;

&lt;h2 id=&quot;next-steps&quot;&gt;Next steps&lt;/h2&gt;

&lt;p&gt;There are several other ways I think GPT-3 can be used to augment my second brain experience. Here are some ideas, in descending order of importance.&lt;/p&gt;

&lt;h3 id=&quot;link-recommendation&quot;&gt;Link recommendation&lt;/h3&gt;

&lt;p&gt;I currently have an Obsidian extension that uses various distance metrics to list similar notes. This is based on the graph structure and backlinks: e.g. if note A links to note C, and note B links to note C, maybe A and B are similar.&lt;/p&gt;

&lt;p&gt;But perhaps the GPT embeddings can capture more nuance in meaning. I plan to write a small script to sort all &lt;em&gt;unlinked&lt;/em&gt; notes by GPT-similarity (for linked notes, I have already manually identified that there is a similarity so these should be excluded). I’m curious to see if there are any abstract relations I have missed: for example, maybe there is some idea from a particular event in Roman History that is a nice anecdote for a financial blowup in the 21st century.&lt;/p&gt;

&lt;h3 id=&quot;summarisation&quot;&gt;Summarisation&lt;/h3&gt;

&lt;p&gt;There are many projects out there using GPT-3’s text completions for Obsidian, for example, &lt;a href=&quot;https://www.notion.so/Man-and-machine-GPT-for-second-brains-4fd4b9505ce0458caf4c309df9c18c13&quot;&gt;obsidian-gpt&lt;/a&gt; allows users to generate text in Obsidian.&lt;/p&gt;

&lt;p&gt;I feel quite strongly that this is a bad idea for second brains – writing content in my own words is a crucial aspect of Molecular Notes. Though it’s possibly useful if you use Obsidian as part of your content creation workflow.&lt;/p&gt;

&lt;p&gt;Instead, I’m intrigued by the idea of using GPT-3 as a kind of “Jarvis”. Instead of entering queries to search, with GPT-3 I could make requests like:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Show me my notes on investment frameworks&lt;/li&gt;
  &lt;li&gt;Summarise the above notes&lt;/li&gt;
  &lt;li&gt;Highlight any disagreements between the notes&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;This was a surprisingly easy project and it’s certainly not because of my coding skills. OpenAI has done all the heavy lifting, exposing a huge amount of functionality with some simple API calls.&lt;/p&gt;

&lt;p&gt;I think there are some generalisable lessons: much of the effort involved getting my data into a suitable format for OpenAI to ingest and creating a user experience. I was lucky with the former because my data (small text blocks) is essentially the perfect input, whereas for some other projects people are trying to come up with clever ways of chunking up large pdf documents so they can be embedded. The UX side was also simple because I’m happy to use a plain CLI. I assume it’s a lot more effort to build a consumer-facing web application.&lt;/p&gt;

&lt;p&gt;This notwithstanding, I think the barrier to building a cool product has never been lower. I strongly encourage readers to examine their current processes to identify areas ripe for AI integration. I don’t want to be replaced by AI, I want to be enhanced by it!&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Molecular Notes: Practice</title>
   <link href="https://reasonabledeviations.com/2022/06/12/molecular-notes-part-2/"/>
   <updated>2022-06-12T00:00:00+00:00</updated>
   <id>https://reasonabledeviations.com/2022/06/12/molecular-notes-part-2</id>
   <content type="html">&lt;p&gt;In this Part 2, I discuss the practical implementation of Molecular Notes in Obsidian. I explain how I organise my Second Brain (Tags, Folders, Topics) and present detailed workflows for ingesting different types of content. I also explain how one can extend Obsidian, giving the example of Polymer – a spaced repetition application that I built on top of my Second Brain.&lt;/p&gt;

&lt;!--more--&gt;

&lt;p&gt;After reading this post, if you would like to clone my setup, head over to the Molecular Notes &lt;a href=&quot;https://github.com/robertmartin8/MolecularNotes&quot;&gt;GitHub repository&lt;/a&gt; and follow the instructions!&lt;/p&gt;

&lt;h2 id=&quot;contents&quot;&gt;Contents&lt;/h2&gt;

&lt;!-- TOC --&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;#choosing-software&quot;&gt;Choosing software&lt;/a&gt;
    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#notion&quot;&gt;Notion&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#remnote&quot;&gt;RemNote&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#roam-research&quot;&gt;Roam Research&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#vscode&quot;&gt;VSCode&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#onenoteevernote&quot;&gt;OneNote/Evernote&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#summary--choosing-software&quot;&gt;Summary – Choosing Software&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#organising-my-second-brain&quot;&gt;Organising my Second Brain&lt;/a&gt;
    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#tags&quot;&gt;Tags&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#folders&quot;&gt;Folders&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#topics&quot;&gt;Topics&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#authors&quot;&gt;Authors&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#learning-from-different-sources&quot;&gt;Learning from different sources&lt;/a&gt;
    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#books&quot;&gt;Books&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#textbooks-and-online-courses&quot;&gt;Textbooks and online courses&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#information-media&quot;&gt;Information media&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#podcasts&quot;&gt;Podcasts&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#journal-articles&quot;&gt;Journal articles&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#synthesis&quot;&gt;Synthesis&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#hacking-obsidian&quot;&gt;Hacking Obsidian&lt;/a&gt;
    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#third-party-extensions&quot;&gt;Third-party extensions&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#viewing-notes-from-a-text-editor&quot;&gt;Viewing notes from a text editor&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#python-helper-script&quot;&gt;Python helper script&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#polymer&quot;&gt;Polymer&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#areas-of-improvement&quot;&gt;Areas of improvement&lt;/a&gt;
    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#maps-of-content&quot;&gt;Maps of Content&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#integration-with-pdf-highlights&quot;&gt;Integration with PDF highlights&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#ongoing-events&quot;&gt;Ongoing events&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#conclusion&quot;&gt;Conclusion&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;!-- /TOC --&gt;

&lt;h2 id=&quot;choosing-software&quot;&gt;Choosing software&lt;/h2&gt;

&lt;p&gt;I split the post into &lt;a href=&quot;/2022/04/18/molecular-notes-part-1/&quot;&gt;Part 1&lt;/a&gt; and Part 2 (this post) to emphasise the difference between philosophy and implementation. Molecular Notes (as a note-taking philosophy) is not tied to any particular software tool. The only non-negotiable constraint is strong support for bidirectional links – when you link from note A to note B, note B should be aware that it has just been linked to.&lt;/p&gt;

&lt;p&gt;Depending on your workflows, you may have other requirements for an app. For me, some other minimum requirements are:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Flexibility: because I’ve spent a lot of time thinking about how I want to take notes, I need a system that allows me to structure notes how I see fit. Flexibility may not be desirable for everyone! Constraints can help you think in a structured way and avoid “blank page syndrome”.&lt;/li&gt;
  &lt;li&gt;Speed: when making notes, I jump around a &lt;em&gt;lot&lt;/em&gt; to explore connections (this is encouraged by Molecular Notes). I need a tool that doesn’t slow me down, otherwise I’ll get fed up.&lt;/li&gt;
  &lt;li&gt;Support for mathematical notation since many of my notes contain formulae.&lt;/li&gt;
  &lt;li&gt;Graph view: a convenient visual representation of a Second Brain.&lt;/li&gt;
  &lt;li&gt;Sparks joy! This is completely subjective – I want a tool whose interface gels with me. In my case, this means something lightweight and hackable.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I use Obsidian, which nails all of these requirements and offers many other great features. Obsidian is built for linked notes and is naturally suited to Second Brains. It is hackable, offering deep customisation and flexibility. Since it is essentially just a wrapper around simple text files, I don’t have to worry about being “locked-in” to Obsidian (in fact, I often interact with my notes from other apps). Obsidian works perfectly well offline, while still providing benefits like cloud-syncing.&lt;/p&gt;

&lt;p&gt;That said, I can completely understand that Obsidian may not be the right app for everyone. It has the same vibe as a programming environment / IDE, which is great for me, but perhaps annoying for non-programmers. In the rest of this section, I briefly comment on several other options for software (I’ve tried most of them). For readers who are already sold on Obsidian, feel free to skip ahead.&lt;/p&gt;

&lt;h3 id=&quot;notion&quot;&gt;Notion&lt;/h3&gt;

&lt;p&gt;I have written extensively about Notion &lt;a href=&quot;/2021/09/18/how-i-use-notion/&quot;&gt;elsewhere&lt;/a&gt;. I once used Notion to implement Zettelkasten – my first iteration of a Second Brain inspired by Sonke Ahrens&lt;em&gt;’ How to Take Smart Notes&lt;/em&gt; (my highlights &lt;a href=&quot;https://robertreads.notion.site/How-to-Take-Smart-Notes-73d4990369204dabb865543214980788&quot;&gt;here&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Notion is an extremely powerful tool – it’s certainly possible to implement Molecular Notes in Notion and I know several people who happily use it for their Second Brain. I am a heavy user of Notion for PKM (personal knowledge management), but I moved my Second Brain away from Notion for several reasons:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Notion is a swiss-army knife, not a precision tool. It offers a tonne of functionality but does each thing less well than a pure-play app. For example, Notion’s Kanban boards are great but probably not as good as Trello’s. Likewise, unlike Obsidian, Notion is clearly not designed specifically for linked note-taking.&lt;/li&gt;
  &lt;li&gt;Following on from the above, there are many nice-to-have Second Brain features like graph view that are absent from Notion. This is not Notion’s fault; it is a general-purpose PKM tool, not a Second Brain tool!&lt;/li&gt;
  &lt;li&gt;Historically Notion has had pretty poor offline support – a constant reminder that all of your notes are stored on Notion’s servers!&lt;/li&gt;
  &lt;li&gt;Slow: Notion feels slow for Second Brains. For example, if you start trying to create a backlink using “[[some note“, there is a noticeable delay before that note pops up. In Obsidian, it feels instantaneous.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;remnote&quot;&gt;RemNote&lt;/h3&gt;

&lt;p&gt;RemNote is a note-taking app that treats spaced-repetition as a first-class citizen, while also providing strong support for bidirectional linking. I used RemNote religiously during university and the spaced-repetition functionality was a game-changer – it allowed me to memorise content in the most efficient way possible.&lt;/p&gt;

&lt;p&gt;The main reason why I shifted to Obsidian for my Second Brain is that Obsidian is more hackable and extensible. With RemNote, there’s not much you can do to customise the interface and all the notes are stored on RemNote’s servers. Additionally, for my Second Brain, spaced repetition (at least in the way RemNote does it) isn’t terribly important.&lt;/p&gt;

&lt;h3 id=&quot;roam-research&quot;&gt;Roam Research&lt;/h3&gt;

&lt;p&gt;Roam has a credible claim to being the app that sparked the renaissance in Second Brains. It is heavily optimised for linked thinking and would be a great app for Molecular Notes.&lt;/p&gt;

&lt;p&gt;My main criticisms:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;It is not as flexible as Obsidian and feels a bit more prescriptive. For example, in Roam, every note consists of bullet points.&lt;/li&gt;
  &lt;li&gt;It is primarily web-based. I think this makes it harder to have a clean and focused note-taking experience. (EDIT: there is now a desktop app, which possibly nullifies this point).&lt;/li&gt;
  &lt;li&gt;All the notes are files on Roam’s servers.&lt;/li&gt;
  &lt;li&gt;There is no free plan. I have no qualms with paying for something as valuable as a Second Brain, but there are many excellent free options out there.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That said, there are several great things about Roam:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Arguably because it’s paid, you can have more faith that the company will stay around for a while and implement new features (though Obsidian’s team has proven to be highly capable in that respect).&lt;/li&gt;
  &lt;li&gt;Excellent graph view.&lt;/li&gt;
  &lt;li&gt;Links to blocks rather than just to notes. In Molecular Notes, this doesn’t matter much, but for other systems it’s very useful to be able to link to specific paragraphs.&lt;/li&gt;
  &lt;li&gt;Unlinked references: Roam probably beats Obsidian on this particular point – with one click you can turn all unlinked references into proper backlinks.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;vscode&quot;&gt;VSCode&lt;/h3&gt;

&lt;p&gt;VSCode is an IDE (integrated development environment) that I use for most of my software development. I often use VSCode to interact with my Obsidian notes (e.g. when I want to do special find-and-replace operations across the entire database).&lt;/p&gt;

&lt;p&gt;However, it turns out that there are VSCode extensions (like &lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=foam.foam-vscode&quot;&gt;Foam&lt;/a&gt; and &lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=dendron.dendron&quot;&gt;Dendron&lt;/a&gt;) that essentially turn VSCode into an Obsidian clone. I use Dendron at work because I don’t have access to Obsidian behind the company firewall.&lt;/p&gt;

&lt;p&gt;These apps are quite similar to Obsidian but the UX is slightly less polished because of the IDE’s constraints. The learning curve was a lot steeper since the target audience for these apps are software developers (not many “normies” have VSCode installed!). Unless you are obsessed with VSCode (not unreasonable!), I think Obsidian offers a better note-taking experience.&lt;/p&gt;

&lt;h3 id=&quot;onenoteevernote&quot;&gt;OneNote/Evernote&lt;/h3&gt;

&lt;p&gt;Avoid! The difficulty of linking internal notes means that these apps &lt;em&gt;cannot&lt;/em&gt; be used as effective Second Brains. I don’t think they even support bi-directional links!&lt;/p&gt;

&lt;p&gt;Evernote was the first note-taking app I used (from 2015-2019), so I did have some sentimental attachment to it. But ultimately they failed to innovate and were left in the dust by competitors like Notion (there are many great &lt;a href=&quot;https://thegrowthplaybook.substack.com/p/the-rise-and-fall-of-evernote?s=r&quot;&gt;articles&lt;/a&gt; on the failure of Evernote).&lt;/p&gt;

&lt;p&gt;OneNote is actually rather good for general note-taking but &lt;em&gt;not&lt;/em&gt; for Second Brains. Microsoft seems to be aware of this and is launching a product called &lt;a href=&quot;https://www.microsoft.com/en-us/microsoft-loop?ms.url=microsoftcommicrosoft-loop&quot;&gt;Loop&lt;/a&gt;, which looks like a Notion clone and should therefore work as a Second Brain (presumably with all the problems of Notion).&lt;/p&gt;

&lt;h3 id=&quot;summary--choosing-software&quot;&gt;Summary – Choosing Software&lt;/h3&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;Broke: Notion
Woke: Roam
Bespoke: Obsidian
Baroque: Evernote/OneNote
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;All told, you need to pick a note-taking app that gels with your philosophy. The core tenets outlined on Obsidian’s &lt;a href=&quot;https://obsidian.md/&quot;&gt;website&lt;/a&gt; are exactly what I want:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Linked thinking as the primary objective&lt;/li&gt;
  &lt;li&gt;Lightweight and extensible&lt;/li&gt;
  &lt;li&gt;Local markdown notes&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;organising-my-second-brain&quot;&gt;Organising my Second Brain&lt;/h2&gt;

&lt;p&gt;People who have tried to get started with note-taking (either digitally or with pen and paper) will quickly appreciate the pain of “organisation paralysis”: it can be hard to decide how to use tags, folders, categories etc, and you often end up with a convoluted system that is impossible to maintain.&lt;/p&gt;

&lt;p&gt;As I argued in &lt;a href=&quot;/2022/04/18/molecular-notes-part-1/&quot;&gt;Part 1&lt;/a&gt;, this doesn’t mean that we should avoid categorisation! It just means that we should focus on a simple and flexible structure.&lt;/p&gt;

&lt;h3 id=&quot;tags&quot;&gt;Tags&lt;/h3&gt;

&lt;p&gt;I use tags to encode the &lt;em&gt;type&lt;/em&gt; of a note. That is, a tag should represent “is-a” relationships: this note “is-a” book note, this “is a” Molecule etc.&lt;/p&gt;

&lt;p&gt;Here is a list of my most commonly used tags:&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/secondbrain2/taglist.png&quot; style=&quot;width:45%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;Obviously, we have the main Molecular Notes primitives: Atom, Molecule, Topic, and Sources (please refer back to &lt;a href=&quot;https://reasonabledeviations.com/2022/04/18/molecular-notes-part-1/#molecular-notes&quot;&gt;Part 1&lt;/a&gt; for a refresher).&lt;/p&gt;

&lt;p&gt;All of my Source notes have an additional tag denoting what type of Source it is, e.g. a book or an article. Most of my Atoms have an additional tag denoting the type of Atom, for example it could be a tool, historical event, formula, etc. In the list above you may be surprised to see “cognitive-bias” as a tag – arguably this should be a Topic instead. But so many of my Atoms are cognitive biases that I think it is a suitable tag. I bring this up to remind the reader that “practicality beats purity” – feel free to modify the system to suit your needs!&lt;/p&gt;

&lt;p&gt;Tags make it very easy to search through notes. For example, I can quickly find all books that mention Kelly Betting using ⌘⇧F.&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/secondbrain2/tagsearch.png&quot; style=&quot;width:80%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;Additionally, Obsidian has a cool feature that allows you to colour graph nodes by tag:&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/secondbrain2/graph.png&quot; style=&quot;width:80%;&quot; /&gt;
&lt;/center&gt;

&lt;h3 id=&quot;folders&quot;&gt;Folders&lt;/h3&gt;

&lt;p&gt;Within my main Obsidian vault, I use a simple folder structure (there is no further nesting):&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/secondbrain2/folder_structure.png&quot; style=&quot;width:80%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;The folders with an underscore in front of them are “system” folders that are used by Obsidian – they don’t contain my notes.&lt;/p&gt;

&lt;p&gt;All Atoms go in the main Obsidian vault, not in subfolders. This decision was based on the observation that opening Atoms is probably the action I do most often, so I should reduce the number of clicks as much as possible.&lt;/p&gt;

&lt;p&gt;I use folders to house specific types of notes: Authors, Molecules, Sources, and Topics. A reasonable question: doesn’t this create duplication between folders and tags? Why bother creating a folder where everything inside it is tagged a certain way? The answer is that folders speed up navigation; they are great for “skimming” your Second Brain. For example, I often want to skim through a list of my Molecules. While I could do this using a search for “tag:#molecule”, this is such a common task that it’s more efficient to be able to simply click “Molecules” in Obsidian’s file explorer and be able to quickly look through:&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/secondbrain2/molecule_explorer.png&quot; style=&quot;width:80%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;The downside of this approach is that I have to remember to move a new note to the correct folder. I wrote a python script to do this for me (i.e. move all notes tagged “#molecule” to the Molecules folder), but even without that, it can be done quickly using the command palette (⌘P “move” ⏎) or you can set a custom shortcut.&lt;/p&gt;

&lt;h3 id=&quot;topics&quot;&gt;Topics&lt;/h3&gt;

&lt;p&gt;Topics are the main “semantic” source of structure (tags and folders are more organisational). They are a powerful tool in helping me to identify connections across domains:&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/secondbrain2/graph_topic.png&quot; style=&quot;width:80%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;I don’t have a set procedure to decide when something should be given an explicit Topic. As a very rough rule of thumb, I make something a Topic if I think more than ~5-10 notes will link to it. Some of my most-used Topics are Finance, Statistics, Philosophy, Volatility (in the financial sense), and Management (notes related to running businesses).&lt;/p&gt;

&lt;p&gt;Volatility is an interesting example because it is actually a &lt;em&gt;Subtopic&lt;/em&gt; of Finance, in the sense that any note related to the Volatility Topic should also be related to the Finance Topic conceptually. But in these cases, I will only link to the Subtopic. Hence if I am writing a new Atom about volatility (e.g. some new options concept), even though it is indeed related to Finance, given that it is only related to Finance via its relation to Volatility, I will only link it to the Volatility Subtopic. This results in a clearer graph structure.&lt;/p&gt;

&lt;p&gt;Don’t worry too much about deciding the best Topics. I often restructure and rename the Topics – this iterative procedure helps me remain abreast of the contents of my Second Brain.&lt;/p&gt;

&lt;h3 id=&quot;authors&quot;&gt;Authors&lt;/h3&gt;

&lt;p&gt;At the top of all my Source notes, I write down the author. But rather than doing this as plain text, I make an empty Author note (containing only &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Type: #author&lt;/code&gt;) and link to that.&lt;/p&gt;

&lt;p&gt;The advantages of this approach are threefold:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;The Authors show up on the graph.&lt;/li&gt;
  &lt;li&gt;Because the Authors are notes tagged &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#author&lt;/code&gt;, I can give them a different colour on the graph.&lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;If I ever need to note down information about the author, I can easily do so on the Author page.&lt;/p&gt;

    &lt;center&gt;
  &lt;img src=&quot;/assets/images/secondbrain2/andrew_lo.png&quot; style=&quot;width:80%;&quot; /&gt;
  &lt;/center&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However, these advantages only materialise when there are several Sources from the same Author in your Second Brain (or you reference the Author in Atoms/Molecules). If this is unlikely to be the case, e.g. if I’ve read a one-off blog post from some random author, I will leave the author as plain text.&lt;/p&gt;

&lt;p&gt;One downside of making placeholder Author notes is that one ends up having to create a lot of notes which then need to be filed away into folders (at least according to my organisation principles). I have written a python script that does this all for me – all I have to do is make an unlinked reference e.g. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;author: [[Firstname Lastname]]&lt;/code&gt; then my helper script will create a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Firstname Lastname.md&lt;/code&gt; note and move it to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Authors/&lt;/code&gt;.&lt;/p&gt;

&lt;h2 id=&quot;learning-from-different-sources&quot;&gt;Learning from different sources&lt;/h2&gt;

&lt;p&gt;As I explained in &lt;a href=&quot;https://reasonabledeviations.com/2022/04/18/molecular-notes-part-1/#objectives&quot;&gt;Part 1&lt;/a&gt;, a key goal of Molecular Notes is to help me learn from a variety of resources: books, textbooks, podcasts, YouTube videos etc. In this section, I explain how I make Source notes for different media types.&lt;/p&gt;

&lt;p&gt;Before that, let me review some key principles:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Avoid duplication:
    &lt;ul&gt;
      &lt;li&gt;If two Sources are explaining the same concept, that concept should be extracted into an Atom and linked to by both Sources.&lt;/li&gt;
      &lt;li&gt;But if each Source provides different &lt;em&gt;commentary&lt;/em&gt; on the same concept, I can and should make a note of both.&lt;/li&gt;
      &lt;li&gt;Before making notes on a Source, I first pull up similar Sources so I can get a better grip on what’s already in my Second Brain. Often, I end up restructuring my old notes at the same time as making new notes!&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Review-oriented structure:
    &lt;ul&gt;
      &lt;li&gt;Notes should be hard to write but easy to review – put in the effort to rearrange things until the structure makes sense!&lt;/li&gt;
      &lt;li&gt;I’m a big fan of using nested bullets because they let me look at my content on different levels of abstraction, allowing for efficient review.  When I’m looking over my Source notes later, I can scan the headings and the top-level bullets to find what I’m looking for (or maybe I’m just browsing), before diving into a particular point in more detail if needed.&lt;/li&gt;
      &lt;li&gt;Contrast this with a paragraph of text, where I essentially have to read the whole paragraph to figure out what the paragraph is discussing. Converting textbook prose to nested bullets isn’t always easy, but having to re-arrange concepts helps me internalise the material.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Naming conventions:
    &lt;ul&gt;
      &lt;li&gt;I’m not at all consistent with how I name Source notes – sometimes I use the original title, sometimes I append the author e.g. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Complexity, Mitchell&lt;/code&gt;. For journal articles, I will either use the title or the reference, e.g. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Avellaneda and Stoikov (2008)&lt;/code&gt;.&lt;/li&gt;
      &lt;li&gt;How do I decide? I name the note whatever I would like to reference it as. This is &lt;em&gt;my&lt;/em&gt; Second Brain, so I let the note name reflect whatever symbol I have in my mind.&lt;/li&gt;
      &lt;li&gt;I generally avoid abbreviations because the marginal effort of linking a long name is tiny due to autocomplete. Again, there are some exceptions to this – if the abbreviation is strongly embedded in my mind (e.g. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GEB&lt;/code&gt; for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Godel, Escher, Bach: an Eternal Golden Braid&lt;/code&gt;), I will just use the acronym!&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;books&quot;&gt;Books&lt;/h3&gt;

&lt;p&gt;I have discussed at length how I consume books in a &lt;a href=&quot;/2022/01/24/reading-philosophy/&quot;&gt;previous post&lt;/a&gt;. For completeness, here is a summary:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;My book review and highlights are contained in Notion (they can be viewed &lt;a href=&quot;https://robertreads.notion.site/&quot;&gt;here&lt;/a&gt;).&lt;/li&gt;
  &lt;li&gt;I then copy the key highlights into a Source note.&lt;/li&gt;
  &lt;li&gt;I extract “known” concepts into Atoms and crystallised pieces of insight into Molecules.&lt;/li&gt;
  &lt;li&gt;I look through other notes from the same Topic (using the local graph) to find potential connections.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;textbooks-and-online-courses&quot;&gt;Textbooks and online courses&lt;/h3&gt;

&lt;p&gt;As discussed in &lt;a href=&quot;/2022/04/18/molecular-notes-part-1/&quot;&gt;Part 1&lt;/a&gt;, Sources are composed of concepts, context, and a thesis. The key distinction between books and textbooks (one can find/replace “textbook” with “online course” in this section since the principles are identical) is that textbooks focus far more on concepts and facts, whereas non-fiction books contain a core thesis and supporting examples. Thus I expect a textbook to result in far more Atoms than a non-fiction book.&lt;/p&gt;

&lt;p&gt;Here are some guidelines when making notes on textbooks:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Consume the content in at least two passes:
    &lt;ul&gt;
      &lt;li&gt;I normally read over a textbook (or watch lecture videos) once on 2x speed just to get an idea of what ideas it contains, before making proper notes on a second pass.&lt;/li&gt;
      &lt;li&gt;This helps me have a sense of what’s important – or indeed if the textbook is worth reading!&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Mimic structure:
    &lt;ul&gt;
      &lt;li&gt;I generally follow the structure of the textbook because it makes it much easier to refer back to the original textbook when I need additional details.&lt;/li&gt;
      &lt;li&gt;Obsidian lets you link to headings, so in other notes, I could refer to &lt;em&gt;Volatility Trading Chapter 2&lt;/em&gt; with a proper backlink (see the Obsidian &lt;a href=&quot;https://help.obsidian.md/How+to/Internal+link&quot;&gt;docs&lt;/a&gt; for an explanation)&lt;/li&gt;
      &lt;li&gt;
        &lt;p&gt;But one must be careful not to fall into the trap of completionism: just because we are following the textbook’s structure &lt;em&gt;does not&lt;/em&gt; mean that we need to take detailed notes on every chapter! I sometimes leave chapters as just a heading if the ideas aren’t immediately relevant to me.&lt;/p&gt;

        &lt;center&gt;
  &lt;img src=&quot;/assets/images/secondbrain2/vol_trading.png&quot; style=&quot;width:80%;&quot; /&gt;
  &lt;/center&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Utility over comprehensiveness:
    &lt;ul&gt;
      &lt;li&gt;We don’t need to re-explain everything in the book! In fact, we should think of Source notes as a collection of pointers. The note should just tell you what is in the Source.&lt;/li&gt;
      &lt;li&gt;Unless you expect a concept or idea to be particularly important for your thinking (in which case you should extract it into an Atom or Molecule), feel free to write nothing more than a minimal phrase to remind you of its existence.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Probably the most differentiated aspect of my textbook workflow (compared to typical textbook note-taking) is that only 70% of the time spent making textbook notes is spent on that particular textbook. The other 30% is spent extracting relevant Atoms from other textbooks. For example, I was recently making notes on Ben Lambert’s excellent course on undergraduate econometrics. I had previously read a textbook called &lt;em&gt;Forecasting – Principles and Practice&lt;/em&gt; that had a lot of overlap, especially on the subject of time series. So I kept this open in the right pane, and whenever there was a concept that was discussed both by Lambert and &lt;em&gt;Forecasting&lt;/em&gt;, I would extract it into an Atom.&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/secondbrain2/lambert.png&quot; style=&quot;width:100%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;With reference to the above image, previously my Source notes in &lt;em&gt;Chapter 9 – ARIMA models&lt;/em&gt; looked quite similar to &lt;em&gt;Chapter 10 – Dynamic regression models&lt;/em&gt;: lots of text with few links. But as I was making notes on Lambert’s course, I progressively extracted Atoms like “AR model”, “MA model”, “ARMA model” – though note that there is still some Source-specific commentary under in &lt;em&gt;Chapter 9 – ARIMA models&lt;/em&gt;, like that within the “Stationary time series” bullet.&lt;/p&gt;

&lt;p&gt;This process leaves me with a far better understanding of the concepts because I have to:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;View the same concept in different contexts&lt;/li&gt;
  &lt;li&gt;Merge two explanations for the same concept&lt;/li&gt;
  &lt;li&gt;Reconcile different notation to strike at the underlying idea&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;information-media&quot;&gt;Information media&lt;/h3&gt;

&lt;p&gt;I generalise blog posts, YouTube videos, Twitter threads etc to “information media” (infomedia).&lt;/p&gt;

&lt;p&gt;The main difference between these types of infomedia and books/textbooks is that they typically focus on a much narrower range of concepts – often just one!&lt;/p&gt;

&lt;p&gt;In these cases, I will often directly create an Atom to explain the concept (in my own words of course) then link to the infomedia directly (not to a Source note of it). In the example below, I read a Farnam Street &lt;a href=&quot;https://fs.blog/chestertons-fence/&quot;&gt;post&lt;/a&gt; about Chesterton’s fence and made an Atom for it. I didn’t create a Source note for the blog post.&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/secondbrain2/chesterton.png&quot; style=&quot;width:80%;&quot; /&gt;
&lt;/center&gt;

&lt;h3 id=&quot;podcasts&quot;&gt;Podcasts&lt;/h3&gt;

&lt;p&gt;Podcasts have been an incredibly important learning vector for me. They are an excellent way to add incremental learning to my day – as a rule of thumb, I listen to podcasts any time my body is in use but my brain is not: when I’m at the gym, walking from A to B during a commute, cooking and cleaning, etc.&lt;/p&gt;

&lt;p&gt;There are several types of podcasts, each of which I treat differently (a list of the podcasts I consume can be found &lt;a href=&quot;https://reasonabledeviations.com/about/#information-diet&quot;&gt;here&lt;/a&gt;).&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;General news and current affairs (e.g. &lt;em&gt;All-In&lt;/em&gt;): I don’t make notes on these – I listen to them in the gym and they are primarily for entertainment.&lt;/li&gt;
  &lt;li&gt;General interviews, e.g. &lt;em&gt;Invest Like the Best&lt;/em&gt;, &lt;em&gt;Masters in Business&lt;/em&gt;: I occasionally take notes if there is an insight that strongly resonates with me.&lt;/li&gt;
  &lt;li&gt;Evergreen interviews, e.g. &lt;em&gt;Flirting With Models&lt;/em&gt;: these are podcasts that have been carefully designed to be dense in evergreen information. I make notes on these as if they are online courses (i.e textbook-equivalent).&lt;/li&gt;
  &lt;li&gt;“Long-form”, e.g. the &lt;em&gt;History of Rome, Huberman Lab&lt;/em&gt;: as above, I treat these as online courses.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I use an app called Airr that allows me to make audio highlights; when I hear something interesting, I press a button to record an AirrQuote and if convenient I make a text note (these are optional).&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/secondbrain2/fwm.png&quot; style=&quot;width:45%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;At certain intervals (e.g. every few episodes, or every season if I’ve gotten lazy) I compile all the highlights into Source notes – either one note per podcast or one note per podcast season depending on the information density.&lt;/p&gt;

&lt;h3 id=&quot;journal-articles&quot;&gt;Journal articles&lt;/h3&gt;

&lt;p&gt;I first want to caveat by saying that I am not an academic, so my workflow for journal articles is primarily from the perspective of a “keen amateur” – I generally care more about learning techniques from the articles, as opposed to citing and building on top of them.&lt;/p&gt;

&lt;p&gt;My Source notes for journal articles are most similar to my Source notes for books: they are often very short, highlighting just the key ideas that are relevant to my thinking.&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/secondbrain2/journal_article.png&quot; style=&quot;width:80%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;I name the note whatever feels right – sometimes that’s the formal citation, sometimes it’s the name of the paper (if it’s particularly memorable). In the front matter for the note, I give the formal citation as a link to Zotero, which I use to store and arrange the PDFs of the papers.&lt;/p&gt;

&lt;h2 id=&quot;synthesis&quot;&gt;Synthesis&lt;/h2&gt;

&lt;p&gt;Synthesis is one of the important parts of a Second Brain, but it is also the aspect I am least able to convey in a blog post. While I’ve built a system that aids synthesis, the truth is that it’s a process that must occur in one’s (primary) brain.&lt;/p&gt;

&lt;p&gt;My approach isn’t to sit down and say “right, it’s time to be creative”. Most often, the process starts while I’m making notes on a particular Source: perhaps the author discusses a concept that feels vaguely familiar or has a similar flavour to something I’ve come across before. I then gently poke around my Second Brain to try and see if there’s a deeper link.&lt;/p&gt;

&lt;p&gt;The local graph is useful here because I have configured it to show both first-degree and second-degree neighbours. The second-degree neighbours are often a fertile hunting ground for connections. In the example below, the “Goal preservation” Atom links to the AI Topic. The local graph shows not just the neighbours of “Goal preservation”, but the neighbours of “AI” too. I can then skim these to see if there are additional links to “Goal preservation”&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/secondbrain2/goal_preservation.png&quot; style=&quot;width:100%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;Occasionally, I do put on my “linking cap” – I set aside some time to go through my Second Brain to remind myself what’s in there and to specifically try and make connections between Atoms via new Molecules.&lt;/p&gt;

&lt;h2 id=&quot;hacking-obsidian&quot;&gt;Hacking Obsidian&lt;/h2&gt;

&lt;p&gt;In this section, I discuss how one can “hack” Obsidian. This section is entirely optional; it is mainly here for the benefit of the advanced users and geeks (you know who you are!) who want to push the boundaries of Obsidian.&lt;/p&gt;

&lt;p&gt;In essence, because Obsidian is made of plain markdown files and a simple folder structure all stored locally, it’s very easy to interact with your Obsidian notes via a different interface. Here are some examples.&lt;/p&gt;

&lt;h3 id=&quot;third-party-extensions&quot;&gt;Third-party extensions&lt;/h3&gt;

&lt;p&gt;There are a tonne of third-party extensions available for Obsidian which require very little effort to set up. For example, the Mind Map extension lets you view your notes as a mindmap (provided you have structured them properly, using nested bullets):&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/secondbrain2/mindmap.png&quot; style=&quot;width:100%;&quot; /&gt;
&lt;/center&gt;

&lt;h3 id=&quot;viewing-notes-from-a-text-editor&quot;&gt;Viewing notes from a text editor&lt;/h3&gt;

&lt;p&gt;Notes are just plain text in folders, which means you can interact with them via your file system. For example, on macOS, this means that your Obsidian notes show up in Spotlight search, which can be quite useful. It also means that you can reorganise your notes in Finder or File Explorer.&lt;/p&gt;

&lt;p&gt;Slightly more advanced: I often interact with my Obsidian vault via VSCode, a programming environment.&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/secondbrain2/vscode.png&quot; style=&quot;width:80%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;Backlinks don’t work here, but there are several reasons why this is useful:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;You can use VSCode’s powerful text editing functionality. For instance, in Obsidian there isn’t an easy way to do a global find and replace across all notes, but this is trivial in VSCode (plus you can do regex find/replace rather than just plaintext).&lt;/li&gt;
  &lt;li&gt;Better support for git backups. Obsidian does have a git extension, but in VSCode you can look at diffs and see exactly what is being changed.&lt;/li&gt;
  &lt;li&gt;More convenient to write python scripts that interact with my Second Brain (see below).&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;python-helper-script&quot;&gt;Python helper script&lt;/h3&gt;

&lt;p&gt;As I’ve mentioned several times in this post, there are a couple of “routine tasks” that I’ve decided to automate using python. My helper script does the following:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Moves files with a given tag into the appropriate folder (e.g. Authors, Molecules, Sources)&lt;/li&gt;
  &lt;li&gt;Creates Author pages: if I’m making a new Source note and write &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;author: [[New Author]]&lt;/code&gt;, it doesn’t actually create a page for the author (unless I click on the backlink). My script looks through these linked-but-not-created pages and makes an Author page (empty apart from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;type: #author&lt;/code&gt;).&lt;/li&gt;
  &lt;li&gt;Creates Topic pages: same as for Authors.&lt;/li&gt;
  &lt;li&gt;Makes a list of notes that I need to review:
    &lt;ul&gt;
      &lt;li&gt;Notes that I’ve tagged &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#todo&lt;/code&gt;.&lt;/li&gt;
      &lt;li&gt;Orphan notes that aren’t linked to by anything and don’t link to anything.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I then set up an alias where if I run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;obsidian&lt;/code&gt; in terminal, it cleans up my library and gives me a report:&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/secondbrain2/command_line.png&quot; style=&quot;width:80%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;The code to do all this is remarkably simple – check out the Molecular Notes &lt;a href=&quot;https://github.com/robertmartin8/MolecularNotes&quot;&gt;GitHub repo&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;polymer&quot;&gt;Polymer&lt;/h3&gt;

&lt;p&gt;One can also build more advanced applications. I built a spaced repetition (flashcard) tool called Polymer to help me review my notes in a different way.&lt;/p&gt;

&lt;p&gt;Like other flashcard tools, Polymer flashes the title of an Atom or Molecule and I try to recall the contents. I then press “Show” to check my understanding, before clicking one of the Difficulty buttons to tell Polymer how hard I found it to recall the note. It then uses a dumb spaced repetition algorithm (explained below) to decide when it should next show me the note.&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/secondbrain2/polymer.png&quot; style=&quot;width:100%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;At a high level, the tool is structured as follows:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Streamlit dashboard as a frontend.&lt;/li&gt;
  &lt;li&gt;A simple JSON “database” to track my stats for different notes: when I last reviewed it, what my recall level was, etc.&lt;/li&gt;
  &lt;li&gt;A very dumb spaced repetition algorithm to construct a queue from the database: if I “fail” to recall the note, it gets pushed back 10 places in the queue; if I recall the note with difficulty, the note gets pushed back 20 places in the queue etc.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;areas-of-improvement&quot;&gt;Areas of improvement&lt;/h2&gt;

&lt;p&gt;As we reach the end of the post, I want to briefly discuss some areas where I am currently improving my workflows.&lt;/p&gt;

&lt;h3 id=&quot;maps-of-content&quot;&gt;Maps of Content&lt;/h3&gt;

&lt;p&gt;I currently don’t have good entry points into most of my Topics. For example, if I want to refresh myself on concepts related to Management, I tend to wander around the local graph.&lt;/p&gt;

&lt;p&gt;In future, I would like to have better indexing via “Maps of Contents” (MOCs). A MOC is simply a semi-structured way to explore a particular Topic. Rather than leaving the Topic note empty, I should use it to organise notes. Here is an example of a MOC I have for the Volatility Topic.&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/secondbrain2/vol_moc.png&quot; style=&quot;width:80%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;I need to eventually make MOCs for other Topics too!&lt;/p&gt;

&lt;h3 id=&quot;integration-with-pdf-highlights&quot;&gt;Integration with PDF highlights&lt;/h3&gt;

&lt;p&gt;I’m still trying to figure out if there’s a better workflow for integrating PDF highlights with my system. This could be useful when taking notes on journal articles and textbooks.&lt;/p&gt;

&lt;p&gt;Currently, I do all of my highlights on the first pass, then manually type notes into my Second Brain on the second pass. This has some advantages (avoiding completionism, filtering for importance) but also requires a bit more effort.&lt;/p&gt;

&lt;p&gt;MarginNote looks promising (thanks to Lyra for the suggestion) and there are some workflows which connect Obsidian to Zotero (see this &lt;a href=&quot;https://www.YouTube.com/watch?v=D9ivU_IKO6M&quot;&gt;video&lt;/a&gt; by Artem Kirsanov).&lt;/p&gt;

&lt;h3 id=&quot;ongoing-events&quot;&gt;Ongoing events&lt;/h3&gt;

&lt;p&gt;Obsidian is very well suited to creating a garden of permanent notes. But in fields like finance, one must also track ongoing events and examine how certain narratives play out over time. This is a task that traditional systems like OneNote, MS Word, or even email threads are actually quite good at because by its nature, event-tracking is more “linear” with the flow of time.&lt;/p&gt;

&lt;p&gt;One of my exploration areas:  can we design a system that combines linear event tracking with an interlinked Second Brain? I have been exploring some workflows based on Daily Notes (a popular concept in Second Brain circles), but haven’t settled on anything concrete yet. There are some tools like &lt;a href=&quot;https://reflect.app/&quot;&gt;reflect.app&lt;/a&gt; which aim to solve this but I haven’t really explored them.&lt;/p&gt;

&lt;h3 id=&quot;molecular-notes-in-teams&quot;&gt;Molecular Notes in teams&lt;/h3&gt;

&lt;p&gt;For me, working on my Second Brain is a highly individual activity. That said, it’s natural to wonder whether Molecular Notes could be useful in group settings as an alternative to a wiki.&lt;/p&gt;

&lt;p&gt;I think one could make this work, provided:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;The team is small: if there’s too much content it ends up being difficult to use&lt;/li&gt;
  &lt;li&gt;Everyone is on the same page about what “deserves” to go into the collective brain&lt;/li&gt;
  &lt;li&gt;The team puts a lot of effort into maintaining maps of content (see above) – this would be essential to allow someone to quickly review what’s in the second brain without having to wander around other peoples’ notes.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Having published these posts on Molecular Notes, along with my two previous posts on &lt;a href=&quot;/2021/09/18/how-i-use-notion/&quot;&gt;&lt;em&gt;How I Use Notion&lt;/em&gt;&lt;/a&gt; and &lt;a href=&quot;/2022/01/24/reading-philosophy/&quot;&gt;&lt;em&gt;How I Read Books&lt;/em&gt;&lt;/a&gt;, I think I’ve said all that I want to say about productivity tools for the time being. Writing about personal productivity always feels somewhat self-indulgent; it inevitably comes down to “this is how I do things – you should do it too!”.&lt;/p&gt;

&lt;p&gt;Thanks to Niamh Q, Callum M, Shiv G, Lyra G, Cedric C, Joseph C for the discussions and inspiration. Writing these posts has given me a great excuse to nerd out about productivity tools and human-computer interaction with you clever people!&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Molecular Notes: Principles</title>
   <link href="https://reasonabledeviations.com/2022/04/18/molecular-notes-part-1/"/>
   <updated>2022-04-18T00:00:00+00:00</updated>
   <id>https://reasonabledeviations.com/2022/04/18/molecular-notes-part-1</id>
   <content type="html">&lt;p&gt;In this post I present Molecular Notes, a note-taking system I created to help me learn from diverse sources (books, textbooks, articles, courses), distil insights, and synthesise new ideas. Molecular Notes is how I approach my &lt;strong&gt;Second Brain&lt;/strong&gt; – the body of concepts and ideas that are relevant to my understanding of the world, both personally and professionally.&lt;/p&gt;

&lt;!--more--&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/secondbrain1/logo_big.png&quot; style=&quot;width:80%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;In general, I try to seek out mental models rather than specific facts; when I talk to people, I want to understand what models they are using. In a way, this post is my attempt to convey the most useful thing I can: my mental model on thinking, accumulating information, and building mental models – a meta mental model so to speak. As much as I enjoy writing my little explorations into miscellaneous bits of finance and mathematics, I am under no illusion as to their value. They are designed to pique curiosity (primarily my own), explore a neat quantitative technique, or outline an interesting connection. This post has much more grandiose ambitions: to outline my philosophy and practice of learning.&lt;/p&gt;

&lt;p&gt;Part 1 introduces the concept of Second Brains and the principles of Molecular Notes. I also compare Molecular Notes with Zettelkasten and Evergreen Notes, two other Second Brain systems. Part 2 covers the implementation: I discuss the software options and provide an in-depth walkthrough of my actual setup in Obsidian, including the practical workflows I use, along with a forkable GitHub repo so you can mimic my setup.&lt;/p&gt;

&lt;p&gt;This will be a detailed post; I spend a fair bit of time introducing the subject, as well as laying the groundwork. Use the table of contents to navigate!&lt;/p&gt;

&lt;p&gt;Thanks to Niamh Q and Shiv G for reading the drafts.&lt;/p&gt;

&lt;h2 id=&quot;contents&quot;&gt;Contents&lt;/h2&gt;

&lt;!-- TOC depthFrom:2 depthTo:6 withLinks:1 updateOnSave:1 orderedList:0 --&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;#what-is-a-second-brain&quot;&gt;What is a Second Brain&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#objectives&quot;&gt;Objectives&lt;/a&gt;
    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#1-rapidly-learning-complex-topics-from-mixed-media-forms&quot;&gt;1. Rapidly learning complex topics from mixed media forms&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#2-retaining-insights-and-understanding&quot;&gt;2. Retaining insights and understanding&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#3-on^2-growth&quot;&gt;3. $O(N^2)$ growth&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#4-synthesising-new-ideas&quot;&gt;4. Synthesising New Ideas&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#5-encouraging-positive-learning-habits&quot;&gt;5. Encouraging positive learning habits&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#summary----objectives&quot;&gt;Summary – Objectives&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#molecular-notes&quot;&gt;Molecular Notes&lt;/a&gt;
    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#sources&quot;&gt;Sources&lt;/a&gt;
        &lt;ul&gt;
          &lt;li&gt;&lt;a href=&quot;#summary--sources&quot;&gt;Summary – Sources&lt;/a&gt;&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#atoms&quot;&gt;Atoms&lt;/a&gt;
        &lt;ul&gt;
          &lt;li&gt;&lt;a href=&quot;#conveying-understanding&quot;&gt;Conveying understanding&lt;/a&gt;&lt;/li&gt;
          &lt;li&gt;&lt;a href=&quot;#showing-how-a-piece-of-knowledge-fits-in-with-other-atoms&quot;&gt;Showing how a piece of knowledge fits in with other atoms&lt;/a&gt;&lt;/li&gt;
          &lt;li&gt;&lt;a href=&quot;#atoms-as-pointers&quot;&gt;Atoms as pointers&lt;/a&gt;&lt;/li&gt;
          &lt;li&gt;&lt;a href=&quot;#summary--atoms&quot;&gt;Summary – Atoms&lt;/a&gt;&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#molecules&quot;&gt;Molecules&lt;/a&gt;
        &lt;ul&gt;
          &lt;li&gt;&lt;a href=&quot;#summary--molecules&quot;&gt;Summary – Molecules&lt;/a&gt;&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#topics&quot;&gt;Topics&lt;/a&gt;
        &lt;ul&gt;
          &lt;li&gt;&lt;a href=&quot;#summary--topics&quot;&gt;Summary – Topics&lt;/a&gt;&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#why-molecular-notes-matters-to-me&quot;&gt;Why Molecular Notes matters to me&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#who-is-molecular-notes-for&quot;&gt;Who is Molecular Notes for?&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#lay-of-the-land--other-note-taking-systems&quot;&gt;Lay of the land – other note-taking systems&lt;/a&gt;
    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#zettelkasten&quot;&gt;Zettelkasten&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#evergreen-notes&quot;&gt;Evergreen Notes&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#summary--molecular-notes-in-context&quot;&gt;Summary – Molecular Notes in context&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#failure-modes&quot;&gt;Failure modes&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#conclusion&quot;&gt;Conclusion&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;!-- /TOC --&gt;

&lt;h2 id=&quot;what-is-a-second-brain&quot;&gt;What is a Second Brain&lt;/h2&gt;

&lt;p&gt;My definition:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;A Second Brain is a system for consuming and interacting with information (specifically, concepts and ideas) that I care about.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The way I define it, a Second Brain is a subset of “personal knowledge management” (PKM) – my definition does &lt;em&gt;not&lt;/em&gt; include stuff like todos, goals, and plans. I bucket those under task management and project management instead (see my &lt;a href=&quot;https://reasonabledeviations.com/2021/09/18/how-i-use-notion/&quot;&gt;post&lt;/a&gt; on using Notion for these aspects of PKM). Note: this isn’t universal. Some people choose to include task management within their Second Brain, but I think it’s useful to think of them separately because the techniques and software tools used for Second Brains are quite different to the other verticals within PKM.&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/secondbrain1/pkm.png&quot; style=&quot;width:80%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;Let me break my definition down further:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;“System”: I should have a process, rather than making ad hoc decisions about how to incorporate new information into my thinking.&lt;/li&gt;
  &lt;li&gt;“Consuming and interacting with”: my brain is not just a hard drive: it computes across a knowledge graph. A proper Second Brain must provide both functions.&lt;/li&gt;
  &lt;li&gt;“Information (specifically concepts and ideas)”: as mentioned above, my Second Brain does not deal with project/task management. It purely deals with concepts, insights, and ideas.&lt;/li&gt;
  &lt;li&gt;“That I care about”: the goal is not to have faithful representations of every learning resource I’ve ever encountered. Instead, I want a map of concepts that are relevant to my thinking (personal or professional).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I have been working on my Second Brain for several years, switching between software tools, ripping up my workflows, and evolving my approaches. I am writing this post now because I feel that I have reached a point of equilibrium for the &lt;em&gt;philosophy&lt;/em&gt; of my system, although the &lt;em&gt;mechanics&lt;/em&gt; are in “perpetual beta” –  I’m always tweaking for improvements.&lt;/p&gt;

&lt;p&gt;Additionally, I am confident that I can use the system to create value, both for personal development and professional learning. I maintained the system during my internships, using it to effectively learn about complex subjects and derive actionable insights.&lt;/p&gt;

&lt;h2 id=&quot;objectives&quot;&gt;Objectives&lt;/h2&gt;

&lt;p&gt;I, like most of my audience, am a knowledge worker. As Wikipedia puts it, the job of a knowledge worker is to “think for a living” – to consume information, process it intelligently (which may require drawing on existing information or learning new techniques), and then communicate the output to stakeholders.&lt;/p&gt;

&lt;p&gt;The underlying goal of a Second Brain is to improve my ability to be a knowledge worker. This manifests itself in several specific objectives:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Rapidly learning complex topics from mixed media forms&lt;/li&gt;
  &lt;li&gt;Retaining insights and understanding&lt;/li&gt;
  &lt;li&gt;$O(N^2)$ growth&lt;/li&gt;
  &lt;li&gt;Synthesising new ideas&lt;/li&gt;
  &lt;li&gt;Encouraging positive learning habits&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I address each in turn.&lt;/p&gt;

&lt;h3 id=&quot;1-rapidly-learning-complex-topics-from-mixed-media-forms&quot;&gt;1. Rapidly learning complex topics from mixed media forms&lt;/h3&gt;

&lt;p&gt;I summarise it thus:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Having a process for consuming sources of information,&lt;/p&gt;

  &lt;p&gt;In a way that allows me to see how the pieces fit together,&lt;/p&gt;

  &lt;p&gt;While pointing to more detailed resources should they be needed.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The notes in my Second Brain should not contain all the details – they should only deal in the key ideas. In the internet age, what matters is knowing how concepts connect and knowing what tool to use for the job.  If I ever need the details, I can simply refer to the original resource.&lt;/p&gt;

&lt;h3 id=&quot;2-retaining-insights-and-understanding&quot;&gt;2. Retaining insights and understanding&lt;/h3&gt;

&lt;p&gt;On many occasions (particularly for university), I have put in the effort to truly understand a particular concept – grokking it at an intuitive level. This normally involves reading lots of different articles and scribbling ideas out on a piece of paper until the intuition clicks in my mind.&lt;/p&gt;

&lt;p&gt;Then, a few weeks/months later, I realise that I have forgotten the understanding that I worked so hard to gain. This is an incredibly frustrating situation: it’s not trivial to rebuild this understanding since it required me to consult many different sources. A key motivation for the development of Molecular Notes was to prevent this from ever happening!&lt;/p&gt;

&lt;p&gt;Specifically, a good Second Brain should allow me to rapidly achieve the same level of understanding about something that I once had in the past. This may be more difficult in situations where that piece of understanding is conditional on many other bits of knowledge, but even then, I should be able to rapidly traverse the local landscape of related concepts so that I may reconstruct intuition.&lt;/p&gt;

&lt;h3 id=&quot;3-on2-growth&quot;&gt;3. $O(N^2)$ growth&lt;/h3&gt;

&lt;p&gt;I don’t want my knowledge and capability to grow merely linearly in the number of things I consume. I want my knowledge to grow $O(N^2)$ – quadratically. Doubling the number of things I read should &lt;em&gt;quadruple&lt;/em&gt; what I know.&lt;/p&gt;

&lt;p&gt;How does one achieve this? With linked thought. Rather than treating each piece of knowledge in isolation, I want to try and link it with my existing body of knowledge. Each new piece of knowledge can potentially connect with all the other things I know.&lt;/p&gt;

&lt;p&gt;This is quadratic because the total number of possible handshakes in a room of $N$ people is of order $N^2$ – specifically, $\frac 1 2  N (N-1)$. I want each new piece of knowledge to “shake the hands” of every other piece of knowledge.&lt;/p&gt;

&lt;p&gt;Note: it is neither possible nor desirable to have true $O(N^2)$ growth – some pieces of knowledge are simply not related. Rather, this goal exists to emphasise the importance of linked thinking and the super-linear growth that it provides.&lt;/p&gt;

&lt;h3 id=&quot;4-synthesising-new-ideas&quot;&gt;4. Synthesising New Ideas&lt;/h3&gt;

&lt;p&gt;Figuring out how a machine works is important. Better still is to be able to propose modifications and improvements to a system, to invent and innovate.&lt;/p&gt;

&lt;p&gt;My Second Brain should not only enable me to get to grips with a complex topic but to have novel perspectives on the topic and to identify new directions to expand into. In the language of educational psychology, creation is at the top of Bloom’s taxonomy.&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/secondbrain1/blooms_taxonomy.png&quot; style=&quot;width:80%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;To be sure, synthesis is not entirely within the purview of the Second Brain system itself – it depends on the creativity of the user’s actual brain, so I don’t treat this as a &lt;em&gt;sine qua non&lt;/em&gt; for a Second Brain system.&lt;/p&gt;

&lt;h3 id=&quot;5-encouraging-positive-learning-habits&quot;&gt;5. Encouraging positive learning habits&lt;/h3&gt;

&lt;p&gt;Simply put, the system should spark joy. This largely comes down to the choice of tool: it should be a tool that is customisable and that I enjoy using.&lt;/p&gt;

&lt;p&gt;However, there are valuable insights deriving from the literature on addictive technologies. Consider Nir Eyal’s &lt;strong&gt;Hook model&lt;/strong&gt;, described in his book &lt;em&gt;Hooked&lt;/em&gt;. Eyal posits that Habit-forming technology requires:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Trigger: either external (e.g. notification) or internal (e.g. routinely checking the news when you wake up)&lt;/li&gt;
  &lt;li&gt;Action: behaviour done in anticipation of reward&lt;/li&gt;
  &lt;li&gt;Variable reward&lt;/li&gt;
  &lt;li&gt;Investment: user invests time into the product&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To the extent I can build a system that follows the Hook model, it will be enjoyable and habit forming.&lt;/p&gt;

&lt;h3 id=&quot;summary--objectives&quot;&gt;Summary – Objectives&lt;/h3&gt;

&lt;p&gt;That’s a lot of different things. But really what I want to strike at is this:&lt;/p&gt;

&lt;p&gt;A Second Brain system should allow me to learn from textbooks, online courses, and articles in a way that helps me understand the concepts, and enables me to apply those concepts in my personal and professional life.&lt;/p&gt;

&lt;h2 id=&quot;molecular-notes&quot;&gt;Molecular Notes&lt;/h2&gt;

&lt;p&gt;Over the past several years, I have experimented with different note-taking approaches, ripping my systems to shreds and starting again on numerous occasions. I finally settled on a system that I have come to call &lt;strong&gt;Molecular Notes&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I want to emphasise that Molecular Notes is an &lt;em&gt;emergent&lt;/em&gt; system – it evolved from many little refinements over the past couple of years, rather than being designed top-down to meet the objectives I laid out earlier. On the one hand, this leads to a more stable and robust system (as is generally true for emergent systems). On the other hand, in writing this post I have found that the emergent nature of the system often makes it surprisingly hard to explain. I will do my best, but if you find some aspects of it unintelligible, the fault is my own. Please send me an &lt;a href=&quot;https://reasonabledeviations.com/about/#get-in-touch&quot;&gt;email&lt;/a&gt; with the offending paragraph and I’ll try to reword it!&lt;/p&gt;

&lt;p&gt;The three primitives in Molecular Notes are:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Sources&lt;/strong&gt;: any source of information: a book, textbook, online course, podcast, youtube video, academic paper, presentation, etc.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Atoms&lt;/strong&gt;: existing concepts or techniques. As a rule of thumb, these will often have Wikipedia articles.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Molecules&lt;/strong&gt; (similar to “zettels” in the Zettelkasten system): crystallised insights, pieces of intuition, links between Atoms, my ideas.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are organised with &lt;strong&gt;Topics&lt;/strong&gt;&lt;em&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;center&gt;
    &lt;img src=&quot;/assets/images/secondbrain1/molecular.png&quot; style=&quot;width:80%;&quot; /&gt;
    &lt;/center&gt;

&lt;p&gt;The overall workflow of Molecular Notes is:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;Mine Atoms out of Sources, make Molecules out of Atoms, organise everything with Topics.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3 id=&quot;sources&quot;&gt;Sources&lt;/h3&gt;

&lt;p&gt;A &lt;strong&gt;Source&lt;/strong&gt; is any medium of information that I want to learn from. The main types of Sources that I deal with are non-fiction books, textbooks, podcasts, online courses and YouTube videos.&lt;/p&gt;

&lt;p&gt;As I have written &lt;a href=&quot;https://reasonabledeviations.com/2022/01/24/reading-philosophy/#extract-atomic-concepts&quot;&gt;previously&lt;/a&gt;, my mental model is that Sources (especially books) are typically composed of:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Concepts: either original to the author or well-established concepts that the author is referencing as part of their thesis&lt;/li&gt;
  &lt;li&gt;Additional details, context, and examples to illustrate the concept.&lt;/li&gt;
  &lt;li&gt;Thesis: the author gives their commentary/explanation regarding how concepts link to other concepts.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;My note-taking approach reflects this model. I want to capture (as briefly as possible) the core thesis of a Source, with reference to some key concepts, along with perhaps one or two of the best examples since these are often good mnemonic tools.&lt;/p&gt;

&lt;p&gt;For example, in NNT’s The Black Swan:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Concept: Black Swans – events that are rare, extremely impactful, and intrinsically unpredictable.&lt;/li&gt;
  &lt;li&gt;Examples: NNT provides several examples from finance and history.&lt;/li&gt;
  &lt;li&gt;Thesis:
    &lt;ul&gt;
      &lt;li&gt;Most of history is determined by Black Swans&lt;/li&gt;
      &lt;li&gt;We should be cautious in learning from the past&lt;/li&gt;
      &lt;li&gt;We should collect positive Black Swans&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This model is not universal: some Sources, particularly technical ones like textbooks, focus on presenting concepts and techniques, without having a thesis. Other Sources, like opinion pieces, focus primarily on a thesis rather than explaining concepts. Additionally, we need to be a bit loose with our definition of “concept”; for example, Sources about history discuss events rather than concepts, but I mentally lump this under “concepts” nonetheless.&lt;/p&gt;

&lt;p&gt;In any case, what I’m getting at is that Sources contain some pieces of knowledge that are “unique” to the Source, and some pieces of knowledge that exist separately to the Source (even if they are frequently referred to by the Source, or indeed invented by the author). Molecular Notes supports this distinction.&lt;/p&gt;

&lt;p&gt;I will try to make this more concrete: when taking notes from several Sources, I find that many concepts are duplicated across Sources. For example, the Black-Scholes model is discussed in every textbook about options; the narrative fallacy is explained in many books on decision-making; Kuhn’s paradigm shifts crop up all the time. If I was to re-explain these concepts in every Source note that references the concepts, I would end up with a lot of duplication. Duplication hampers discoverability: the next time I want to re-acquaint myself with “paradigm shifts” by searching for it in my note-taking system, I would be overwhelmed as the definition shows up in 10 sources.&lt;/p&gt;

&lt;p&gt;This leads to one of the key tenets of Molecular Notes: we should extract concepts into individual notes – &lt;strong&gt;Atoms&lt;/strong&gt; – and link to them from the Source (modern note-taking software like Obsidian makes this trivial).&lt;/p&gt;

&lt;p&gt;I will explain the philosophy of Atoms in detail in the next section, but I think an example of a Source note will be illustrative. Below is my note for &lt;em&gt;Adaptive Markets&lt;/em&gt; by Andrew Lo:&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/secondbrain1/adaptive_markets.png&quot; style=&quot;width:80%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;The purple text denotes a link to an Atom I’ve made. For example, the &lt;em&gt;Efficient markets hypothesis&lt;/em&gt; is a concept discussed in several Sources, so I created an Atom for it. In the bottom-right of the screenshot, you can see all the other books that point to the &lt;em&gt;Efficient markets hypothesis&lt;/em&gt;.&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/secondbrain1/efficient_markets_atom.png&quot; style=&quot;width:80%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;Some judgment is needed to decide what should be extracted as an Atom and what shouldn’t be. As a simple rule of thumb, I extract an Atom if I think it is likely that other Sources will discuss it, or if I think I will link ideas to that Atom specifically. The process of deciding whether to extract a concept is useful in itself because it makes me constantly reason about my notes on a meta level – debating whether something is likely to be useful for my thinking and looking at a Source on a higher level of abstraction.&lt;/p&gt;

&lt;p&gt;It is also important to allow for flux. Often my initial notes won’t contain many extracted Atoms until I later realise that a particular concept discussed by the author (for example, black swans), comes up in several other Sources. At that point, to prevent duplication, I extract the concept into an Atom and make all of the Sources link to that Atom. So even when I’m taking notes from textbook A, I’ve got panes containing my notes from textbooks B and C, trying to identify which aspects I can extract into Atoms. For example, I was recently working through an online course on econometrics (left-most pane) which referenced moving-average models. This sounded familiar and I realised I had covered it before in a textbook (the right-most pane). So I combined the explanations from the textbook and the online course into an Atom (middle pane), then made both Sources link to that Atom.&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/secondbrain1/econometrics.png&quot; style=&quot;width:80%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;There are certain Sources (especially blog posts and Twitter threads) that only present one concept. In this case, it would be wasteful to create a Source note since it would only link to one concept. So I “cut out the middleman”, making an Atom and directly linking to the Source.&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/secondbrain1/berksons.png&quot; style=&quot;width:80%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;However, if this Twitter thread merely referenced Berkson’s paradox as a part of a broader thesis, I’d be tempted to make it a Source note that would link to the Berkson’s paradox Atom.&lt;/p&gt;

&lt;p&gt;In Part 2 of this post, I provide a detailed workflow for how I create organised Source notes for different Source types. But for now, here are some guiding principles:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;The notes should mention all the key concepts discussed by a Source, but should &lt;em&gt;not&lt;/em&gt; explain all of these concepts (the explanation should go in the Atom for the relevant concept).&lt;/li&gt;
  &lt;li&gt;Aim to reduce the author’s thesis to its simplest form – I shouldn’t feel bad about reducing a book to 1-3 bullet points!&lt;/li&gt;
  &lt;li&gt;In essence, the note should tell me what is inside the Source, rather than repeating all the contents. My notes store pointers.&lt;/li&gt;
  &lt;li&gt;Regarding structure, I aim for simplicity and flexibility. Consistency is useful but overrated. I like to use nested bullets because they visually emphasise that there are different levels of abstraction. By scanning the top-level bullets, I should be able to quickly get a feel for the thrust of a particular Source. If I need more information, I can start descending into the bullets. If I need more information still, I should just open the original Source!&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;summary--sources&quot;&gt;Summary – Sources&lt;/h4&gt;

&lt;p&gt;I consume &lt;strong&gt;Sources&lt;/strong&gt; by making notes on them. While making notes, I extract concepts into individual notes called &lt;strong&gt;Atoms&lt;/strong&gt;. These Atoms can be linked to by several Sources.&lt;/p&gt;

&lt;h3 id=&quot;atoms&quot;&gt;Atoms&lt;/h3&gt;

&lt;p&gt;An &lt;strong&gt;Atom&lt;/strong&gt; represents an existing concept, technique, formula, framework, historical event, or historical figure. It is a piece of knowledge that doesn’t “belong” to me – it already exists, and the Atom is merely an explanation of it in my own words.&lt;/p&gt;

&lt;p&gt;As a rule of thumb, Atoms are pieces of knowledge that could have their own Wikipedia page (indeed, many of my Atoms &lt;em&gt;do&lt;/em&gt; have corresponding Wikipedia entries).&lt;/p&gt;

&lt;p&gt;The functions of an Atom are threefold. It must:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Explain the concept to me, such that I can rapidly resume a level of understanding I once had (Objective #2 – &lt;em&gt;Retaining insights and understanding&lt;/em&gt;)&lt;/li&gt;
  &lt;li&gt;Show how that piece of knowledge fits in with other Atoms (Objective #1 – &lt;em&gt;Rapidly learning complex topics&lt;/em&gt;; Objective #3 – $O(N^2)$ &lt;em&gt;growth&lt;/em&gt;).&lt;/li&gt;
  &lt;li&gt;Point to resources should I need more detail. Attached to each Atom should be a link to some Source (or even just a wiki page / URL) with the “best” explanation I can find for the concept.&lt;/li&gt;
&lt;/ol&gt;

&lt;h4 id=&quot;conveying-understanding&quot;&gt;Conveying understanding&lt;/h4&gt;

&lt;p&gt;I &lt;em&gt;always&lt;/em&gt; write Atoms in my own words. This is a critically important part of understanding something. I aim to keep the Atoms very brief, such that I can rapidly get up to speed. In particular, I am not aiming to capture all the details: only the parts of it that are interesting or relevant to my thinking.&lt;/p&gt;

&lt;p&gt;For example, the Wikipedia page for the Battle of Alesia (during Julius Caesar’s campaign in Gaul) contains about 4000 words and covers the historical context, details of the Siege, casualty numbers etc. Yet my particular interest lies in Julius Caesar’s tactics regarding the construction of concentric walls, so my note is a mere 90 words (in fairness, the context has been offloaded to another Atom for the Gallic Wars).&lt;/p&gt;

&lt;center&gt;
    &lt;img src=&quot;/assets/images/secondbrain1/battle_of_alesia.png&quot; style=&quot;width:80%;&quot; /&gt;
    &lt;/center&gt;

&lt;h4 id=&quot;showing-how-a-piece-of-knowledge-fits-in-with-other-atoms&quot;&gt;Showing how a piece of knowledge fits in with other atoms&lt;/h4&gt;

&lt;p&gt;I liberally link my Atoms to other Atoms. Additionally, most Atoms refer to a Source and are categorised with Topics, both of which allow me to better understand the context of a particular Atom.&lt;/p&gt;

&lt;center&gt;
    &lt;img src=&quot;/assets/images/secondbrain1/BLUE.png&quot; style=&quot;width:80%;&quot; /&gt;
    &lt;/center&gt;

&lt;h4 id=&quot;atoms-as-pointers&quot;&gt;Atoms as pointers&lt;/h4&gt;

&lt;p&gt;For each Atom, I provide a Reference. This is usually the Source note that discussed the Atom, but more generally, I want a link to the “best” resource for learning more. In the example above, if I wanted to know more about BLUEs, I would click into the Reference (in this case, my Source note for Ben Lambert’s online course in Econometrics), and look at the context in which BLUE was referenced. If that’s not enough, my Source note links to the original resource.&lt;/p&gt;

&lt;h4 id=&quot;summary--atoms&quot;&gt;Summary – Atoms&lt;/h4&gt;

&lt;p&gt;In essence, my library of &lt;strong&gt;Atoms&lt;/strong&gt; forms something like a personal Wikipedia, with several crucial differences:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;My library only contains Atoms that are relevant to my thinking (in the sense that they originated from a Source I felt was worth reading).&lt;/li&gt;
  &lt;li&gt;Wikipedia pages aim to be comprehensive, Atoms do not. Atoms should only cover the aspects of a concept that are interesting or relevant to me.&lt;/li&gt;
  &lt;li&gt;Because an Atom is written in my own words, it should be easier for me to regain lost understanding.&lt;/li&gt;
  &lt;li&gt;Once an Atom is created, it can link to other Atoms and be linked to by other Atoms. This creates a network of ideas that can graphically demonstrate how concepts link to one another.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;molecules&quot;&gt;Molecules&lt;/h3&gt;

&lt;p&gt;Thus far, Molecular Notes has been nothing more than a system for principled note-taking that seeks to reduce duplication. This is good, but we still haven’t achieved all of the objectives.&lt;/p&gt;

&lt;ul class=&quot;task-list&quot; style=&quot;list-style-type: none&quot;&gt;
  &lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; class=&quot;task-list-item-checkbox&quot; disabled=&quot;disabled&quot; checked=&quot;checked&quot; /&gt;1. Rapidly learning complex topics from mixed media forms&lt;/li&gt;
  &lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; class=&quot;task-list-item-checkbox&quot; disabled=&quot;disabled&quot; checked=&quot;checked&quot; /&gt;2. Retaining insights and understanding&lt;/li&gt;
  &lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; class=&quot;task-list-item-checkbox&quot; disabled=&quot;disabled&quot; /&gt;3. $O(N^2)$ growth&lt;/li&gt;
  &lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; class=&quot;task-list-item-checkbox&quot; disabled=&quot;disabled&quot; /&gt;4. Synthesising new ideas&lt;/li&gt;
  &lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; class=&quot;task-list-item-checkbox&quot; disabled=&quot;disabled&quot; checked=&quot;checked&quot; /&gt;5. Encouraging positive learning habits&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The solution: &lt;strong&gt;Molecules&lt;/strong&gt; (a.k.a “permanent notes” in the Zettelkasten system).&lt;/p&gt;

&lt;p&gt;It’s difficult for me to explain what exactly a Molecule is. The best I can do: a Molecule is an &lt;em&gt;insight&lt;/em&gt;. This insight should be self-contained and discrete (in the sense that it can stand on its own). My Molecules are typically only a couple of sentences – a paragraph at most. In most cases, a Molecule will “build on” one or more Atoms (which is why I called it a Molecule in the first place).&lt;/p&gt;

&lt;p&gt;To be a little more concrete, here are several types of Molecules along with examples.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Original insights I have, often inspired by some Source. (When I say “original”, I mean “originated by me”. I doubt any of my ideas are original in the sense that they haven’t been thought of before).&lt;/li&gt;
&lt;/ul&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/secondbrain1/wisdom.png&quot; style=&quot;width:80%;&quot; /&gt;
&lt;/center&gt;

&lt;ul&gt;
  &lt;li&gt;Original insights I have, related to an Atom:&lt;/li&gt;
&lt;/ul&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/secondbrain1/sapir_whorf.png&quot; style=&quot;width:80%;&quot; /&gt;
&lt;/center&gt;

&lt;ul&gt;
  &lt;li&gt;Links between two Sources, e.g. when I notice that two Sources are talking about the same concept in different ways:&lt;/li&gt;
&lt;/ul&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/secondbrain1/ubermensch.png&quot; style=&quot;width:80%;&quot; /&gt;
&lt;/center&gt;

&lt;ul&gt;
  &lt;li&gt;Links between two Atoms:&lt;/li&gt;
&lt;/ul&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/secondbrain1/pursue_emergent.png&quot; style=&quot;width:80%;&quot; /&gt;
&lt;/center&gt;

&lt;ul&gt;
  &lt;li&gt;Intelligent pieces of commentary related to a Source:&lt;/li&gt;
&lt;/ul&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/secondbrain1/correlation_suggests.png&quot; style=&quot;width:80%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;All of the above examples are “original” ideas or links between several pieces of knowledge. However, Molecules don’t need to be original. Many of my Molecules are crystallised insights based on a Source: moments of clarity I’ve achieved after stumbling through the conceptual thicket. I make Molecules like these when I feel that I know exactly the point an author is trying to get across and I can explain it succintly in my own words.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;From &lt;em&gt;Black Box Thinking&lt;/em&gt; by Matthew Syed:&lt;/li&gt;
&lt;/ul&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/secondbrain1/brainstorming.png&quot; style=&quot;width:80%;&quot; /&gt;
&lt;/center&gt;

&lt;ul&gt;
  &lt;li&gt;From Euan Sinclair’s textbook &lt;em&gt;Volatility Trading&lt;/em&gt;:&lt;/li&gt;
&lt;/ul&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/secondbrain1/volatility_measures.png&quot; style=&quot;width:80%;&quot; /&gt;
&lt;/center&gt;

&lt;ul&gt;
  &lt;li&gt;From Patrick O’Shaughnessy’s &lt;em&gt;Invest Like the Best&lt;/em&gt; podcast, with guest Marissa King:&lt;/li&gt;
&lt;/ul&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/secondbrain1/good_listening.png&quot; style=&quot;width:80%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;I will typically only make something a Molecule (rather than just a bullet point in the Source) if I feel like I have internalised the idea to the point where I would be comfortable calling it a piece of “my” intuition.&lt;/p&gt;

&lt;h4 id=&quot;summary--molecules&quot;&gt;Summary – Molecules&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Molecules&lt;/strong&gt; are &lt;em&gt;insights&lt;/em&gt; – either my own, or an insight I have internalised from a particular Source. Creating Molecules is the primary goal of Molecular Notes!&lt;/p&gt;

&lt;h3 id=&quot;topics&quot;&gt;Topics&lt;/h3&gt;

&lt;p&gt;Along with the three primitives – Sources, Atoms, Molecules – Molecular Notes uses &lt;strong&gt;Topics&lt;/strong&gt; (a.k.a categories) as an organising principle.&lt;/p&gt;

&lt;p&gt;Categorisation is relatively controversial among note-takers. I want to briefly outline both sides of the debate.&lt;/p&gt;

&lt;p&gt;The “spreadsheet” school of thought (my terminology) posits that the whole purpose of note-taking is to turn unstructured thoughts into something highly structured, presentable and reviewable (hence “spreadsheet”)  – the more categorisation and organisation, the better. But many Second Brain apologists reject top-down categorisation (see e.g. &lt;em&gt;&lt;a href=&quot;https://zettelkasten.de/posts/no-categories/&quot;&gt;Why Categories for Your Note Archive are a Bad Idea&lt;/a&gt;)&lt;/em&gt; in favour of emergent structure – they think that structure should emerge organically from the links.&lt;/p&gt;

&lt;p&gt;I think the truth is somewhere between these two extremes: I think that both deliberate and emergent structure – categories and links – are useful. In my view, structure aids understanding because it allows me to build a better map of where a piece of knowledge belongs. Pure emergence (i.e entirely unstructured note-taking) makes it harder to revisit the content.&lt;/p&gt;

&lt;p&gt;The problem with categories in traditional note-taking systems is that they often encourage a structure that is too rigid to support the concepts I am trying to learn. For example, in a traditional “files and folders” note system, it is hard for a note to belong to several categories: you’d have to duplicate a file in multiple folders, which seems wasteful. This leads to the “Categorisation paralysis”: when the inability to decide where to categorise some knowledge reduces efficiency (I am reminded of this &lt;a href=&quot;https://youtu.be/IJ07DcGGmMg?t=77&quot;&gt;scene&lt;/a&gt; in Wall-E, as he struggles to categorise a spork).&lt;/p&gt;

&lt;p&gt;But in modern software, this concern melts away. The way I use Obsidian, categories are merely empty notes that I can link to: I can link to as many categories as I would like. For that reason, I typically think of these as &lt;strong&gt;Topics&lt;/strong&gt; rather than categories – “category” feels more exclusive, whereas “Topic” just means that there is a related subject.&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/secondbrain1/statphys.png&quot; style=&quot;width:80%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;The advantage of Topics is that they give structure to my Second Brain and help me figure out which area of my graph to hunt for a particular concept or link. If I’m learning something about statistics, I will often have a look at the local graph of the statistics Topic to see if there are any interesting links:&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/secondbrain1/statistics.png&quot; style=&quot;width:80%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;If over time, I see that a particular Topic isn’t useful to me (e.g. I later find it was too specific or too broad), I just delete it!&lt;/p&gt;

&lt;p&gt;In Part 2, I will go into more detail about how exactly I organise my Second Brain: in particular, how I use Topics, folders, and tags.&lt;/p&gt;

&lt;h4 id=&quot;summary--topics&quot;&gt;Summary – Topics&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Topics&lt;/strong&gt; are a simple and flexible way of introducing structure to my Second Brain. They make it easier to search my Second Brain and to find possible links.&lt;/p&gt;

&lt;h2 id=&quot;why-molecular-notes-matters-to-me&quot;&gt;Why Molecular Notes matters to me&lt;/h2&gt;

&lt;p&gt;Generally I’m quite critical of “productivity YouTube”. The classic archetype is a YouTuber explaining some productivity tool when it’s clear that that they are mostly using the tool to administrate their YouTube videos – something that is presumably not applicable to the majority of viewers. Or a Zettelkasten video where the YouTuber’s three zettels are “Niklas Luhmann”, “Zettelkasten”, and “Linked thinking” – i.e they are using the zettelkasten system as a prop in a video rather than an actual note-taking system. Andy Matuschak puts it thus: “people who write extensively about note-writing rarely have a serious context of use”. Cedric C brings up a similar point regarding mental models (&lt;a href=&quot;https://commoncog.com/blog/the-mental-model-fallacy/&quot;&gt;link&lt;/a&gt;): there are far more people who write about mental models than people who effectively apply mental models.&lt;/p&gt;

&lt;p&gt;These concerns were a real barrier when it came to writing this blog post – the prospect of becoming a “productivity YouTuber” terrified me. Even as I write this, I realise that I’m still unable to clearly articulate what Molecular Notes enables me to do, though I unequivocally maintain that I’ve derived immense value from it.&lt;/p&gt;

&lt;p&gt;I guess it comes down to this: to the extent that you think I’ve got interesting ideas or a thought-provoking blog, or you like the way I think about things, all of that has been significantly enabled by my Second Brain. Perhaps not &lt;em&gt;originated&lt;/em&gt; by the Second Brain, but at least &lt;em&gt;amplified&lt;/em&gt;. Subjectively, I was a keen learner even before using Second Brain and I was decent at spotting connections between different things I had read. But codifying my workflows into Molecular Notes has dramatically accelerated up my pace of learning, even given the constraints of adulthood. I’ll admit this is a weak explanation. I’m essentially saying that I like using my Second Brain because I think it makes me more productive – a highly subjective and vaguely circular argument.&lt;/p&gt;

&lt;p&gt;To be slightly more concrete, Molecular Notes gave me a tangible benefit during my internship at a hedge fund (as it continues to do in my full-time job at the same hedge fund) because it allowed me to learn about highly technical areas quickly, while spotting the manifold links to related fields – trading is surprisingly interdisciplinary at the end of the day, drawing on mathematics, computation, psychology, statistics, and economics (among others). I would love to be more specific, but unfortunately the constraints of discretion prevent me from doing so. Nevertheless, the key point I’m trying to convey is that I believe Molecular Notes has helped me in practice. It isn’t just “blog material” – in fact, I only wrote this post after several people independently asked for it.&lt;/p&gt;

&lt;h2 id=&quot;who-is-molecular-notes-for&quot;&gt;Who is Molecular Notes for?&lt;/h2&gt;

&lt;p&gt;Inevitably, Molecular Notes evolved around my particular needs and interests: it is different to the Zettelkasten and Evergreen note-taking systems because my needs are different to those of Luhmann and Matuschak, their creators. It thus stands to reason that what works for me may not work for you, so you should approach Molecular notes with the same philosophy I approached the existing note-taking systems: a source of inspiration; a basket of features, some of which may fit your use case.&lt;/p&gt;

&lt;p&gt;I want to highlight several areas in particular where I think Molecular Notes is &lt;em&gt;not&lt;/em&gt; well suited (without modification).&lt;/p&gt;

&lt;p&gt;For students taking closed-book non-essay exams (particularly STEM subjects), I recommend using a note-taking system that better matches the exam format and treats spaced repetition as a first-class citizen. In my university programme, it was important for me to have all of the concepts and techniques at my fingertips and to practice applying them. Knowing about the concepts on a meta level (in particular, finding connections between different techniques) was not terribly important – perhaps this says something about the exam format! Obsidian does provide spaced repetition functionality via extensions, and as I will discuss in Part 2, I coded up my own little spaced repetition tool to work with Molecular Notes, but in truth I think you are better off with a dedicated spaced repetition tool like RemNote. It was specifically designed for spaced repetition and they have full-time developers maintaining and improving the functionality.&lt;/p&gt;

&lt;p&gt;Secondly, while I think many of the ideas in Molecular Notes are highly relevant to academics – particularly the ability to look for connections across Sources – more emphasis would need to be placed on collating references from articles. In academic writing, it is sometimes useful to have verbatim quotes from another article, while in Molecular Notes this is rare. If one’s goal is academic writing, the original Zettelkasten system may be a better fit.&lt;/p&gt;

&lt;p&gt;Lastly, Molecular Notes is entirely optimised for evergreen content, as opposed to tracking ongoing events or dealing with new situations (remember, my Second Brain is &lt;em&gt;not&lt;/em&gt; a project management tool). In Part 2, I will discuss how Molecular Notes could be modified to address these applications.&lt;/p&gt;

&lt;h2 id=&quot;lay-of-the-land--other-note-taking-systems&quot;&gt;Lay of the land – other note-taking systems&lt;/h2&gt;

&lt;p&gt;Molecular Notes is based on a lot of prior art. Having presented the principles of Molecular Notes, I want to compare and contrast it to two other related systems: Zettelkasten (the OG) and Evergreen notes.&lt;/p&gt;

&lt;p&gt;Perhaps after reading this section, you will feel that one of those is a better fit for you. As far as I’m concerned, that’d be an excellent outcome! We must each find the tools that best suit our goals and learning style.&lt;/p&gt;

&lt;h3 id=&quot;zettelkasten&quot;&gt;Zettelkasten&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;How to Take Smart Notes&lt;/em&gt; by Sonke Ahrens (my highlights &lt;a href=&quot;https://robertreads.notion.site/How-to-Take-Smart-Notes-73d4990369204dabb865543214980788&quot;&gt;here&lt;/a&gt;) may end up being the most important book I’ve read because it significantly accelerated my interest in note-taking systems, thus acting as a force multiplier on everything I’ve learnt since. In the book, Ahrens outlines &lt;em&gt;Zettelkasten&lt;/em&gt;, a note-taking system used by the sociologist Niklas Luhmann. Zettelkasten, implemented in Notion, was my first “thoughtful” note-taking set-up – though my system later began to deviate for reasons discussed below.&lt;/p&gt;

&lt;p&gt;There is &lt;em&gt;so&lt;/em&gt; much about Zettelkasten online (&lt;a href=&quot;https://zenkit.com/en/blog/a-beginners-guide-to-the-zettelkasten-method/&quot;&gt;this&lt;/a&gt; is a good starting point) that I hesitate to add to the noise. But for the sake of contrasting it with Molecular notes, the main primitives in the Zettelkasten system are:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Reference notes&lt;/strong&gt;: contain bibliographic information about a source, along with a brief summary of the source.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Literature notes&lt;/strong&gt;: a summary of a particular point made in a Source. When you read a Source, you will generate many separate Literature notes.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Permanent notes&lt;/strong&gt; (zettels): your insights.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Eva Keiffenham has a helpful diagram outlining the difference between literature notes and permanent notes in Zettelkasten:&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/secondbrain1/lit_vs_perm.png&quot; style=&quot;width:80%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;If you would like to see a Zettelkasten system implemented in Obsidian, this &lt;a href=&quot;https://www.youtube.com/watch?v=E6ySG7xYgjY&quot;&gt;video&lt;/a&gt; by Artem Kirsanov is the best walkthrough I’ve seen (be warned: many/most of the outlines of Zettelkasten on YouTube are mediocre at best).&lt;/p&gt;

&lt;p&gt;The main reason I needed to evolve beyond Zettelkasten is that it lacks a good primitive to deal with established concepts. For example, let’s say I am learning about chemistry and I have just learnt about the Haber process to manufacture ammonia. This is important to my thinking, so I want to include it in my note-taking system. Zettelkasten would include this either as a literature note (referencing the textbook you’re reading) or in a permanent note. But neither seems ideal. If it’s in a literature note, then when I read another textbook with a slightly different discussion of the Haber process, am I going to have a separate literature note for it? Putting it in a permanent note (explained in my own words) makes more sense, but it feels weird to put a “known” concept with an unoriginal explanation into the Zettelkasten.&lt;/p&gt;

&lt;p&gt;Secondly, many aspects of Zettelkasten arose out of the physical medium that Luhmann used (literal slips of paper in boxes). The decision to have Literature notes as separate individual notes makes sense in the context of physical slips of paper (writing a large document would make it hard to restructure), yet in a digital system I think it’s much easier to have all of my notes (or at least &lt;em&gt;pointers&lt;/em&gt; to notes) from a given Source in one document. Luhmann’s original Zettelkasten also uses a nontrivial naming convention to represent backlinks – something that is clearly unnecessary with a digital Zettelkasten.&lt;/p&gt;

&lt;p&gt;We should remember that Luhmann devised the Zettelkasten system to be a more productive sociologist. Sociology, as I understand it, largely involves the analysis of different qualitative theories – it deals with a smaller body of known facts/concepts and a larger body of qualitative discussion (this is not meant to denigrate sociology in any way). For STEM subjects, on the other hand, there are textbooks full of known concepts and techniques. I want to learn when to use these concepts and how they all connect – Zettelkasten is not naturally suited to this.&lt;/p&gt;

&lt;h3 id=&quot;evergreen-notes&quot;&gt;Evergreen Notes&lt;/h3&gt;

&lt;p&gt;Evergreen Notes is a system developed by Andy Matuschak, similar in many ways to Zettelkasten. Maggie Appleton has a nice visual &lt;a href=&quot;https://maggieappleton.com/evergreens&quot;&gt;explainer&lt;/a&gt;, but the ground truth is surely Matuschak’s own &lt;a href=&quot;https://notes.andymatuschak.org/z4SDCZQeRo4xFEQ8H4qrSqd68ucpgE6LU155C&quot;&gt;writing&lt;/a&gt; on the topic. If you are interested in note-taking, this is an absolute must read – I suggest you set aside an hour to trawl through his insights. I arrived at many of the same ideas independently, but I would have saved many hours had I simply read his thoughts to begin with.&lt;/p&gt;

&lt;p&gt;I agree with most of what Matuschak writes, but there are a couple of areas where his system didn’t quite work for me.&lt;/p&gt;

&lt;p&gt;Firstly, there are no Source notes. As Matuschak explains, his system is designed to optimise for creative thought rather than ingesting other peoples’ ideas so there isn’t a great way to take linear notes on a textbook, for example. I’m sure this is valid for someone as erudite as Matuschak, but given where I am in life’s learning curve, it’s very important to me that I have a good system for taking notes on educational resources.&lt;/p&gt;

&lt;p&gt;Secondly, he doesn’t use categories of any kind (this criticism applies to Zettelkasten as well). He explains this in a delightfully-titled permanent note “&lt;a href=&quot;https://notes.andymatuschak.org/z29hLZHiVt7W2uss2uMpSZquAX5T6vaeSF6Cy&quot;&gt;Prefer associative ontologies to hierarchical taxonomies&lt;/a&gt;”, but for the reasons I described earlier, I think categories (Topics) are a good way to index my knowledge.&lt;/p&gt;

&lt;p&gt;Following on from the above, the lack of structure makes his system (at least the public version) very hard to navigate, especially since it lacks search functionality and a graph view. Matuschak himself &lt;a href=&quot;https://notes.andymatuschak.org/zT6iA52811NuLvbU9W8ixeDc3KUqyCT1wN8&quot;&gt;points this out&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;summary--molecular-notes-in-context&quot;&gt;Summary – Molecular Notes in context&lt;/h3&gt;

&lt;p&gt;I don’t want to overstate the novelty of Molecular Notes: it was heavily inspired by Zettelkasten, and incorporates many of Matuschak’s innovations (though most were arrived at independently).&lt;/p&gt;

&lt;p&gt;The main additions of Molecular Notes that I feel aren’t well-captured by the existing systems:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Good workflows for taking linear notes from some resource (Sources)&lt;/li&gt;
  &lt;li&gt;Primitives for established concepts (Atoms)&lt;/li&gt;
  &lt;li&gt;A top-down structure, albeit a flexible one (Topics)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;failure-modes&quot;&gt;Failure modes&lt;/h2&gt;

&lt;p&gt;A recurring theme of this post is that you should modify Molecular Notes to work for you. But before wrapping up, I want to outline some failure modes that you should take care to avoid!&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Categorisation hell:
    &lt;ul&gt;
      &lt;li&gt;Don’t spend too much time trying to come up with a fancy categorisation scheme.&lt;/li&gt;
      &lt;li&gt;Pick something simple and flexible – something that you can build on top of as you get a better understanding of your particular needs&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Completionism:
    &lt;ul&gt;
      &lt;li&gt;Don’t add irrelevant things to your Second Brain.&lt;/li&gt;
      &lt;li&gt;It can be tempting to try and be comprehensive but this leads to notes that are harder to review&lt;/li&gt;
      &lt;li&gt;Not every concept deserves its own note!&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Complicated metadata:
    &lt;ul&gt;
      &lt;li&gt;Don’t try to overcomplicate the metadata – when I create a new note I only need to put three fields: “Topics”, “Reference” and “Type”.&lt;/li&gt;
      &lt;li&gt;Anything complicated starts to be a barrier to creating notes.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Copy-pasting content:
    &lt;ul&gt;
      &lt;li&gt;Copy-pasting is a no-go: it defeats the purpose of a Second Brain.&lt;/li&gt;
      &lt;li&gt;Further, it dramatically reduces retention while simultaneously encouraging completionism&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Not being intentional about note-taking:
    &lt;ul&gt;
      &lt;li&gt;Don’t slip into autopilot and make traditional notes!&lt;/li&gt;
      &lt;li&gt;Molecular Notes requires you to actively engage with the content, restructure old Sources even as you’re consuming a new Source, and constantly be on the lookout for opportunities to link ideas.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Part 1 of the series has outlined my objectives for a Second Brain system and proposed Molecular Notes to address them.&lt;/p&gt;

&lt;p&gt;I apologise if this post has been excessively abstract. In Part 2, the pendulum will swing the other way as I present my actual Obsidian setup in a sickening amount of detail. You have been warned!&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Convexity in DCFs</title>
   <link href="https://reasonabledeviations.com/2022/02/25/dcf-convexity/"/>
   <updated>2022-02-25T00:00:00+00:00</updated>
   <id>https://reasonabledeviations.com/2022/02/25/dcf-convexity</id>
   <content type="html">&lt;p&gt;In this post, we revisit classical DCFs through the lens of convexity. This leads to the counterintuitive finding that increased uncertainty about an asset’s fundamentals can sometimes be a good thing!&lt;/p&gt;

&lt;!--more--&gt;

&lt;h2 id=&quot;overview-of-dcfs&quot;&gt;Overview of DCFs&lt;/h2&gt;

&lt;p&gt;DCF analysis is used to value cash flow generating assets. The key idea is that we prefer cash today to cash a year from now, such that £100 next year is only worth $£100 \nu$ to us today, where $\nu = 1 / (1+r)$ is the discount factor and $r$ is the discount rate (for $r &amp;gt; 0$ we have $\nu &amp;lt; 1$).&lt;/p&gt;

&lt;p&gt;In a simple case, consider a perpetuity: an asset that pays out a fixed cash flow &lt;em&gt;CF&lt;/em&gt; every year for eternity. Because of discounting, this is a geometric series with finite value: $V = CF / r$. If the perpetuity is growing, the value is still finite as long as the annual growth rate &lt;em&gt;g&lt;/em&gt; of the cash flows is less than the discount rate. In this case, we have $V = CF / (r - g)$.&lt;/p&gt;

&lt;p&gt;Unlike fixed income products, the cash flows for equities are not contractually determined; they depend on the uncertain profits of a business. A stock analyst must estimate DCF inputs like the cash flow growth rate. In practice, it’s easier to break down “cash flow” into more tractable components, the most important being revenue growth, operating margins, and capital expenditure:&lt;/p&gt;

\[\text{revenue} \times \text{op. margin} - \text{capex} = \text{free cash flow}\]

&lt;p&gt;Analysts tend to input point estimates into DCFs but because of the difficulty of estimating inputs like the future revenue growth rate, I’ve often been intrigued by the idea of using distributional inputs instead, e.g. using a revenue growth rate of $30\% \pm 20\%$ rather than $30\%$.&lt;/p&gt;

&lt;p&gt;This is not an original thought: there are tools such as Oracle crystal ball that allow you to construct a valuation distribution using distributional inputs. Aswath Damodaran, NYU’s “valuation guru”, often includes such analysis in his valuations (a good walkthrough can be found on his &lt;a href=&quot;https://aswathdamodaran.blogspot.com/2016/05/dcf-myth-32-if-you-don-look-its-not.html&quot;&gt;blog&lt;/a&gt;). Yet my impression is that analysts struggle to derive actionable insights from the resulting value distribution.&lt;/p&gt;

&lt;p&gt;Sensitivity analysis – re-running the DCF with different inputs – &lt;em&gt;is&lt;/em&gt; commonly used by equity analysts. But I think even in this case, emphasis is placed on the specific numbers (e.g. “if revenue growth is 20% instead of 30% my price target is \$12 rather than \$20) rather than the &lt;em&gt;shape&lt;/em&gt; of the DCF function.&lt;/p&gt;

&lt;h2 id=&quot;an-investment-puzzle&quot;&gt;An investment puzzle&lt;/h2&gt;

&lt;p&gt;To motivate this exploration, let’s use a little puzzle.&lt;/p&gt;

&lt;p&gt;Consider two cash-flow-generating private companies Aco and Bco. Assume that each has the following characteristics:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Current cash flow is £10m/yr&lt;/li&gt;
  &lt;li&gt;Cost of capital is 10%&lt;/li&gt;
  &lt;li&gt;Each will have a high-growth phase for 10y, before settling down and growing at the rate of the overall economy (2.5% per year)&lt;/li&gt;
  &lt;li&gt;We expect the growth rate in the growth phase to be 30%&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The only difference between the two companies is as follows:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;We are very confident that Aco will grow at 30% per year&lt;/li&gt;
  &lt;li&gt;But for Bco, while 30% is our expected growth rate, we are uncertain: we have identified two equally likely paths for the company, one in which the growth rate is 35%, and another in which the growth rate is 25%&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Which company would we rather buy?&lt;/p&gt;

&lt;p&gt;Perhaps intuition suggests that we take the relatively certain option, after all, uncertainty is normally a bad thing. This is wrong. At least in this simplified model of reality, Bco is a better choice. In the next section, we will do some arithmetic to illustrate this.&lt;/p&gt;

&lt;p&gt;(If this feels wrong and the phrase “volatility drag” comes to mind, worry not: this point is addressed later on).&lt;/p&gt;

&lt;h2 id=&quot;two-stage-dcf-model&quot;&gt;Two-stage DCF model&lt;/h2&gt;

&lt;p&gt;The setup of the problem invites the use of a two-stage DCF model, in which a cash-flow generating asset is modelled as the present value of future cash flows, with the cash flows being generated in two phases: a high-growth phase and a perpetuity phase (still growing, but at the same rate as the overall economy).&lt;/p&gt;

&lt;p&gt;This is simple to implement in a spreadsheet: using the input assumptions from above, we find that the intrinsic value of Aco is £1.02bn. If this seems crazy for a company that only has £10m cash flow initially, remember that the cash flow is compounding at 30% over 10y.&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/dcf_convexity/dcf_a.png&quot; style=&quot;width:100%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;For Bco, we run a DCF separately for the bad case (25% growth) and the good case (35% growth).&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/dcf_convexity/dcf_b.png&quot; style=&quot;width:100%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;Because the good and bad cases are equally likely, the expected intrinsic value of Bco is simply the average of the intrinsic values in each case. But at this point we notice something strange:&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/dcf_convexity/dcf_summary.png&quot; style=&quot;width:30%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;In expectation, Bco is 6% more valuable than Aco, despite having the same expected growth rate as Aco. For some reason, increased uncertainty in Bco’s growth rate has led to a higher expected intrinsic value!&lt;/p&gt;

&lt;h2 id=&quot;dcf-convexity&quot;&gt;DCF convexity&lt;/h2&gt;

&lt;p&gt;Uncertainty in the growth rate leads to a higher expected DCF value because the DCF formula is &lt;em&gt;convex&lt;/em&gt; with respect to the growth rate.&lt;/p&gt;

&lt;h3 id=&quot;convexity&quot;&gt;Convexity&lt;/h3&gt;

&lt;p&gt;The high school definition of convexity: $f(x)$ is convex if its second derivative is positive, i.e. it curves upwards. An equivalent definition is that any straight line between two points is always above $f(x)$.&lt;/p&gt;

&lt;p&gt;In a financial setting, $x$ is normally the price of some security (e.g. a stock price), and $f(x)$ is the value of some derivative contract whose payoff depends on the price of the underlying. For example, the plot below shows the price of a call option as a function of the underlying stock price (holding other inputs constant). The payoff diagram “curves upwards”, so the payoff is convex.&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/dcf_convexity/option_price.png&quot; style=&quot;width:60%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;Yet the mathematical definition of convexity doesn’t entirely capture what traders mean when they talk about “convex bets”. Colloquially, a convex bet is one that “wins bigger than it loses”. Options possess convexity; they have limited downside (the premium you paid for the option) but potentially unlimited upside.&lt;/p&gt;

&lt;p&gt;Stock prices go up and down a lot. The reason why options are “good” is that they make more money when the stock moves in one direction than they lose when the stock goes the other way. For example, we may have the following situation for a call option:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Stock up 10%, option up 50%&lt;/li&gt;
  &lt;li&gt;Stock down 10%, option down 20%&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you own an option, because of its convex payoff, you &lt;em&gt;want&lt;/em&gt; uncertainty in the underlying. Considering the numbers above, given the choice between a stock that stays flat and a stock that goes up and down a lot, you would want the latter, because it “wins bigger than it loses”. This is why traders often say they are seeking convexity (though in reality what they mean is &lt;em&gt;mispriced&lt;/em&gt; convexity – you have to pay to own an option’s convexity).&lt;/p&gt;

&lt;p&gt;The main takeaway from this section: convex payoffs benefit from uncertainty/volatility. This is essentially the entire thesis of NNT’s &lt;em&gt;Antifragile&lt;/em&gt; (my review and highlights &lt;a href=&quot;https://robertreads.notion.site/robertreads/d1aa189b12e547799c3a5954b289cb5d?v=c344d511453b46eebace4671761ae631&amp;amp;p=9d146479dba24058913c3c043365a064&quot;&gt;here&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;If you want to read more about convexity, I refer you to Kris Abdelmessih’s excellent &lt;a href=&quot;https://moontowermeta.com/jensens-inequality-as-an-intuition-tool/&quot;&gt;post&lt;/a&gt;, which builds intuition and illustrates Jensen’s inequality.&lt;/p&gt;

&lt;h3 id=&quot;the-dcf-formula&quot;&gt;The DCF formula&lt;/h3&gt;

&lt;p&gt;Because DCFs are almost always done in spreadsheets, you rarely see the DCF formula in the wild. Having written out the two-stage DCF formula below, I suppose that’s probably for the better:&lt;/p&gt;

\[PV = \frac{CF(1+r)}{r - g_0} \left[ 1 - \left(\frac{1+g_0}{1+r}\right)^{n+1}\right] + \frac{CF(1+g_0)^n (1+g)}{(r-g)(1+r)^n}\]

&lt;p&gt;($CF$ is the initial cash flow, $g_0$ is the growth in the first stage, $n$ is the number of years in the first stage, $g$ is the perpetuity growth).&lt;/p&gt;

&lt;p&gt;To show this is convex with respect to the growth rate, I could differentiate it twice with respect to $g_0$ and prove that the resulting expression is positive. That’s a lot of effort. Instead, here is a plot of the DCF value of an asset for different growth rates, fixing $CF = 10, r = 10\%, g=2.5\%, n=10$:&lt;/p&gt;

&lt;html&gt;
&lt;head&gt;&lt;meta charset=&quot;utf-8&quot; /&gt;&lt;/head&gt;
&lt;body&gt;
    &lt;div&gt;                        &lt;script type=&quot;text/javascript&quot;&gt;window.PlotlyConfig = {MathJaxConfig: 'local'};&lt;/script&gt;
        &lt;script src=&quot;/assets/plotly.min.js&quot;&gt;&lt;/script&gt;                &lt;div id=&quot;9f47a945-14c8-4042-9b75-133b3d18a12d&quot; class=&quot;plotly-graph-div&quot; style=&quot;height:500px; width:800px;&quot;&gt;&lt;/div&gt;            &lt;script type=&quot;text/javascript&quot;&gt;                                    window.PLOTLYENV=window.PLOTLYENV || {};                                    if (document.getElementById(&quot;9f47a945-14c8-4042-9b75-133b3d18a12d&quot;)) {                    Plotly.newPlot(                        &quot;9f47a945-14c8-4042-9b75-133b3d18a12d&quot;,                        [{&quot;hovertemplate&quot;:&quot;g0=%{x}&lt;br&gt;pv=%{y}&lt;extra&gt;&lt;/extra&gt;&quot;,&quot;legendgroup&quot;:&quot;&quot;,&quot;line&quot;:{&quot;color&quot;:&quot;#636efa&quot;,&quot;dash&quot;:&quot;solid&quot;},&quot;marker&quot;:{&quot;symbol&quot;:&quot;circle&quot;},&quot;mode&quot;:&quot;lines&quot;,&quot;name&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;v&quot;,&quot;showlegend&quot;:false,&quot;x&quot;:[0.0,0.005050505050505051,0.010101010101010102,0.015151515151515152,0.020202020202020204,0.025252525252525256,0.030303030303030304,0.03535353535353536,0.04040404040404041,0.045454545454545456,0.05050505050505051,0.05555555555555556,0.06060606060606061,0.06565656565656566,0.07070707070707072,0.07575757575757576,0.08080808080808081,0.08585858585858587,0.09090909090909091,0.09595959595959597,0.10101010101010102,0.10606060606060606,0.11111111111111112,0.11616161616161617,0.12121212121212122,0.12626262626262627,0.13131313131313133,0.13636363636363638,0.14141414141414144,0.14646464646464646,0.15151515151515152,0.15656565656565657,0.16161616161616163,0.16666666666666669,0.17171717171717174,0.1767676767676768,0.18181818181818182,0.18686868686868688,0.19191919191919193,0.196969696969697,0.20202020202020204,0.2070707070707071,0.21212121212121213,0.21717171717171718,0.22222222222222224,0.2272727272727273,0.23232323232323235,0.2373737373737374,0.24242424242424243,0.2474747474747475,0.25252525252525254,0.2575757575757576,0.26262626262626265,0.2676767676767677,0.27272727272727276,0.2777777777777778,0.2828282828282829,0.2878787878787879,0.29292929292929293,0.297979797979798,0.30303030303030304,0.3080808080808081,0.31313131313131315,0.31818181818181823,0.32323232323232326,0.3282828282828283,0.33333333333333337,0.3383838383838384,0.3434343434343435,0.3484848484848485,0.3535353535353536,0.3585858585858586,0.36363636363636365,0.36868686868686873,0.37373737373737376,0.37878787878787884,0.38383838383838387,0.3888888888888889,0.393939393939394,0.398989898989899,0.4040404040404041,0.4090909090909091,0.4141414141414142,0.4191919191919192,0.42424242424242425,0.42929292929292934,0.43434343434343436,0.43939393939393945,0.4444444444444445,0.44949494949494956,0.4545454545454546,0.4595959595959596,0.4646464646464647,0.4696969696969697,0.4747474747474748,0.47979797979797983,0.48484848484848486,0.48989898989898994,0.494949494949495,0.5],&quot;xaxis&quot;:&quot;x&quot;,&quot;y&quot;:[124.13658727908282,128.34567305840557,132.72181420963204,137.27134696981423,142.00082347346245,146.9170181240802,152.02693412296617,157.33781015839105,162.85712725830078,168.59261580974479,174.55226274827524,180.7443189206117,187.17730662390719,193.8600273250106,200.8015695631604,208.0113170395926,215.4989568976083,223.27448819667995,231.34823058423234,239.73083316879223,248.4332835982247,257.46691734700113,266.84342721587166,276.57487304859546,286.6736916689965,297.1527070426671,308.02514066735034,319.30462219607637,331.0052002972243,343.1413537557678,355.72800281993676,368.7805207976762,382.3147459072791,396.34699338664643,410.8940678656902,425.9732760064546,441.6024394155738,457.79990783377275,474.58457260714795,491.97588044504664,509.99384746942076,528.6590735605868,547.992757004381,568.0167094457984,588.7533711542111,610.2258266053599,632.4578203854005,655.4737734222817,679.2987995498652,703.9587224102464,729.4800926997632,755.890205764302,783.2171195495528,811.4896729119241,840.737504295911,870.9910707837876,902.2816675235426,934.6414475410528,968.103441942593,1002.7015805138021,1038.4707127213164,1075.4466291234044,1113.6660831958939,1153.1668135799084,1193.9875667578542,1236.1681201642941,1279.7493057383522,1324.7730339243667,1371.282318127667,1419.321299632305,1468.9352729877355,1520.170711871528,1573.0752954351838,1627.697935140301,1684.0888020923985,1742.2993548797108,1802.382367924459,1864.3919603541256,1928.383625400326,1994.4142603329974,2062.5421969377066,2132.8272325439143,2205.330661612165,2280.1153078882726,2357.245557132593,2436.7873904325947,2518.808418107095,2603.3779142104836,2690.5668516454243,2780.447937892684,2873.0956513666147,2968.586278405192,3066.9979509033374,3168.410684598495,3272.9064180175637,3380.5690520941594,3491.4844904655965,3605.7406804587717,3723.42765477438,3844.6375738790684],&quot;yaxis&quot;:&quot;y&quot;,&quot;type&quot;:&quot;scatter&quot;}],                        {&quot;template&quot;:{&quot;data&quot;:{&quot;bar&quot;:[{&quot;error_x&quot;:{&quot;color&quot;:&quot;#2a3f5f&quot;},&quot;error_y&quot;:{&quot;color&quot;:&quot;#2a3f5f&quot;},&quot;marker&quot;:{&quot;line&quot;:{&quot;color&quot;:&quot;#E5ECF6&quot;,&quot;width&quot;:0.5},&quot;pattern&quot;:{&quot;fillmode&quot;:&quot;overlay&quot;,&quot;size&quot;:10,&quot;solidity&quot;:0.2}},&quot;type&quot;:&quot;bar&quot;}],&quot;barpolar&quot;:[{&quot;marker&quot;:{&quot;line&quot;:{&quot;color&quot;:&quot;#E5ECF6&quot;,&quot;width&quot;:0.5},&quot;pattern&quot;:{&quot;fillmode&quot;:&quot;overlay&quot;,&quot;size&quot;:10,&quot;solidity&quot;:0.2}},&quot;type&quot;:&quot;barpolar&quot;}],&quot;carpet&quot;:[{&quot;aaxis&quot;:{&quot;endlinecolor&quot;:&quot;#2a3f5f&quot;,&quot;gridcolor&quot;:&quot;white&quot;,&quot;linecolor&quot;:&quot;white&quot;,&quot;minorgridcolor&quot;:&quot;white&quot;,&quot;startlinecolor&quot;:&quot;#2a3f5f&quot;},&quot;baxis&quot;:{&quot;endlinecolor&quot;:&quot;#2a3f5f&quot;,&quot;gridcolor&quot;:&quot;white&quot;,&quot;linecolor&quot;:&quot;white&quot;,&quot;minorgridcolor&quot;:&quot;white&quot;,&quot;startlinecolor&quot;:&quot;#2a3f5f&quot;},&quot;type&quot;:&quot;carpet&quot;}],&quot;choropleth&quot;:[{&quot;colorbar&quot;:{&quot;outlinewidth&quot;:0,&quot;ticks&quot;:&quot;&quot;},&quot;type&quot;:&quot;choropleth&quot;}],&quot;contour&quot;:[{&quot;colorbar&quot;:{&quot;outlinewidth&quot;:0,&quot;ticks&quot;:&quot;&quot;},&quot;colorscale&quot;:[[0.0,&quot;#0d0887&quot;],[0.1111111111111111,&quot;#46039f&quot;],[0.2222222222222222,&quot;#7201a8&quot;],[0.3333333333333333,&quot;#9c179e&quot;],[0.4444444444444444,&quot;#bd3786&quot;],[0.5555555555555556,&quot;#d8576b&quot;],[0.6666666666666666,&quot;#ed7953&quot;],[0.7777777777777778,&quot;#fb9f3a&quot;],[0.8888888888888888,&quot;#fdca26&quot;],[1.0,&quot;#f0f921&quot;]],&quot;type&quot;:&quot;contour&quot;}],&quot;contourcarpet&quot;:[{&quot;colorbar&quot;:{&quot;outlinewidth&quot;:0,&quot;ticks&quot;:&quot;&quot;},&quot;type&quot;:&quot;contourcarpet&quot;}],&quot;heatmap&quot;:[{&quot;colorbar&quot;:{&quot;outlinewidth&quot;:0,&quot;ticks&quot;:&quot;&quot;},&quot;colorscale&quot;:[[0.0,&quot;#0d0887&quot;],[0.1111111111111111,&quot;#46039f&quot;],[0.2222222222222222,&quot;#7201a8&quot;],[0.3333333333333333,&quot;#9c179e&quot;],[0.4444444444444444,&quot;#bd3786&quot;],[0.5555555555555556,&quot;#d8576b&quot;],[0.6666666666666666,&quot;#ed7953&quot;],[0.7777777777777778,&quot;#fb9f3a&quot;],[0.8888888888888888,&quot;#fdca26&quot;],[1.0,&quot;#f0f921&quot;]],&quot;type&quot;:&quot;heatmap&quot;}],&quot;heatmapgl&quot;:[{&quot;colorbar&quot;:{&quot;outlinewidth&quot;:0,&quot;ticks&quot;:&quot;&quot;},&quot;colorscale&quot;:[[0.0,&quot;#0d0887&quot;],[0.1111111111111111,&quot;#46039f&quot;],[0.2222222222222222,&quot;#7201a8&quot;],[0.3333333333333333,&quot;#9c179e&quot;],[0.4444444444444444,&quot;#bd3786&quot;],[0.5555555555555556,&quot;#d8576b&quot;],[0.6666666666666666,&quot;#ed7953&quot;],[0.7777777777777778,&quot;#fb9f3a&quot;],[0.8888888888888888,&quot;#fdca26&quot;],[1.0,&quot;#f0f921&quot;]],&quot;type&quot;:&quot;heatmapgl&quot;}],&quot;histogram&quot;:[{&quot;marker&quot;:{&quot;pattern&quot;:{&quot;fillmode&quot;:&quot;overlay&quot;,&quot;size&quot;:10,&quot;solidity&quot;:0.2}},&quot;type&quot;:&quot;histogram&quot;}],&quot;histogram2d&quot;:[{&quot;colorbar&quot;:{&quot;outlinewidth&quot;:0,&quot;ticks&quot;:&quot;&quot;},&quot;colorscale&quot;:[[0.0,&quot;#0d0887&quot;],[0.1111111111111111,&quot;#46039f&quot;],[0.2222222222222222,&quot;#7201a8&quot;],[0.3333333333333333,&quot;#9c179e&quot;],[0.4444444444444444,&quot;#bd3786&quot;],[0.5555555555555556,&quot;#d8576b&quot;],[0.6666666666666666,&quot;#ed7953&quot;],[0.7777777777777778,&quot;#fb9f3a&quot;],[0.8888888888888888,&quot;#fdca26&quot;],[1.0,&quot;#f0f921&quot;]],&quot;type&quot;:&quot;histogram2d&quot;}],&quot;histogram2dcontour&quot;:[{&quot;colorbar&quot;:{&quot;outlinewidth&quot;:0,&quot;ticks&quot;:&quot;&quot;},&quot;colorscale&quot;:[[0.0,&quot;#0d0887&quot;],[0.1111111111111111,&quot;#46039f&quot;],[0.2222222222222222,&quot;#7201a8&quot;],[0.3333333333333333,&quot;#9c179e&quot;],[0.4444444444444444,&quot;#bd3786&quot;],[0.5555555555555556,&quot;#d8576b&quot;],[0.6666666666666666,&quot;#ed7953&quot;],[0.7777777777777778,&quot;#fb9f3a&quot;],[0.8888888888888888,&quot;#fdca26&quot;],[1.0,&quot;#f0f921&quot;]],&quot;type&quot;:&quot;histogram2dcontour&quot;}],&quot;mesh3d&quot;:[{&quot;colorbar&quot;:{&quot;outlinewidth&quot;:0,&quot;ticks&quot;:&quot;&quot;},&quot;type&quot;:&quot;mesh3d&quot;}],&quot;parcoords&quot;:[{&quot;line&quot;:{&quot;colorbar&quot;:{&quot;outlinewidth&quot;:0,&quot;ticks&quot;:&quot;&quot;}},&quot;type&quot;:&quot;parcoords&quot;}],&quot;pie&quot;:[{&quot;automargin&quot;:true,&quot;type&quot;:&quot;pie&quot;}],&quot;scatter&quot;:[{&quot;marker&quot;:{&quot;colorbar&quot;:{&quot;outlinewidth&quot;:0,&quot;ticks&quot;:&quot;&quot;}},&quot;type&quot;:&quot;scatter&quot;}],&quot;scatter3d&quot;:[{&quot;line&quot;:{&quot;colorbar&quot;:{&quot;outlinewidth&quot;:0,&quot;ticks&quot;:&quot;&quot;}},&quot;marker&quot;:{&quot;colorbar&quot;:{&quot;outlinewidth&quot;:0,&quot;ticks&quot;:&quot;&quot;}},&quot;type&quot;:&quot;scatter3d&quot;}],&quot;scattercarpet&quot;:[{&quot;marker&quot;:{&quot;colorbar&quot;:{&quot;outlinewidth&quot;:0,&quot;ticks&quot;:&quot;&quot;}},&quot;type&quot;:&quot;scattercarpet&quot;}],&quot;scattergeo&quot;:[{&quot;marker&quot;:{&quot;colorbar&quot;:{&quot;outlinewidth&quot;:0,&quot;ticks&quot;:&quot;&quot;}},&quot;type&quot;:&quot;scattergeo&quot;}],&quot;scattergl&quot;:[{&quot;marker&quot;:{&quot;colorbar&quot;:{&quot;outlinewidth&quot;:0,&quot;ticks&quot;:&quot;&quot;}},&quot;type&quot;:&quot;scattergl&quot;}],&quot;scattermapbox&quot;:[{&quot;marker&quot;:{&quot;colorbar&quot;:{&quot;outlinewidth&quot;:0,&quot;ticks&quot;:&quot;&quot;}},&quot;type&quot;:&quot;scattermapbox&quot;}],&quot;scatterpolar&quot;:[{&quot;marker&quot;:{&quot;colorbar&quot;:{&quot;outlinewidth&quot;:0,&quot;ticks&quot;:&quot;&quot;}},&quot;type&quot;:&quot;scatterpolar&quot;}],&quot;scatterpolargl&quot;:[{&quot;marker&quot;:{&quot;colorbar&quot;:{&quot;outlinewidth&quot;:0,&quot;ticks&quot;:&quot;&quot;}},&quot;type&quot;:&quot;scatterpolargl&quot;}],&quot;scatterternary&quot;:[{&quot;marker&quot;:{&quot;colorbar&quot;:{&quot;outlinewidth&quot;:0,&quot;ticks&quot;:&quot;&quot;}},&quot;type&quot;:&quot;scatterternary&quot;}],&quot;surface&quot;:[{&quot;colorbar&quot;:{&quot;outlinewidth&quot;:0,&quot;ticks&quot;:&quot;&quot;},&quot;colorscale&quot;:[[0.0,&quot;#0d0887&quot;],[0.1111111111111111,&quot;#46039f&quot;],[0.2222222222222222,&quot;#7201a8&quot;],[0.3333333333333333,&quot;#9c179e&quot;],[0.4444444444444444,&quot;#bd3786&quot;],[0.5555555555555556,&quot;#d8576b&quot;],[0.6666666666666666,&quot;#ed7953&quot;],[0.7777777777777778,&quot;#fb9f3a&quot;],[0.8888888888888888,&quot;#fdca26&quot;],[1.0,&quot;#f0f921&quot;]],&quot;type&quot;:&quot;surface&quot;}],&quot;table&quot;:[{&quot;cells&quot;:{&quot;fill&quot;:{&quot;color&quot;:&quot;#EBF0F8&quot;},&quot;line&quot;:{&quot;color&quot;:&quot;white&quot;}},&quot;header&quot;:{&quot;fill&quot;:{&quot;color&quot;:&quot;#C8D4E3&quot;},&quot;line&quot;:{&quot;color&quot;:&quot;white&quot;}},&quot;type&quot;:&quot;table&quot;}]},&quot;layout&quot;:{&quot;annotationdefaults&quot;:{&quot;arrowcolor&quot;:&quot;#2a3f5f&quot;,&quot;arrowhead&quot;:0,&quot;arrowwidth&quot;:1},&quot;autotypenumbers&quot;:&quot;strict&quot;,&quot;coloraxis&quot;:{&quot;colorbar&quot;:{&quot;outlinewidth&quot;:0,&quot;ticks&quot;:&quot;&quot;}},&quot;colorscale&quot;:{&quot;diverging&quot;:[[0,&quot;#8e0152&quot;],[0.1,&quot;#c51b7d&quot;],[0.2,&quot;#de77ae&quot;],[0.3,&quot;#f1b6da&quot;],[0.4,&quot;#fde0ef&quot;],[0.5,&quot;#f7f7f7&quot;],[0.6,&quot;#e6f5d0&quot;],[0.7,&quot;#b8e186&quot;],[0.8,&quot;#7fbc41&quot;],[0.9,&quot;#4d9221&quot;],[1,&quot;#276419&quot;]],&quot;sequential&quot;:[[0.0,&quot;#0d0887&quot;],[0.1111111111111111,&quot;#46039f&quot;],[0.2222222222222222,&quot;#7201a8&quot;],[0.3333333333333333,&quot;#9c179e&quot;],[0.4444444444444444,&quot;#bd3786&quot;],[0.5555555555555556,&quot;#d8576b&quot;],[0.6666666666666666,&quot;#ed7953&quot;],[0.7777777777777778,&quot;#fb9f3a&quot;],[0.8888888888888888,&quot;#fdca26&quot;],[1.0,&quot;#f0f921&quot;]],&quot;sequentialminus&quot;:[[0.0,&quot;#0d0887&quot;],[0.1111111111111111,&quot;#46039f&quot;],[0.2222222222222222,&quot;#7201a8&quot;],[0.3333333333333333,&quot;#9c179e&quot;],[0.4444444444444444,&quot;#bd3786&quot;],[0.5555555555555556,&quot;#d8576b&quot;],[0.6666666666666666,&quot;#ed7953&quot;],[0.7777777777777778,&quot;#fb9f3a&quot;],[0.8888888888888888,&quot;#fdca26&quot;],[1.0,&quot;#f0f921&quot;]]},&quot;colorway&quot;:[&quot;#636efa&quot;,&quot;#EF553B&quot;,&quot;#00cc96&quot;,&quot;#ab63fa&quot;,&quot;#FFA15A&quot;,&quot;#19d3f3&quot;,&quot;#FF6692&quot;,&quot;#B6E880&quot;,&quot;#FF97FF&quot;,&quot;#FECB52&quot;],&quot;font&quot;:{&quot;color&quot;:&quot;#2a3f5f&quot;},&quot;geo&quot;:{&quot;bgcolor&quot;:&quot;white&quot;,&quot;lakecolor&quot;:&quot;white&quot;,&quot;landcolor&quot;:&quot;#E5ECF6&quot;,&quot;showlakes&quot;:true,&quot;showland&quot;:true,&quot;subunitcolor&quot;:&quot;white&quot;},&quot;hoverlabel&quot;:{&quot;align&quot;:&quot;left&quot;},&quot;hovermode&quot;:&quot;closest&quot;,&quot;mapbox&quot;:{&quot;style&quot;:&quot;light&quot;},&quot;paper_bgcolor&quot;:&quot;white&quot;,&quot;plot_bgcolor&quot;:&quot;#E5ECF6&quot;,&quot;polar&quot;:{&quot;angularaxis&quot;:{&quot;gridcolor&quot;:&quot;white&quot;,&quot;linecolor&quot;:&quot;white&quot;,&quot;ticks&quot;:&quot;&quot;},&quot;bgcolor&quot;:&quot;#E5ECF6&quot;,&quot;radialaxis&quot;:{&quot;gridcolor&quot;:&quot;white&quot;,&quot;linecolor&quot;:&quot;white&quot;,&quot;ticks&quot;:&quot;&quot;}},&quot;scene&quot;:{&quot;xaxis&quot;:{&quot;backgroundcolor&quot;:&quot;#E5ECF6&quot;,&quot;gridcolor&quot;:&quot;white&quot;,&quot;gridwidth&quot;:2,&quot;linecolor&quot;:&quot;white&quot;,&quot;showbackground&quot;:true,&quot;ticks&quot;:&quot;&quot;,&quot;zerolinecolor&quot;:&quot;white&quot;},&quot;yaxis&quot;:{&quot;backgroundcolor&quot;:&quot;#E5ECF6&quot;,&quot;gridcolor&quot;:&quot;white&quot;,&quot;gridwidth&quot;:2,&quot;linecolor&quot;:&quot;white&quot;,&quot;showbackground&quot;:true,&quot;ticks&quot;:&quot;&quot;,&quot;zerolinecolor&quot;:&quot;white&quot;},&quot;zaxis&quot;:{&quot;backgroundcolor&quot;:&quot;#E5ECF6&quot;,&quot;gridcolor&quot;:&quot;white&quot;,&quot;gridwidth&quot;:2,&quot;linecolor&quot;:&quot;white&quot;,&quot;showbackground&quot;:true,&quot;ticks&quot;:&quot;&quot;,&quot;zerolinecolor&quot;:&quot;white&quot;}},&quot;shapedefaults&quot;:{&quot;line&quot;:{&quot;color&quot;:&quot;#2a3f5f&quot;}},&quot;ternary&quot;:{&quot;aaxis&quot;:{&quot;gridcolor&quot;:&quot;white&quot;,&quot;linecolor&quot;:&quot;white&quot;,&quot;ticks&quot;:&quot;&quot;},&quot;baxis&quot;:{&quot;gridcolor&quot;:&quot;white&quot;,&quot;linecolor&quot;:&quot;white&quot;,&quot;ticks&quot;:&quot;&quot;},&quot;bgcolor&quot;:&quot;#E5ECF6&quot;,&quot;caxis&quot;:{&quot;gridcolor&quot;:&quot;white&quot;,&quot;linecolor&quot;:&quot;white&quot;,&quot;ticks&quot;:&quot;&quot;}},&quot;title&quot;:{&quot;x&quot;:0.05},&quot;xaxis&quot;:{&quot;automargin&quot;:true,&quot;gridcolor&quot;:&quot;white&quot;,&quot;linecolor&quot;:&quot;white&quot;,&quot;ticks&quot;:&quot;&quot;,&quot;title&quot;:{&quot;standoff&quot;:15},&quot;zerolinecolor&quot;:&quot;white&quot;,&quot;zerolinewidth&quot;:2},&quot;yaxis&quot;:{&quot;automargin&quot;:true,&quot;gridcolor&quot;:&quot;white&quot;,&quot;linecolor&quot;:&quot;white&quot;,&quot;ticks&quot;:&quot;&quot;,&quot;title&quot;:{&quot;standoff&quot;:15},&quot;zerolinecolor&quot;:&quot;white&quot;,&quot;zerolinewidth&quot;:2}}},&quot;xaxis&quot;:{&quot;anchor&quot;:&quot;y&quot;,&quot;domain&quot;:[0.0,1.0],&quot;title&quot;:{&quot;text&quot;:&quot;g0&quot;},&quot;tickformat&quot;:&quot;.1%&quot;},&quot;yaxis&quot;:{&quot;anchor&quot;:&quot;x&quot;,&quot;domain&quot;:[0.0,1.0],&quot;title&quot;:{&quot;text&quot;:&quot;pv&quot;}},&quot;legend&quot;:{&quot;tracegroupgap&quot;:0},&quot;margin&quot;:{&quot;t&quot;:60},&quot;height&quot;:500,&quot;width&quot;:800,&quot;shapes&quot;:[{&quot;line&quot;:{&quot;dash&quot;:&quot;dash&quot;},&quot;type&quot;:&quot;line&quot;,&quot;x0&quot;:0.3,&quot;x1&quot;:0.3,&quot;xref&quot;:&quot;x&quot;,&quot;y0&quot;:0,&quot;y1&quot;:1,&quot;yref&quot;:&quot;y domain&quot;}]},                        {&quot;responsive&quot;: true}                    )                };                            &lt;/script&gt;        &lt;/div&gt;
&lt;/body&gt;
&lt;/html&gt;

&lt;p&gt;Note: the above analysis merely shows that the DCF formula is convex with respect to the &lt;em&gt;growth rate&lt;/em&gt;. In principle, the DCF formula could be linear, concave, or convex with respect to the other value determinants. The plot below shows that DCFs are convex also to the discount rate, so uncertainty in the cost of capital is a good thing.&lt;/p&gt;

&lt;html&gt;
&lt;head&gt;&lt;meta charset=&quot;utf-8&quot; /&gt;&lt;/head&gt;
&lt;body&gt;
    &lt;div&gt;                        &lt;script type=&quot;text/javascript&quot;&gt;window.PlotlyConfig = {MathJaxConfig: 'local'};&lt;/script&gt;
        &lt;script src=&quot;/assets/plotly.min.js&quot;&gt;&lt;/script&gt;                &lt;div id=&quot;15a8ff76-533b-4f25-a328-eaa98f20ef5e&quot; class=&quot;plotly-graph-div&quot; style=&quot;height:500px; width:800px;&quot;&gt;&lt;/div&gt;            &lt;script type=&quot;text/javascript&quot;&gt;                                    window.PLOTLYENV=window.PLOTLYENV || {};                                    if (document.getElementById(&quot;15a8ff76-533b-4f25-a328-eaa98f20ef5e&quot;)) {                    Plotly.newPlot(                        &quot;15a8ff76-533b-4f25-a328-eaa98f20ef5e&quot;,                        [{&quot;hovertemplate&quot;:&quot;x=%{x}&lt;br&gt;y=%{y}&lt;extra&gt;&lt;/extra&gt;&quot;,&quot;legendgroup&quot;:&quot;&quot;,&quot;line&quot;:{&quot;color&quot;:&quot;#636efa&quot;,&quot;dash&quot;:&quot;solid&quot;},&quot;marker&quot;:{&quot;symbol&quot;:&quot;circle&quot;},&quot;mode&quot;:&quot;lines&quot;,&quot;name&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;v&quot;,&quot;showlegend&quot;:false,&quot;x&quot;:[0.05,0.05242424242424243,0.054848484848484855,0.057272727272727274,0.0596969696969697,0.062121212121212126,0.06454545454545454,0.06696969696969697,0.0693939393939394,0.07181818181818182,0.07424242424242425,0.07666666666666666,0.0790909090909091,0.08151515151515151,0.08393939393939394,0.08636363636363636,0.08878787878787879,0.09121212121212122,0.09363636363636364,0.09606060606060607,0.09848484848484848,0.10090909090909092,0.10333333333333333,0.10575757575757576,0.10818181818181818,0.11060606060606061,0.11303030303030304,0.11545454545454546,0.11787878787878787,0.1203030303030303,0.12272727272727273,0.12515151515151515,0.12757575757575756,0.13,0.13242424242424244,0.13484848484848486,0.13727272727272727,0.1396969696969697,0.14212121212121212,0.14454545454545453,0.14696969696969697,0.1493939393939394,0.15181818181818182,0.15424242424242424,0.15666666666666668,0.1590909090909091,0.1615151515151515,0.16393939393939394,0.16636363636363638,0.1687878787878788,0.1712121212121212,0.17363636363636364,0.17606060606060608,0.17848484848484847,0.1809090909090909,0.18333333333333335,0.18575757575757573,0.18818181818181817,0.1906060606060606,0.19303030303030305,0.19545454545454544,0.19787878787878788,0.20030303030303032,0.20272727272727276,0.20515151515151514,0.20757575757575758,0.21000000000000002,0.2124242424242424,0.21484848484848484,0.21727272727272728,0.21969696969696972,0.2221212121212121,0.22454545454545455,0.226969696969697,0.22939393939393937,0.2318181818181818,0.23424242424242425,0.2366666666666667,0.23909090909090908,0.24151515151515152,0.24393939393939396,0.24636363636363634,0.24878787878787878,0.2512121212121212,0.25363636363636366,0.25606060606060604,0.2584848484848485,0.2609090909090909,0.2633333333333333,0.26575757575757575,0.2681818181818182,0.2706060606060606,0.273030303030303,0.27545454545454545,0.2778787878787879,0.28030303030303033,0.2827272727272727,0.28515151515151516,0.2875757575757576,0.29],&quot;xaxis&quot;:&quot;x&quot;,&quot;y&quot;:[3868.0517729769726,3482.8702815240044,3161.016473015478,2888.2039153268024,2654.139244822926,2451.218298236791,2273.7018284766727,2117.1768931633587,1978.1947167579124,1854.0210617359885,1742.4603364544896,1641.72922120211,1550.3642773122792,1467.153335304447,1391.0838156454051,1321.3032995255949,1257.0890907353776,1197.8244642827067,1142.9799485149435,1092.0984387109938,1044.783257347839,1000.688502301269,959.5111873330777,920.9847982468607,884.8739759035889,850.9701027174174,819.0876184649687,789.0609285836899,760.7417967050984,733.9971351982786,708.7071246102819,684.7636062736375,662.0687028880507,640.5336302307023,620.0776698015951,600.6272775411039,582.1153080517579,564.4803372341166,547.6660690765516,531.6208146522258,516.2970332761968,501.6509273419946,487.64208365399554,474.2331551498779,461.3895778068537,449.0793182783534,437.27264844055594,425.94194356145647,415.0615012561767,404.6073787748044,394.5572464945291,384.8902557655449,375.586919497783,366.62900407937,357.9994313929927,349.6821898474939,341.6622534726537,333.92550823827423,326.4586848569288,319.2492974152357,312.28558725307704,305.55647157530245,299.0514963375214,292.76079299758203,286.6750387683474,280.78542004606385,275.0835987227838,269.56168112148407,264.2121893192068,259.0280346472513,254.00249317845328,249.1291830302834,244.4020433291565,239.81531469618426,235.36352112788606,231.04145315722036,226.84415219094473,222.76689592883685,218.80518477887773,214.95472919018482,211.21143783243895,207.57140655676068,204.03090807866312,200.58638232879815,197.23442742179287,193.97179119768703,190.79536329424627,187.70216771184465,184.68935583575848,181.75419988353005,178.8940867476434,176.10651220611663,173.38907547574826,170.73947408470855,168.15549904299945,165.63503029085658,163.17603240677855,160.7765505581181,158.43470667853444,156.14869585770367],&quot;yaxis&quot;:&quot;y&quot;,&quot;type&quot;:&quot;scatter&quot;}],                        {&quot;template&quot;:{&quot;data&quot;:{&quot;bar&quot;:[{&quot;error_x&quot;:{&quot;color&quot;:&quot;#2a3f5f&quot;},&quot;error_y&quot;:{&quot;color&quot;:&quot;#2a3f5f&quot;},&quot;marker&quot;:{&quot;line&quot;:{&quot;color&quot;:&quot;#E5ECF6&quot;,&quot;width&quot;:0.5},&quot;pattern&quot;:{&quot;fillmode&quot;:&quot;overlay&quot;,&quot;size&quot;:10,&quot;solidity&quot;:0.2}},&quot;type&quot;:&quot;bar&quot;}],&quot;barpolar&quot;:[{&quot;marker&quot;:{&quot;line&quot;:{&quot;color&quot;:&quot;#E5ECF6&quot;,&quot;width&quot;:0.5},&quot;pattern&quot;:{&quot;fillmode&quot;:&quot;overlay&quot;,&quot;size&quot;:10,&quot;solidity&quot;:0.2}},&quot;type&quot;:&quot;barpolar&quot;}],&quot;carpet&quot;:[{&quot;aaxis&quot;:{&quot;endlinecolor&quot;:&quot;#2a3f5f&quot;,&quot;gridcolor&quot;:&quot;white&quot;,&quot;linecolor&quot;:&quot;white&quot;,&quot;minorgridcolor&quot;:&quot;white&quot;,&quot;startlinecolor&quot;:&quot;#2a3f5f&quot;},&quot;baxis&quot;:{&quot;endlinecolor&quot;:&quot;#2a3f5f&quot;,&quot;gridcolor&quot;:&quot;white&quot;,&quot;linecolor&quot;:&quot;white&quot;,&quot;minorgridcolor&quot;:&quot;white&quot;,&quot;startlinecolor&quot;:&quot;#2a3f5f&quot;},&quot;type&quot;:&quot;carpet&quot;}],&quot;choropleth&quot;:[{&quot;colorbar&quot;:{&quot;outlinewidth&quot;:0,&quot;ticks&quot;:&quot;&quot;},&quot;type&quot;:&quot;choropleth&quot;}],&quot;contour&quot;:[{&quot;colorbar&quot;:{&quot;outlinewidth&quot;:0,&quot;ticks&quot;:&quot;&quot;},&quot;colorscale&quot;:[[0.0,&quot;#0d0887&quot;],[0.1111111111111111,&quot;#46039f&quot;],[0.2222222222222222,&quot;#7201a8&quot;],[0.3333333333333333,&quot;#9c179e&quot;],[0.4444444444444444,&quot;#bd3786&quot;],[0.5555555555555556,&quot;#d8576b&quot;],[0.6666666666666666,&quot;#ed7953&quot;],[0.7777777777777778,&quot;#fb9f3a&quot;],[0.8888888888888888,&quot;#fdca26&quot;],[1.0,&quot;#f0f921&quot;]],&quot;type&quot;:&quot;contour&quot;}],&quot;contourcarpet&quot;:[{&quot;colorbar&quot;:{&quot;outlinewidth&quot;:0,&quot;ticks&quot;:&quot;&quot;},&quot;type&quot;:&quot;contourcarpet&quot;}],&quot;heatmap&quot;:[{&quot;colorbar&quot;:{&quot;outlinewidth&quot;:0,&quot;ticks&quot;:&quot;&quot;},&quot;colorscale&quot;:[[0.0,&quot;#0d0887&quot;],[0.1111111111111111,&quot;#46039f&quot;],[0.2222222222222222,&quot;#7201a8&quot;],[0.3333333333333333,&quot;#9c179e&quot;],[0.4444444444444444,&quot;#bd3786&quot;],[0.5555555555555556,&quot;#d8576b&quot;],[0.6666666666666666,&quot;#ed7953&quot;],[0.7777777777777778,&quot;#fb9f3a&quot;],[0.8888888888888888,&quot;#fdca26&quot;],[1.0,&quot;#f0f921&quot;]],&quot;type&quot;:&quot;heatmap&quot;}],&quot;heatmapgl&quot;:[{&quot;colorbar&quot;:{&quot;outlinewidth&quot;:0,&quot;ticks&quot;:&quot;&quot;},&quot;colorscale&quot;:[[0.0,&quot;#0d0887&quot;],[0.1111111111111111,&quot;#46039f&quot;],[0.2222222222222222,&quot;#7201a8&quot;],[0.3333333333333333,&quot;#9c179e&quot;],[0.4444444444444444,&quot;#bd3786&quot;],[0.5555555555555556,&quot;#d8576b&quot;],[0.6666666666666666,&quot;#ed7953&quot;],[0.7777777777777778,&quot;#fb9f3a&quot;],[0.8888888888888888,&quot;#fdca26&quot;],[1.0,&quot;#f0f921&quot;]],&quot;type&quot;:&quot;heatmapgl&quot;}],&quot;histogram&quot;:[{&quot;marker&quot;:{&quot;pattern&quot;:{&quot;fillmode&quot;:&quot;overlay&quot;,&quot;size&quot;:10,&quot;solidity&quot;:0.2}},&quot;type&quot;:&quot;histogram&quot;}],&quot;histogram2d&quot;:[{&quot;colorbar&quot;:{&quot;outlinewidth&quot;:0,&quot;ticks&quot;:&quot;&quot;},&quot;colorscale&quot;:[[0.0,&quot;#0d0887&quot;],[0.1111111111111111,&quot;#46039f&quot;],[0.2222222222222222,&quot;#7201a8&quot;],[0.3333333333333333,&quot;#9c179e&quot;],[0.4444444444444444,&quot;#bd3786&quot;],[0.5555555555555556,&quot;#d8576b&quot;],[0.6666666666666666,&quot;#ed7953&quot;],[0.7777777777777778,&quot;#fb9f3a&quot;],[0.8888888888888888,&quot;#fdca26&quot;],[1.0,&quot;#f0f921&quot;]],&quot;type&quot;:&quot;histogram2d&quot;}],&quot;histogram2dcontour&quot;:[{&quot;colorbar&quot;:{&quot;outlinewidth&quot;:0,&quot;ticks&quot;:&quot;&quot;},&quot;colorscale&quot;:[[0.0,&quot;#0d0887&quot;],[0.1111111111111111,&quot;#46039f&quot;],[0.2222222222222222,&quot;#7201a8&quot;],[0.3333333333333333,&quot;#9c179e&quot;],[0.4444444444444444,&quot;#bd3786&quot;],[0.5555555555555556,&quot;#d8576b&quot;],[0.6666666666666666,&quot;#ed7953&quot;],[0.7777777777777778,&quot;#fb9f3a&quot;],[0.8888888888888888,&quot;#fdca26&quot;],[1.0,&quot;#f0f921&quot;]],&quot;type&quot;:&quot;histogram2dcontour&quot;}],&quot;mesh3d&quot;:[{&quot;colorbar&quot;:{&quot;outlinewidth&quot;:0,&quot;ticks&quot;:&quot;&quot;},&quot;type&quot;:&quot;mesh3d&quot;}],&quot;parcoords&quot;:[{&quot;line&quot;:{&quot;colorbar&quot;:{&quot;outlinewidth&quot;:0,&quot;ticks&quot;:&quot;&quot;}},&quot;type&quot;:&quot;parcoords&quot;}],&quot;pie&quot;:[{&quot;automargin&quot;:true,&quot;type&quot;:&quot;pie&quot;}],&quot;scatter&quot;:[{&quot;marker&quot;:{&quot;colorbar&quot;:{&quot;outlinewidth&quot;:0,&quot;ticks&quot;:&quot;&quot;}},&quot;type&quot;:&quot;scatter&quot;}],&quot;scatter3d&quot;:[{&quot;line&quot;:{&quot;colorbar&quot;:{&quot;outlinewidth&quot;:0,&quot;ticks&quot;:&quot;&quot;}},&quot;marker&quot;:{&quot;colorbar&quot;:{&quot;outlinewidth&quot;:0,&quot;ticks&quot;:&quot;&quot;}},&quot;type&quot;:&quot;scatter3d&quot;}],&quot;scattercarpet&quot;:[{&quot;marker&quot;:{&quot;colorbar&quot;:{&quot;outlinewidth&quot;:0,&quot;ticks&quot;:&quot;&quot;}},&quot;type&quot;:&quot;scattercarpet&quot;}],&quot;scattergeo&quot;:[{&quot;marker&quot;:{&quot;colorbar&quot;:{&quot;outlinewidth&quot;:0,&quot;ticks&quot;:&quot;&quot;}},&quot;type&quot;:&quot;scattergeo&quot;}],&quot;scattergl&quot;:[{&quot;marker&quot;:{&quot;colorbar&quot;:{&quot;outlinewidth&quot;:0,&quot;ticks&quot;:&quot;&quot;}},&quot;type&quot;:&quot;scattergl&quot;}],&quot;scattermapbox&quot;:[{&quot;marker&quot;:{&quot;colorbar&quot;:{&quot;outlinewidth&quot;:0,&quot;ticks&quot;:&quot;&quot;}},&quot;type&quot;:&quot;scattermapbox&quot;}],&quot;scatterpolar&quot;:[{&quot;marker&quot;:{&quot;colorbar&quot;:{&quot;outlinewidth&quot;:0,&quot;ticks&quot;:&quot;&quot;}},&quot;type&quot;:&quot;scatterpolar&quot;}],&quot;scatterpolargl&quot;:[{&quot;marker&quot;:{&quot;colorbar&quot;:{&quot;outlinewidth&quot;:0,&quot;ticks&quot;:&quot;&quot;}},&quot;type&quot;:&quot;scatterpolargl&quot;}],&quot;scatterternary&quot;:[{&quot;marker&quot;:{&quot;colorbar&quot;:{&quot;outlinewidth&quot;:0,&quot;ticks&quot;:&quot;&quot;}},&quot;type&quot;:&quot;scatterternary&quot;}],&quot;surface&quot;:[{&quot;colorbar&quot;:{&quot;outlinewidth&quot;:0,&quot;ticks&quot;:&quot;&quot;},&quot;colorscale&quot;:[[0.0,&quot;#0d0887&quot;],[0.1111111111111111,&quot;#46039f&quot;],[0.2222222222222222,&quot;#7201a8&quot;],[0.3333333333333333,&quot;#9c179e&quot;],[0.4444444444444444,&quot;#bd3786&quot;],[0.5555555555555556,&quot;#d8576b&quot;],[0.6666666666666666,&quot;#ed7953&quot;],[0.7777777777777778,&quot;#fb9f3a&quot;],[0.8888888888888888,&quot;#fdca26&quot;],[1.0,&quot;#f0f921&quot;]],&quot;type&quot;:&quot;surface&quot;}],&quot;table&quot;:[{&quot;cells&quot;:{&quot;fill&quot;:{&quot;color&quot;:&quot;#EBF0F8&quot;},&quot;line&quot;:{&quot;color&quot;:&quot;white&quot;}},&quot;header&quot;:{&quot;fill&quot;:{&quot;color&quot;:&quot;#C8D4E3&quot;},&quot;line&quot;:{&quot;color&quot;:&quot;white&quot;}},&quot;type&quot;:&quot;table&quot;}]},&quot;layout&quot;:{&quot;annotationdefaults&quot;:{&quot;arrowcolor&quot;:&quot;#2a3f5f&quot;,&quot;arrowhead&quot;:0,&quot;arrowwidth&quot;:1},&quot;autotypenumbers&quot;:&quot;strict&quot;,&quot;coloraxis&quot;:{&quot;colorbar&quot;:{&quot;outlinewidth&quot;:0,&quot;ticks&quot;:&quot;&quot;}},&quot;colorscale&quot;:{&quot;diverging&quot;:[[0,&quot;#8e0152&quot;],[0.1,&quot;#c51b7d&quot;],[0.2,&quot;#de77ae&quot;],[0.3,&quot;#f1b6da&quot;],[0.4,&quot;#fde0ef&quot;],[0.5,&quot;#f7f7f7&quot;],[0.6,&quot;#e6f5d0&quot;],[0.7,&quot;#b8e186&quot;],[0.8,&quot;#7fbc41&quot;],[0.9,&quot;#4d9221&quot;],[1,&quot;#276419&quot;]],&quot;sequential&quot;:[[0.0,&quot;#0d0887&quot;],[0.1111111111111111,&quot;#46039f&quot;],[0.2222222222222222,&quot;#7201a8&quot;],[0.3333333333333333,&quot;#9c179e&quot;],[0.4444444444444444,&quot;#bd3786&quot;],[0.5555555555555556,&quot;#d8576b&quot;],[0.6666666666666666,&quot;#ed7953&quot;],[0.7777777777777778,&quot;#fb9f3a&quot;],[0.8888888888888888,&quot;#fdca26&quot;],[1.0,&quot;#f0f921&quot;]],&quot;sequentialminus&quot;:[[0.0,&quot;#0d0887&quot;],[0.1111111111111111,&quot;#46039f&quot;],[0.2222222222222222,&quot;#7201a8&quot;],[0.3333333333333333,&quot;#9c179e&quot;],[0.4444444444444444,&quot;#bd3786&quot;],[0.5555555555555556,&quot;#d8576b&quot;],[0.6666666666666666,&quot;#ed7953&quot;],[0.7777777777777778,&quot;#fb9f3a&quot;],[0.8888888888888888,&quot;#fdca26&quot;],[1.0,&quot;#f0f921&quot;]]},&quot;colorway&quot;:[&quot;#636efa&quot;,&quot;#EF553B&quot;,&quot;#00cc96&quot;,&quot;#ab63fa&quot;,&quot;#FFA15A&quot;,&quot;#19d3f3&quot;,&quot;#FF6692&quot;,&quot;#B6E880&quot;,&quot;#FF97FF&quot;,&quot;#FECB52&quot;],&quot;font&quot;:{&quot;color&quot;:&quot;#2a3f5f&quot;},&quot;geo&quot;:{&quot;bgcolor&quot;:&quot;white&quot;,&quot;lakecolor&quot;:&quot;white&quot;,&quot;landcolor&quot;:&quot;#E5ECF6&quot;,&quot;showlakes&quot;:true,&quot;showland&quot;:true,&quot;subunitcolor&quot;:&quot;white&quot;},&quot;hoverlabel&quot;:{&quot;align&quot;:&quot;left&quot;},&quot;hovermode&quot;:&quot;closest&quot;,&quot;mapbox&quot;:{&quot;style&quot;:&quot;light&quot;},&quot;paper_bgcolor&quot;:&quot;white&quot;,&quot;plot_bgcolor&quot;:&quot;#E5ECF6&quot;,&quot;polar&quot;:{&quot;angularaxis&quot;:{&quot;gridcolor&quot;:&quot;white&quot;,&quot;linecolor&quot;:&quot;white&quot;,&quot;ticks&quot;:&quot;&quot;},&quot;bgcolor&quot;:&quot;#E5ECF6&quot;,&quot;radialaxis&quot;:{&quot;gridcolor&quot;:&quot;white&quot;,&quot;linecolor&quot;:&quot;white&quot;,&quot;ticks&quot;:&quot;&quot;}},&quot;scene&quot;:{&quot;xaxis&quot;:{&quot;backgroundcolor&quot;:&quot;#E5ECF6&quot;,&quot;gridcolor&quot;:&quot;white&quot;,&quot;gridwidth&quot;:2,&quot;linecolor&quot;:&quot;white&quot;,&quot;showbackground&quot;:true,&quot;ticks&quot;:&quot;&quot;,&quot;zerolinecolor&quot;:&quot;white&quot;},&quot;yaxis&quot;:{&quot;backgroundcolor&quot;:&quot;#E5ECF6&quot;,&quot;gridcolor&quot;:&quot;white&quot;,&quot;gridwidth&quot;:2,&quot;linecolor&quot;:&quot;white&quot;,&quot;showbackground&quot;:true,&quot;ticks&quot;:&quot;&quot;,&quot;zerolinecolor&quot;:&quot;white&quot;},&quot;zaxis&quot;:{&quot;backgroundcolor&quot;:&quot;#E5ECF6&quot;,&quot;gridcolor&quot;:&quot;white&quot;,&quot;gridwidth&quot;:2,&quot;linecolor&quot;:&quot;white&quot;,&quot;showbackground&quot;:true,&quot;ticks&quot;:&quot;&quot;,&quot;zerolinecolor&quot;:&quot;white&quot;}},&quot;shapedefaults&quot;:{&quot;line&quot;:{&quot;color&quot;:&quot;#2a3f5f&quot;}},&quot;ternary&quot;:{&quot;aaxis&quot;:{&quot;gridcolor&quot;:&quot;white&quot;,&quot;linecolor&quot;:&quot;white&quot;,&quot;ticks&quot;:&quot;&quot;},&quot;baxis&quot;:{&quot;gridcolor&quot;:&quot;white&quot;,&quot;linecolor&quot;:&quot;white&quot;,&quot;ticks&quot;:&quot;&quot;},&quot;bgcolor&quot;:&quot;#E5ECF6&quot;,&quot;caxis&quot;:{&quot;gridcolor&quot;:&quot;white&quot;,&quot;linecolor&quot;:&quot;white&quot;,&quot;ticks&quot;:&quot;&quot;}},&quot;title&quot;:{&quot;x&quot;:0.05},&quot;xaxis&quot;:{&quot;automargin&quot;:true,&quot;gridcolor&quot;:&quot;white&quot;,&quot;linecolor&quot;:&quot;white&quot;,&quot;ticks&quot;:&quot;&quot;,&quot;title&quot;:{&quot;standoff&quot;:15},&quot;zerolinecolor&quot;:&quot;white&quot;,&quot;zerolinewidth&quot;:2},&quot;yaxis&quot;:{&quot;automargin&quot;:true,&quot;gridcolor&quot;:&quot;white&quot;,&quot;linecolor&quot;:&quot;white&quot;,&quot;ticks&quot;:&quot;&quot;,&quot;title&quot;:{&quot;standoff&quot;:15},&quot;zerolinecolor&quot;:&quot;white&quot;,&quot;zerolinewidth&quot;:2}}},&quot;xaxis&quot;:{&quot;anchor&quot;:&quot;y&quot;,&quot;domain&quot;:[0.0,1.0],&quot;title&quot;:{&quot;text&quot;:&quot;r&quot;},&quot;tickformat&quot;:&quot;.1%&quot;},&quot;yaxis&quot;:{&quot;anchor&quot;:&quot;x&quot;,&quot;domain&quot;:[0.0,1.0],&quot;title&quot;:{&quot;text&quot;:&quot;PV&quot;}},&quot;legend&quot;:{&quot;tracegroupgap&quot;:0},&quot;margin&quot;:{&quot;t&quot;:60},&quot;height&quot;:500,&quot;width&quot;:800,&quot;shapes&quot;:[{&quot;line&quot;:{&quot;dash&quot;:&quot;dash&quot;},&quot;type&quot;:&quot;line&quot;,&quot;x0&quot;:0.1,&quot;x1&quot;:0.1,&quot;xref&quot;:&quot;x&quot;,&quot;y0&quot;:0,&quot;y1&quot;:1,&quot;yref&quot;:&quot;y domain&quot;}]},                        {&quot;responsive&quot;: true}                    )                };                            &lt;/script&gt;        &lt;/div&gt;
&lt;/body&gt;
&lt;/html&gt;

&lt;h2 id=&quot;is-volatility-good-or-bad&quot;&gt;Is volatility good or bad?&lt;/h2&gt;

&lt;p&gt;The argument I have made so far can be summarised as follows:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;The expected value of a convex function increases with uncertainty in the input variable. In other words, convex payoffs like uncertainty.&lt;/li&gt;
  &lt;li&gt;The DCF formula is convex with respect to growth rates.&lt;/li&gt;
  &lt;li&gt;Hence, uncertainty in the growth rate increases the expected intrinsic value of an asset.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Another statement is also true: volatility in the growth rate &lt;em&gt;decreases&lt;/em&gt; the expected intrinsic value of an asset.&lt;/p&gt;

&lt;p&gt;How can it be that &lt;em&gt;uncertainty&lt;/em&gt; in the growth rate increase the expected intrinsic value, but &lt;em&gt;volatility&lt;/em&gt; in the growth rate decrease the expected intrinsic value?&lt;/p&gt;

&lt;p&gt;Arguably there are some semantical shenanigans going on in my distinction between uncertainty and volatility, but there is an important point in how I laid out the puzzle at the beginning.&lt;/p&gt;

&lt;p&gt;For Bco, the two equally likely outcomes are:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Fixed growth rate of 35% for 10y&lt;/li&gt;
  &lt;li&gt;Fixed growth rate of 25% for 10y&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is equivalent to saying that we flip a coin whose outcome determines whether we are in a universe where cash flows grow at 35% every year for 10y, or a universe where cash flows grow at 25% every year for 10y. Critically, once the universe has been chosen, there is &lt;em&gt;zero&lt;/em&gt; volatility in the cash flow growth rate.&lt;/p&gt;

&lt;p&gt;Now consider a modified puzzle, where we choose between:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Aco: as before, cash flows grow 30% every year for 10y before entering perpetuity growth.&lt;/li&gt;
  &lt;li&gt;Cco: cash flows grow at $30\% \pm 20\%$, i.e. every year we draw the growth rate from a normal distribution $N(30\%, 10\%^2)$.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To value Cco, we can’t simply draw growth rates from the normal distribution and substitute them into the DCF formula (this is the whole point!): we must simulate different paths and calculate the DCF value of each path, before taking the mean across all paths.&lt;/p&gt;

&lt;p&gt;The below chart shows how the expected intrinsic value of the asset varies with the volatility of the cash flow growth rate:&lt;/p&gt;

&lt;html&gt;
&lt;head&gt;&lt;meta charset=&quot;utf-8&quot; /&gt;&lt;/head&gt;
&lt;body&gt;
    &lt;div&gt;                        &lt;script type=&quot;text/javascript&quot;&gt;window.PlotlyConfig = {MathJaxConfig: 'local'};&lt;/script&gt;
        &lt;script src=&quot;/assets/plotly.min.js&quot;&gt;&lt;/script&gt;                &lt;div id=&quot;c9c0e8f3-7b0d-43a6-bb96-e058fdd07dea&quot; class=&quot;plotly-graph-div&quot; style=&quot;height:500px; width:800px;&quot;&gt;&lt;/div&gt;            &lt;script type=&quot;text/javascript&quot;&gt;                                    window.PLOTLYENV=window.PLOTLYENV || {};                                    if (document.getElementById(&quot;c9c0e8f3-7b0d-43a6-bb96-e058fdd07dea&quot;)) {                    Plotly.newPlot(                        &quot;c9c0e8f3-7b0d-43a6-bb96-e058fdd07dea&quot;,                        [{&quot;error_y&quot;:{&quot;array&quot;:[1.1368683772161603e-15,2.287729494586913,4.771959091990818,7.193444849679408,11.01812763891056,14.573453627724955,19.95373112652473,27.15191087864327,38.91980631654761,58.43768702488321]},&quot;hovertemplate&quot;:&quot;cf_std=%{x}&lt;br&gt;PV=%{y}&lt;extra&gt;&lt;/extra&gt;&quot;,&quot;legendgroup&quot;:&quot;&quot;,&quot;marker&quot;:{&quot;color&quot;:&quot;#636efa&quot;,&quot;symbol&quot;:&quot;circle&quot;},&quot;mode&quot;:&quot;markers&quot;,&quot;name&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;v&quot;,&quot;showlegend&quot;:false,&quot;x&quot;:[0.0,0.1,0.2,0.30000000000000004,0.4,0.5,0.6000000000000001,0.7000000000000001,0.8,0.9],&quot;xaxis&quot;:&quot;x&quot;,&quot;y&quot;:[1016.8667312023643,1020.8955880703639,1021.8544455709264,1000.3328068201967,1001.6280149777323,1010.4646558328055,995.9138054831911,962.0466645997537,956.8631061262503,950.1439187267503],&quot;yaxis&quot;:&quot;y&quot;,&quot;type&quot;:&quot;scatter&quot;}],                        {&quot;template&quot;:{&quot;data&quot;:{&quot;bar&quot;:[{&quot;error_x&quot;:{&quot;color&quot;:&quot;#2a3f5f&quot;},&quot;error_y&quot;:{&quot;color&quot;:&quot;#2a3f5f&quot;},&quot;marker&quot;:{&quot;line&quot;:{&quot;color&quot;:&quot;#E5ECF6&quot;,&quot;width&quot;:0.5},&quot;pattern&quot;:{&quot;fillmode&quot;:&quot;overlay&quot;,&quot;size&quot;:10,&quot;solidity&quot;:0.2}},&quot;type&quot;:&quot;bar&quot;}],&quot;barpolar&quot;:[{&quot;marker&quot;:{&quot;line&quot;:{&quot;color&quot;:&quot;#E5ECF6&quot;,&quot;width&quot;:0.5},&quot;pattern&quot;:{&quot;fillmode&quot;:&quot;overlay&quot;,&quot;size&quot;:10,&quot;solidity&quot;:0.2}},&quot;type&quot;:&quot;barpolar&quot;}],&quot;carpet&quot;:[{&quot;aaxis&quot;:{&quot;endlinecolor&quot;:&quot;#2a3f5f&quot;,&quot;gridcolor&quot;:&quot;white&quot;,&quot;linecolor&quot;:&quot;white&quot;,&quot;minorgridcolor&quot;:&quot;white&quot;,&quot;startlinecolor&quot;:&quot;#2a3f5f&quot;},&quot;baxis&quot;:{&quot;endlinecolor&quot;:&quot;#2a3f5f&quot;,&quot;gridcolor&quot;:&quot;white&quot;,&quot;linecolor&quot;:&quot;white&quot;,&quot;minorgridcolor&quot;:&quot;white&quot;,&quot;startlinecolor&quot;:&quot;#2a3f5f&quot;},&quot;type&quot;:&quot;carpet&quot;}],&quot;choropleth&quot;:[{&quot;colorbar&quot;:{&quot;outlinewidth&quot;:0,&quot;ticks&quot;:&quot;&quot;},&quot;type&quot;:&quot;choropleth&quot;}],&quot;contour&quot;:[{&quot;colorbar&quot;:{&quot;outlinewidth&quot;:0,&quot;ticks&quot;:&quot;&quot;},&quot;colorscale&quot;:[[0.0,&quot;#0d0887&quot;],[0.1111111111111111,&quot;#46039f&quot;],[0.2222222222222222,&quot;#7201a8&quot;],[0.3333333333333333,&quot;#9c179e&quot;],[0.4444444444444444,&quot;#bd3786&quot;],[0.5555555555555556,&quot;#d8576b&quot;],[0.6666666666666666,&quot;#ed7953&quot;],[0.7777777777777778,&quot;#fb9f3a&quot;],[0.8888888888888888,&quot;#fdca26&quot;],[1.0,&quot;#f0f921&quot;]],&quot;type&quot;:&quot;contour&quot;}],&quot;contourcarpet&quot;:[{&quot;colorbar&quot;:{&quot;outlinewidth&quot;:0,&quot;ticks&quot;:&quot;&quot;},&quot;type&quot;:&quot;contourcarpet&quot;}],&quot;heatmap&quot;:[{&quot;colorbar&quot;:{&quot;outlinewidth&quot;:0,&quot;ticks&quot;:&quot;&quot;},&quot;colorscale&quot;:[[0.0,&quot;#0d0887&quot;],[0.1111111111111111,&quot;#46039f&quot;],[0.2222222222222222,&quot;#7201a8&quot;],[0.3333333333333333,&quot;#9c179e&quot;],[0.4444444444444444,&quot;#bd3786&quot;],[0.5555555555555556,&quot;#d8576b&quot;],[0.6666666666666666,&quot;#ed7953&quot;],[0.7777777777777778,&quot;#fb9f3a&quot;],[0.8888888888888888,&quot;#fdca26&quot;],[1.0,&quot;#f0f921&quot;]],&quot;type&quot;:&quot;heatmap&quot;}],&quot;heatmapgl&quot;:[{&quot;colorbar&quot;:{&quot;outlinewidth&quot;:0,&quot;ticks&quot;:&quot;&quot;},&quot;colorscale&quot;:[[0.0,&quot;#0d0887&quot;],[0.1111111111111111,&quot;#46039f&quot;],[0.2222222222222222,&quot;#7201a8&quot;],[0.3333333333333333,&quot;#9c179e&quot;],[0.4444444444444444,&quot;#bd3786&quot;],[0.5555555555555556,&quot;#d8576b&quot;],[0.6666666666666666,&quot;#ed7953&quot;],[0.7777777777777778,&quot;#fb9f3a&quot;],[0.8888888888888888,&quot;#fdca26&quot;],[1.0,&quot;#f0f921&quot;]],&quot;type&quot;:&quot;heatmapgl&quot;}],&quot;histogram&quot;:[{&quot;marker&quot;:{&quot;pattern&quot;:{&quot;fillmode&quot;:&quot;overlay&quot;,&quot;size&quot;:10,&quot;solidity&quot;:0.2}},&quot;type&quot;:&quot;histogram&quot;}],&quot;histogram2d&quot;:[{&quot;colorbar&quot;:{&quot;outlinewidth&quot;:0,&quot;ticks&quot;:&quot;&quot;},&quot;colorscale&quot;:[[0.0,&quot;#0d0887&quot;],[0.1111111111111111,&quot;#46039f&quot;],[0.2222222222222222,&quot;#7201a8&quot;],[0.3333333333333333,&quot;#9c179e&quot;],[0.4444444444444444,&quot;#bd3786&quot;],[0.5555555555555556,&quot;#d8576b&quot;],[0.6666666666666666,&quot;#ed7953&quot;],[0.7777777777777778,&quot;#fb9f3a&quot;],[0.8888888888888888,&quot;#fdca26&quot;],[1.0,&quot;#f0f921&quot;]],&quot;type&quot;:&quot;histogram2d&quot;}],&quot;histogram2dcontour&quot;:[{&quot;colorbar&quot;:{&quot;outlinewidth&quot;:0,&quot;ticks&quot;:&quot;&quot;},&quot;colorscale&quot;:[[0.0,&quot;#0d0887&quot;],[0.1111111111111111,&quot;#46039f&quot;],[0.2222222222222222,&quot;#7201a8&quot;],[0.3333333333333333,&quot;#9c179e&quot;],[0.4444444444444444,&quot;#bd3786&quot;],[0.5555555555555556,&quot;#d8576b&quot;],[0.6666666666666666,&quot;#ed7953&quot;],[0.7777777777777778,&quot;#fb9f3a&quot;],[0.8888888888888888,&quot;#fdca26&quot;],[1.0,&quot;#f0f921&quot;]],&quot;type&quot;:&quot;histogram2dcontour&quot;}],&quot;mesh3d&quot;:[{&quot;colorbar&quot;:{&quot;outlinewidth&quot;:0,&quot;ticks&quot;:&quot;&quot;},&quot;type&quot;:&quot;mesh3d&quot;}],&quot;parcoords&quot;:[{&quot;line&quot;:{&quot;colorbar&quot;:{&quot;outlinewidth&quot;:0,&quot;ticks&quot;:&quot;&quot;}},&quot;type&quot;:&quot;parcoords&quot;}],&quot;pie&quot;:[{&quot;automargin&quot;:true,&quot;type&quot;:&quot;pie&quot;}],&quot;scatter&quot;:[{&quot;marker&quot;:{&quot;colorbar&quot;:{&quot;outlinewidth&quot;:0,&quot;ticks&quot;:&quot;&quot;}},&quot;type&quot;:&quot;scatter&quot;}],&quot;scatter3d&quot;:[{&quot;line&quot;:{&quot;colorbar&quot;:{&quot;outlinewidth&quot;:0,&quot;ticks&quot;:&quot;&quot;}},&quot;marker&quot;:{&quot;colorbar&quot;:{&quot;outlinewidth&quot;:0,&quot;ticks&quot;:&quot;&quot;}},&quot;type&quot;:&quot;scatter3d&quot;}],&quot;scattercarpet&quot;:[{&quot;marker&quot;:{&quot;colorbar&quot;:{&quot;outlinewidth&quot;:0,&quot;ticks&quot;:&quot;&quot;}},&quot;type&quot;:&quot;scattercarpet&quot;}],&quot;scattergeo&quot;:[{&quot;marker&quot;:{&quot;colorbar&quot;:{&quot;outlinewidth&quot;:0,&quot;ticks&quot;:&quot;&quot;}},&quot;type&quot;:&quot;scattergeo&quot;}],&quot;scattergl&quot;:[{&quot;marker&quot;:{&quot;colorbar&quot;:{&quot;outlinewidth&quot;:0,&quot;ticks&quot;:&quot;&quot;}},&quot;type&quot;:&quot;scattergl&quot;}],&quot;scattermapbox&quot;:[{&quot;marker&quot;:{&quot;colorbar&quot;:{&quot;outlinewidth&quot;:0,&quot;ticks&quot;:&quot;&quot;}},&quot;type&quot;:&quot;scattermapbox&quot;}],&quot;scatterpolar&quot;:[{&quot;marker&quot;:{&quot;colorbar&quot;:{&quot;outlinewidth&quot;:0,&quot;ticks&quot;:&quot;&quot;}},&quot;type&quot;:&quot;scatterpolar&quot;}],&quot;scatterpolargl&quot;:[{&quot;marker&quot;:{&quot;colorbar&quot;:{&quot;outlinewidth&quot;:0,&quot;ticks&quot;:&quot;&quot;}},&quot;type&quot;:&quot;scatterpolargl&quot;}],&quot;scatterternary&quot;:[{&quot;marker&quot;:{&quot;colorbar&quot;:{&quot;outlinewidth&quot;:0,&quot;ticks&quot;:&quot;&quot;}},&quot;type&quot;:&quot;scatterternary&quot;}],&quot;surface&quot;:[{&quot;colorbar&quot;:{&quot;outlinewidth&quot;:0,&quot;ticks&quot;:&quot;&quot;},&quot;colorscale&quot;:[[0.0,&quot;#0d0887&quot;],[0.1111111111111111,&quot;#46039f&quot;],[0.2222222222222222,&quot;#7201a8&quot;],[0.3333333333333333,&quot;#9c179e&quot;],[0.4444444444444444,&quot;#bd3786&quot;],[0.5555555555555556,&quot;#d8576b&quot;],[0.6666666666666666,&quot;#ed7953&quot;],[0.7777777777777778,&quot;#fb9f3a&quot;],[0.8888888888888888,&quot;#fdca26&quot;],[1.0,&quot;#f0f921&quot;]],&quot;type&quot;:&quot;surface&quot;}],&quot;table&quot;:[{&quot;cells&quot;:{&quot;fill&quot;:{&quot;color&quot;:&quot;#EBF0F8&quot;},&quot;line&quot;:{&quot;color&quot;:&quot;white&quot;}},&quot;header&quot;:{&quot;fill&quot;:{&quot;color&quot;:&quot;#C8D4E3&quot;},&quot;line&quot;:{&quot;color&quot;:&quot;white&quot;}},&quot;type&quot;:&quot;table&quot;}]},&quot;layout&quot;:{&quot;annotationdefaults&quot;:{&quot;arrowcolor&quot;:&quot;#2a3f5f&quot;,&quot;arrowhead&quot;:0,&quot;arrowwidth&quot;:1},&quot;autotypenumbers&quot;:&quot;strict&quot;,&quot;coloraxis&quot;:{&quot;colorbar&quot;:{&quot;outlinewidth&quot;:0,&quot;ticks&quot;:&quot;&quot;}},&quot;colorscale&quot;:{&quot;diverging&quot;:[[0,&quot;#8e0152&quot;],[0.1,&quot;#c51b7d&quot;],[0.2,&quot;#de77ae&quot;],[0.3,&quot;#f1b6da&quot;],[0.4,&quot;#fde0ef&quot;],[0.5,&quot;#f7f7f7&quot;],[0.6,&quot;#e6f5d0&quot;],[0.7,&quot;#b8e186&quot;],[0.8,&quot;#7fbc41&quot;],[0.9,&quot;#4d9221&quot;],[1,&quot;#276419&quot;]],&quot;sequential&quot;:[[0.0,&quot;#0d0887&quot;],[0.1111111111111111,&quot;#46039f&quot;],[0.2222222222222222,&quot;#7201a8&quot;],[0.3333333333333333,&quot;#9c179e&quot;],[0.4444444444444444,&quot;#bd3786&quot;],[0.5555555555555556,&quot;#d8576b&quot;],[0.6666666666666666,&quot;#ed7953&quot;],[0.7777777777777778,&quot;#fb9f3a&quot;],[0.8888888888888888,&quot;#fdca26&quot;],[1.0,&quot;#f0f921&quot;]],&quot;sequentialminus&quot;:[[0.0,&quot;#0d0887&quot;],[0.1111111111111111,&quot;#46039f&quot;],[0.2222222222222222,&quot;#7201a8&quot;],[0.3333333333333333,&quot;#9c179e&quot;],[0.4444444444444444,&quot;#bd3786&quot;],[0.5555555555555556,&quot;#d8576b&quot;],[0.6666666666666666,&quot;#ed7953&quot;],[0.7777777777777778,&quot;#fb9f3a&quot;],[0.8888888888888888,&quot;#fdca26&quot;],[1.0,&quot;#f0f921&quot;]]},&quot;colorway&quot;:[&quot;#636efa&quot;,&quot;#EF553B&quot;,&quot;#00cc96&quot;,&quot;#ab63fa&quot;,&quot;#FFA15A&quot;,&quot;#19d3f3&quot;,&quot;#FF6692&quot;,&quot;#B6E880&quot;,&quot;#FF97FF&quot;,&quot;#FECB52&quot;],&quot;font&quot;:{&quot;color&quot;:&quot;#2a3f5f&quot;},&quot;geo&quot;:{&quot;bgcolor&quot;:&quot;white&quot;,&quot;lakecolor&quot;:&quot;white&quot;,&quot;landcolor&quot;:&quot;#E5ECF6&quot;,&quot;showlakes&quot;:true,&quot;showland&quot;:true,&quot;subunitcolor&quot;:&quot;white&quot;},&quot;hoverlabel&quot;:{&quot;align&quot;:&quot;left&quot;},&quot;hovermode&quot;:&quot;closest&quot;,&quot;mapbox&quot;:{&quot;style&quot;:&quot;light&quot;},&quot;paper_bgcolor&quot;:&quot;white&quot;,&quot;plot_bgcolor&quot;:&quot;#E5ECF6&quot;,&quot;polar&quot;:{&quot;angularaxis&quot;:{&quot;gridcolor&quot;:&quot;white&quot;,&quot;linecolor&quot;:&quot;white&quot;,&quot;ticks&quot;:&quot;&quot;},&quot;bgcolor&quot;:&quot;#E5ECF6&quot;,&quot;radialaxis&quot;:{&quot;gridcolor&quot;:&quot;white&quot;,&quot;linecolor&quot;:&quot;white&quot;,&quot;ticks&quot;:&quot;&quot;}},&quot;scene&quot;:{&quot;xaxis&quot;:{&quot;backgroundcolor&quot;:&quot;#E5ECF6&quot;,&quot;gridcolor&quot;:&quot;white&quot;,&quot;gridwidth&quot;:2,&quot;linecolor&quot;:&quot;white&quot;,&quot;showbackground&quot;:true,&quot;ticks&quot;:&quot;&quot;,&quot;zerolinecolor&quot;:&quot;white&quot;},&quot;yaxis&quot;:{&quot;backgroundcolor&quot;:&quot;#E5ECF6&quot;,&quot;gridcolor&quot;:&quot;white&quot;,&quot;gridwidth&quot;:2,&quot;linecolor&quot;:&quot;white&quot;,&quot;showbackground&quot;:true,&quot;ticks&quot;:&quot;&quot;,&quot;zerolinecolor&quot;:&quot;white&quot;},&quot;zaxis&quot;:{&quot;backgroundcolor&quot;:&quot;#E5ECF6&quot;,&quot;gridcolor&quot;:&quot;white&quot;,&quot;gridwidth&quot;:2,&quot;linecolor&quot;:&quot;white&quot;,&quot;showbackground&quot;:true,&quot;ticks&quot;:&quot;&quot;,&quot;zerolinecolor&quot;:&quot;white&quot;}},&quot;shapedefaults&quot;:{&quot;line&quot;:{&quot;color&quot;:&quot;#2a3f5f&quot;}},&quot;ternary&quot;:{&quot;aaxis&quot;:{&quot;gridcolor&quot;:&quot;white&quot;,&quot;linecolor&quot;:&quot;white&quot;,&quot;ticks&quot;:&quot;&quot;},&quot;baxis&quot;:{&quot;gridcolor&quot;:&quot;white&quot;,&quot;linecolor&quot;:&quot;white&quot;,&quot;ticks&quot;:&quot;&quot;},&quot;bgcolor&quot;:&quot;#E5ECF6&quot;,&quot;caxis&quot;:{&quot;gridcolor&quot;:&quot;white&quot;,&quot;linecolor&quot;:&quot;white&quot;,&quot;ticks&quot;:&quot;&quot;}},&quot;title&quot;:{&quot;x&quot;:0.05},&quot;xaxis&quot;:{&quot;automargin&quot;:true,&quot;gridcolor&quot;:&quot;white&quot;,&quot;linecolor&quot;:&quot;white&quot;,&quot;ticks&quot;:&quot;&quot;,&quot;title&quot;:{&quot;standoff&quot;:15},&quot;zerolinecolor&quot;:&quot;white&quot;,&quot;zerolinewidth&quot;:2},&quot;yaxis&quot;:{&quot;automargin&quot;:true,&quot;gridcolor&quot;:&quot;white&quot;,&quot;linecolor&quot;:&quot;white&quot;,&quot;ticks&quot;:&quot;&quot;,&quot;title&quot;:{&quot;standoff&quot;:15},&quot;zerolinecolor&quot;:&quot;white&quot;,&quot;zerolinewidth&quot;:2}}},&quot;xaxis&quot;:{&quot;anchor&quot;:&quot;y&quot;,&quot;domain&quot;:[0.0,1.0],&quot;title&quot;:{&quot;text&quot;:&quot;cf_std&quot;},&quot;tickformat&quot;:&quot;.0%&quot;},&quot;yaxis&quot;:{&quot;anchor&quot;:&quot;x&quot;,&quot;domain&quot;:[0.0,1.0],&quot;title&quot;:{&quot;text&quot;:&quot;PV&quot;}},&quot;legend&quot;:{&quot;tracegroupgap&quot;:0},&quot;margin&quot;:{&quot;t&quot;:60},&quot;height&quot;:500,&quot;width&quot;:800},                        {&quot;responsive&quot;: true}                    )                };                            &lt;/script&gt;        &lt;/div&gt;
&lt;/body&gt;
&lt;/html&gt;

&lt;p&gt;We can see that volatility in the growth rate (slightly) reduces the expected intrinsic value of the company.&lt;/p&gt;

&lt;p&gt;This phenomenon is closely related to &lt;strong&gt;variance drag&lt;/strong&gt; – mathematically, for growth rates drawn from a normal distribution $N(\mu, \sigma^2)$, the expected CAGR (geometric mean growth) is $\mu - \frac 1 2 \sigma^2$. This concept is nicely explained in one of Benn Eifert’s classic &lt;a href=&quot;https://twitter.com/bennpeifert/status/1362908508237090816?lang=en-GB&quot;&gt;tweet threads&lt;/a&gt;. The situation is not quite the same as the typical variance drag setup  because we are not considering the compounding growth of an investment: in particular, we are not saying that the company’s NPV is growing at a compounding rate, only its cash flows.&lt;/p&gt;

&lt;p&gt;To summarise, we need to make the subtle distinction between an uncertain cash flow growth rate and a volatile stream of cash flows. Because of convexity, the former is good. But because of variance drag, the latter is bad. In real life, both will show up at the same time and pull in different directions!&lt;/p&gt;

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

&lt;p&gt;The main lesson of this post is that you should pay attention to convexity because it turns uncertainty into increased expected value. When you own a convex payoff, you want uncertainty in the input parameter!&lt;/p&gt;

&lt;p&gt;For equity analysts reading this, a more tangible takeaway is as follows: if you can see a credible blue-sky scenario for a company (e.g. a scenario that leads to a much higher growth rate or lower cost of capital), because of the convexity of DCFs, that blue-sky scenario might be so accretive to the intrinsic value of an asset that the bear case is irrelevant.&lt;/p&gt;

&lt;p&gt;More generally, this post is a teaser for the type of insight that can be gleaned when we start considering distributional inputs to DCFs. I’m fascinated by the idea of using old tools in new ways and hope to explore other aspects of “probabilistic valuation” in future posts.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>How I Read Books</title>
   <link href="https://reasonabledeviations.com/2022/01/24/reading-philosophy/"/>
   <updated>2022-01-24T00:00:00+00:00</updated>
   <id>https://reasonabledeviations.com/2022/01/24/reading-philosophy</id>
   <content type="html">&lt;p&gt;I recently launched a &lt;a href=&quot;https://robertreads.notion.site&quot;&gt;website&lt;/a&gt; to open-source some of my book reviews. To accompany this, I’d like to share some thoughts on my philosophy of reading books, and how my current workflow reflects this philosophy.&lt;/p&gt;

&lt;!--more--&gt;

&lt;p&gt;This post primarily focuses on non-fiction because it is an area in which I think I have “edge” – I am confident in my ability to extract value from non-fiction books. I do enjoy fiction (and discuss it briefly at the end), but I don’t feel qualified to teach someone how to appreciate it.&lt;/p&gt;

&lt;h2 id=&quot;contents&quot;&gt;Contents&lt;/h2&gt;

&lt;!-- TOC --&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;#motivation&quot;&gt;Motivation&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#interlude-spaced-repetition&quot;&gt;Interlude: spaced repetition&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#spaced-repetition-and-reading&quot;&gt;Spaced repetition and reading&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#what-to-read&quot;&gt;What to read&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#reading-the-book&quot;&gt;Reading the book&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#processing-highlights-a-week-after-finishing-the-book&quot;&gt;Processing highlights (a week after finishing the book)&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#writing-a-review&quot;&gt;Writing a review&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#second-brain&quot;&gt;Second brain&lt;/a&gt;
    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#summarise-the-book&quot;&gt;Summarise the book&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#extract-atomic-concepts&quot;&gt;Extract atomic concepts&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#growth&quot;&gt;Growth&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#fiction&quot;&gt;Fiction&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#conclusion&quot;&gt;Conclusion&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;!-- /TOC --&gt;

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

&lt;p&gt;What do I want from a non-fiction book? I distil this into my &lt;em&gt;reading triplet&lt;/em&gt;:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;Retention&lt;/strong&gt;: remembering what key ideas the book contains.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Understanding&lt;/strong&gt;: internalising the ideas well enough to explain them to someone.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Contextualisation&lt;/strong&gt;: knowing how the book relates to other books and how its ideas fit into my “second brain”.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;My reading workflow has evolved over several years and hundreds of books to optimise for these goals. It has &lt;em&gt;not&lt;/em&gt; evolved to maximise the number of books read, nor my retention of an individual book. I say “evolved” rather than built because it’s been an organic and iterative process, that is still being improved!&lt;/p&gt;

&lt;p&gt;I truly believe that reading books, when done properly, can be a competitive advantage.&lt;/p&gt;

&lt;h2 id=&quot;interlude-spaced-repetition&quot;&gt;Interlude: spaced repetition&lt;/h2&gt;

&lt;p&gt;Before continuing, I will take a slight detour to discuss spaced repetition, a key component underlying my reading philosophy. If you are already familiar with the concept, feel free to skip this section.&lt;/p&gt;

&lt;p&gt;After learning something new, we tend to forget it over time. In the late 19th century, psychologist Hermann Ebbinghaus tried to quantify this experimentally (rare for psychologists at the time!). One of the results of his research was the &lt;strong&gt;Ebbinghaus forgetting curve&lt;/strong&gt;, which plots how retention changes as a function of time (it is roughly $e^{-kt}$).&lt;/p&gt;

&lt;p&gt;More importantly, Ebbinghaus figured out that active recall of the content (testing yourself) not only refreshes the forgetting curve but makes it decay more slowly subsequently.&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/reading/ebbinghaus.png&quot; style=&quot;width:80%;&quot; /&gt;
&lt;figcaption&gt;Image from Bo Ae Chun&lt;/figcaption&gt;
&lt;/center&gt;

&lt;p&gt;This leads to the idea of “spaced repetition” – refreshing your knowledge at particular intervals to maximise retention. Spaced repetition is the core philosophy of flashcard systems like Anki and RemNote, which I &lt;em&gt;heavily&lt;/em&gt; relied on during my time at university and truly were a meaningful contributor to whatever academic success I may have had.&lt;/p&gt;

&lt;h2 id=&quot;spaced-repetition-and-reading&quot;&gt;Spaced repetition and reading&lt;/h2&gt;

&lt;p&gt;How does the idea of spaced repetition apply to reading books? In a personal productivity workflow, repetition is a bad thing. It takes up time, adds no value, and should be automated away. But if &lt;em&gt;retention&lt;/em&gt; is a variable for which you are optimising, repetition is a good thing. As such, my reading workflow is deliberately inefficient (more on this later).&lt;/p&gt;

&lt;p&gt;This is why I refuse to use Blinkist or read summaries in place of reading books (despite being the kind of person who consumes youtube/podcasts at 1.5-2x speeds). In a typical non-fiction book, the author spends several chapters building up the thesis and providing motivation for it, with loads of examples along the way. The repetition aids retention and the examples help you contextualise the information. This philosophy also suggests that you shouldn’t necessarily read books in one sitting – so I typically choose to read several books in parallel rather than reading sequentially.&lt;/p&gt;

&lt;p&gt;Note that while I would not use book summaries to replace books – they can be excellent &lt;em&gt;accompaniments&lt;/em&gt;. In the spirit of spaced repetition, listening to Blinkist or reading a summary a couple of weeks after you’ve read the main book is a great way of recalling the core ideas of the book.&lt;/p&gt;

&lt;h2 id=&quot;what-to-read&quot;&gt;What to read&lt;/h2&gt;

&lt;p&gt;I use Goodreads to track all of my reading: books I want to read, am reading, and have read.&lt;/p&gt;

&lt;p&gt;I’m liberal when it comes to adding books to my “Want to Read” shelf: I add a book whenever someone gives me a recommendation, a book is referenced by people I respect, or I come across it randomly and it piques my interest. I’ve currently got about 100 books on this list.&lt;/p&gt;

&lt;p&gt;When I’m ready to start a new book, I look at the reading list and consider some of the following factors to decide what to read:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Has the book been strongly recommended by credible people?&lt;/li&gt;
  &lt;li&gt;Is the book being referenced by other books I’ve enjoyed? This was why I reluctantly read Taleb’s books.&lt;/li&gt;
  &lt;li&gt;I keep a rough mental list of topics I’m interested in (e.g. finance, decision making, science fiction). Any classics in these areas are always fair game.&lt;/li&gt;
  &lt;li&gt;If I’ve read a couple of books on the same topic in a row, I’ll try to rotate to something else.&lt;/li&gt;
  &lt;li&gt;It’s important to sprinkle in some “rogue” books, completely unrelated to my interests. For example, I recently picked up a book on Danish interior design called &lt;em&gt;The Book of Hygge&lt;/em&gt; (my review &lt;a href=&quot;https://robertreads.notion.site/robertreads/d1aa189b12e547799c3a5954b289cb5d?v=c344d511453b46eebace4671761ae631&amp;amp;p=a904e1e1348843e3be343ac2a1a8bc4a&quot;&gt;here&lt;/a&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ultimately, I’m not as deliberate as I could be with book selection. I think it’s fine to be somewhat organic because it allows your minds to explore “threads” and build connections. Perhaps more importantly, it ensures that reading remains an enjoyable activity. If you have a predefined list of books that you are making yourself read, it becomes a chore – the most dangerous thing one could do in building a reading workflow is to become sick of reading.&lt;/p&gt;

&lt;p&gt;Once I’ve decided what to read, I create a blank Notion page for the book (literally just the title).&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/reading/blank_hygge.png&quot; style=&quot;width:80%;&quot; /&gt;
&lt;/center&gt;

&lt;h2 id=&quot;reading-the-book&quot;&gt;Reading the book&lt;/h2&gt;

&lt;p&gt;As much as I love physical books, I read most of my books on Kindle. Even if I had a physical book, I would probably prefer to read it on Kindle (though I will sometimes get the physical copy to adorn my shelf!). The reason for this is the highlighting feature – I can’t understate how much of a game-changer this has been for me.&lt;/p&gt;

&lt;p&gt;My guiding philosophy when making highlights is the “funnel”. I highlight very liberally when I’m reading the book:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Interesting new concepts&lt;/li&gt;
  &lt;li&gt;Good explanations for concepts that I was already familiar with&lt;/li&gt;
  &lt;li&gt;Anything that feels like a good summary of a key idea&lt;/li&gt;
  &lt;li&gt;Phrases I like (e.g. particularly well-constructed sentences)&lt;/li&gt;
  &lt;li&gt;Things that make me laugh&lt;/li&gt;
  &lt;li&gt;Things I don’t understand&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But highlighting something once doesn’t obligate me to care about it in future. In the subsequent stages of my reading workflow, I delete lots of highlights, winnowing them down to only the key ideas.&lt;/p&gt;

&lt;p&gt;While reading the book, I also make brief notes in the Notion page if something strikes me as a key idea (though I should be able to reconstruct this from my highlights). I also start thinking about the shape of my book review, though at this stage, the notes tend to be quite scattered.&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/reading/hygge_rough.png&quot; style=&quot;width:80%;&quot; /&gt;
&lt;/center&gt;

&lt;h2 id=&quot;processing-highlights-a-week-after-finishing-the-book&quot;&gt;Processing highlights (a week after finishing the book)&lt;/h2&gt;

&lt;p&gt;After finishing the book, I let it rest for a while, focussing on one of the other books I’ve been concurrently reading, or just cracking on with other things in life.&lt;/p&gt;

&lt;p&gt;About a week later, I start processing the highlights. Firstly, I get the highlights from my Kindle onto my computer, using a little &lt;a href=&quot;https://github.com/robertmartin8/kindleclippings&quot;&gt;python script&lt;/a&gt; I wrote.&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/reading/clippings_folder.png&quot; style=&quot;width:80%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;I then manually copy these highlights into Notion. Note: there are services like &lt;a href=&quot;http://clippings.io&quot;&gt;clippings.io&lt;/a&gt; or Readwise that can do all this for you, but I enjoy my hacky process and again, in the spirit of spaced repetition, the inefficiency of manually copying highlights isn’t a bad thing.&lt;/p&gt;

&lt;p&gt;At this point, in my Notion page for the book, I’ll have some miscellaneous bullet points as well as a tonne of highlights.&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/reading/snowcrash_raw.png&quot; style=&quot;width:80%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;The next order of business is to go through the highlights, culling the redundancies. For example, I may have highlighted the author’s first reference to a concept, only to realise that they offer a better summary at the end of the chapter. I only keep the latter.&lt;/p&gt;

&lt;p&gt;After doing this, I try to group the highlights into different themes. This is a difficult task because it requires you to think on a more abstract level about the key points in the book and how they link together. I am often unable to do it one go; I try for a bit, get frustrated and give up, then return later. I strongly believe that this process, though uncomfortable, significantly improves my understanding of the book. Moreover, it makes it much easier for me to find a specific idea months later.&lt;/p&gt;

&lt;p&gt;I also put in the effort to annotate some of the highlights, making a note of why it was impactful to me, what concept it reminds me of, what part of my life it applies to, etc. I use purple to emphasise certain parts of the highlight and write my annotations in italics below.&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/reading/clean_highlights.png&quot; style=&quot;width:80%;&quot; /&gt;
&lt;/center&gt;

&lt;h2 id=&quot;writing-a-review&quot;&gt;Writing a review&lt;/h2&gt;

&lt;p&gt;Writing a review is one of the hardest parts of this process. It entails wrestling with the content and mulling over the categorised highlights to not only understand the concepts the author is presenting but to critically review their originality and how they fit in more broadly with other books.&lt;/p&gt;

&lt;p&gt;I don’t have much advice about writing reviews. It’s something that comes with practice and having written 100+ reviews I still struggle. But for each review I try to include:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;The core idea of the book and maybe a couple of my favourite quotes&lt;/li&gt;
  &lt;li&gt;How this book is similar/different to others in the genre&lt;/li&gt;
  &lt;li&gt;Any interesting links I observed between this book and others (see the section on &lt;a href=&quot;#second-brain&quot;&gt;Second brain&lt;/a&gt; below)&lt;/li&gt;
  &lt;li&gt;How much I enjoyed the book&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At the end of this process, I post the book to Goodreads, and now to my review site as well.&lt;/p&gt;

&lt;p&gt;These little things gamify the process, providing a little dopamine hit.  Remember – dopamine is the chemical for &lt;em&gt;wanting more&lt;/em&gt;, not the chemical for satisfaction (that’s serotonin), so there’s value in associating dopamine with something productive.&lt;/p&gt;

&lt;h2 id=&quot;second-brain&quot;&gt;Second brain&lt;/h2&gt;

&lt;p&gt;At this point, I have a book review and categorised highlights (see my &lt;a href=&quot;https://robertreads.notion.site/&quot;&gt;book reviews site&lt;/a&gt; for examples). To maximise the value I get from the book as per my reading triplet, I then try to integrate the book with my second brain.&lt;/p&gt;

&lt;p&gt;A second brain is a network of concepts and ideas that are relevant to your life and interests. I run my second brain in &lt;a href=&quot;https://obsidian.md/&quot;&gt;Obsidian&lt;/a&gt;. To fully describe my system would require a &lt;a href=&quot;/2022/04/18/molecular-notes-part-1/&quot;&gt;separate blog post&lt;/a&gt;, but it is loosely based on the Zettelkasten method, best described in Sonke Ahrens’ book &lt;em&gt;How to Take Smart Notes&lt;/em&gt; (my review &lt;a href=&quot;https://robertreads.notion.site/robertreads/d1aa189b12e547799c3a5954b289cb5d?v=c344d511453b46eebace4671761ae631&amp;amp;p=73d4990369204dabb865543214980788&quot;&gt;here&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Concretely, my process for integrating the book with my second brain is as follows:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Summarise the book&lt;/li&gt;
  &lt;li&gt;Extract atomic concepts&lt;/li&gt;
  &lt;li&gt;Growth&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;summarise-the-book&quot;&gt;Summarise the book&lt;/h3&gt;

&lt;p&gt;The goal is to distil the author’s core thesis (maybe with a couple of examples to aid memory) in your own words. The points should not be original, but it’s important to use your own words, otherwise it is easy to copy-paste ideas without internalising them.&lt;/p&gt;

&lt;p&gt;Below is an example of my summary for &lt;em&gt;Adaptive Markets&lt;/em&gt; (my review &lt;a href=&quot;https://robertreads.notion.site/robertreads/d1aa189b12e547799c3a5954b289cb5d?v=c344d511453b46eebace4671761ae631&amp;amp;p=b406e6c2b4904444944f04b899944ba9&quot;&gt;here&lt;/a&gt;). I write this summary in Obsidian and sometimes duplicate it on my Notion site for the benefit of other readers.&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/reading/adaptive_markets.png&quot; style=&quot;width:70%;&quot; /&gt;
&lt;/center&gt;

&lt;h3 id=&quot;extract-atomic-concepts&quot;&gt;Extract atomic concepts&lt;/h3&gt;

&lt;p&gt;My mental model is that a non-fiction book is made of concepts, examples, and commentary. The concepts can either be original to the author, or well-established. Examples are provided by the author to illustrate the concepts. In the commentary, the author discusses how those particular concepts relate to other ideas, or provides a critical perspective on those concepts.&lt;/p&gt;

&lt;p&gt;My note-taking approach reflects this model. For example, I try to extract the concepts and examples into individual notes. This avoids unnecessary duplication because they often show up in other books. In the &lt;em&gt;Adaptive Markets&lt;/em&gt; screenshot above, the purple text denotes a link to an individual note I’ve made. The &lt;em&gt;efficient markets hypothesis&lt;/em&gt; is a concept discussed in several books, so I created an atomic note for it; the bottom-right of the screen shows all the books that point to this concept. Likewise, the &lt;em&gt;Gazzaniga split-brain experiment&lt;/em&gt; is frequently used as an example in books on decision-making to illustrate our powers of rationalisation, so it deserves its own note.&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/reading/emh.png&quot; style=&quot;width:100%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;This approach also allows me to better understand how the concept fits in with the idea landscape, for example, by looking at Obsidian’s graph view (in the top-right of the screenshot).&lt;/p&gt;

&lt;h3 id=&quot;growth&quot;&gt;Growth&lt;/h3&gt;

&lt;p&gt;Now that the book is in the second brain, the process doesn’t finish. It instead becomes a “seed” in the garden, whose nourishment is derived from linking it to other concepts.&lt;/p&gt;

&lt;p&gt;Using the graph tool, I look through related notes – book reviews, summaries of different web articles, ideas I’ve had – to try and find links. For example, after reading &lt;em&gt;Ask Your Developer&lt;/em&gt;, a book about managing software firms written by Twilio CEO Jeff Lawson (my review &lt;a href=&quot;https://robertreads.notion.site/robertreads/d1aa189b12e547799c3a5954b289cb5d?v=c344d511453b46eebace4671761ae631&amp;amp;p=a96677f597174b958a43d9db477787e2&quot;&gt;here&lt;/a&gt;), I noticed that there were some interesting links to the design of decentralised systems, and therefore a link to German military strategy (&lt;em&gt;auftragstaktik&lt;/em&gt;). I add these ideas to the summary page, or if they are meaningful enough, create a separate note for them.&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/reading/askyourdev.png&quot; style=&quot;width:60%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;This is the key to building $O(N^2)$ knowledge growth – each incremental piece of content you consume has &lt;em&gt;N&lt;/em&gt; other ideas/atoms/sources to connect to.&lt;/p&gt;

&lt;p&gt;A key part of growth is discussion! Nothing sharpens your understanding of a book, highlights your misunderstandings, or inspires new ideas like a discussion with someone else. They don’t need to have read the book – the process of trying to convey the key message of the book to someone else really helps your mind distil it, as per the Feynman method. Oftentimes after discussing a book with my friends I’ll come back and modify my summary to reflect newfound understanding.&lt;/p&gt;

&lt;h2 id=&quot;fiction&quot;&gt;Fiction&lt;/h2&gt;

&lt;p&gt;There are several reasons to read fiction.&lt;/p&gt;

&lt;p&gt;Firstly, for “culture”. A cynical take is that the only purpose of reading the classics is to tell people you’ve read them. This probably used to be a source of motivation for me when I was younger, but I’ve matured a bit: it’s important to read classic/popular books so that you can understand references, and so that you can enrich conversations by pointing out a parallel to a well-known tale.&lt;/p&gt;

&lt;p&gt;Secondly, fiction books often do a good job of illustrating philosophical concepts. Rather than reading a political philosophy textbook to learn the definition of an authoritarian state, you could instead read a dystopian classic. &lt;em&gt;1984&lt;/em&gt;, &lt;em&gt;Brave New World&lt;/em&gt;, and &lt;em&gt;Fahrenheit 451&lt;/em&gt; give three completely different perspectives on an authoritarian state; having read all three allows you to make intelligent commentary on what characteristics the real world shares with each of them, and perhaps even gain insight into where we may be headed. I have learnt more about Japanese history from reading fiction (e.g. the work of &lt;a href=&quot;https://robertreads.notion.site/robertreads/d1aa189b12e547799c3a5954b289cb5d?v=c344d511453b46eebace4671761ae631&amp;amp;p=72aa32e8aa054352a145b732e7cf5b97&quot;&gt;Yukio Mishima&lt;/a&gt;), more about Russian history from reading Dostoevsky, than I would have learnt from non-fiction history books. My perspectives on AI, technology, and the long term future of humanity have been influenced by science fiction.&lt;/p&gt;

&lt;p&gt;The last reason, of course, is for the entertainment value. This is purely subjective, but I think reading fiction books provides a different kind of entertainment to watching TV. It requires more effort but can provide deeper appreciation. In any case, it’s not a binary decision: one should read fiction books and watch TV!&lt;/p&gt;

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

&lt;p&gt;My processes are in “perpetual beta” (a concept from &lt;a href=&quot;https://robertreads.notion.site/Superforecasting-7e68c63d4aa94ba4a0748ec0a1a78952&quot;&gt;Superforecasting&lt;/a&gt; by Philip Tetlock) – I’m constantly trying to improve my system for reading books. For example, I’m terrible at giving up books; I feel a kind of petty competition where I refuse to let the author “defeat me”, so once I start a book I feel obliged to finish it. This is clearly suboptimal – there are poorly written books out there and I could unlock a lot of value by giving up and starting a better one. To improve on this, as part of my 2022 reading goals, I have resolved to give up a certain number of books.&lt;/p&gt;

&lt;p&gt;The last thing I’ll say is that (to paraphrase Orwell) you should ignore all of this advice sooner than do something barbarous – not reading! Even reading books without making highlights or taking notes is valuable; I didn’t really make highlights or take notes for the first couple of hundred books I read, and while I certainly would have gotten far more out of them had I applied my current workflow, I don’t regret reading those books.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Probability matching and Kelly betting</title>
   <link href="https://reasonabledeviations.com/2022/01/10/probability-matching-kelly/"/>
   <updated>2022-01-10T00:00:00+00:00</updated>
   <id>https://reasonabledeviations.com/2022/01/10/probability-matching-kelly</id>
   <content type="html">&lt;p&gt;In this post, we discuss a cognitive bias called probability matching, explaining how it is rational from a population perspective. We then make an analogy to the Kelly criterion, a betting strategy that finds widespread use in both gambling and finance.&lt;/p&gt;

&lt;!--more--&gt;

&lt;h2 id=&quot;probability-matching&quot;&gt;Probability matching&lt;/h2&gt;

&lt;p&gt;I probably don’t need to tell you that humans aren’t perfectly rational utility-maximisers – thanks to the work of heavyweights like Kahneman and Tversky, the existence of cognitive biases is widely appreciated (in theory, if not in practice). Books like &lt;a href=&quot;https://robertreads.notion.site/robertreads/d1aa189b12e547799c3a5954b289cb5d?v=c344d511453b46eebace4671761ae631&amp;amp;p=58b61e6b71d9441aa62d8c9a328a3487&quot;&gt;&lt;em&gt;Thinking, Fast and Slow&lt;/em&gt;&lt;/a&gt; catalogue some of these biases.&lt;/p&gt;

&lt;p&gt;Today I want to discuss one in particular: &lt;strong&gt;probability matching&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Consider a weighted coin that comes up heads 75% of the time. I am going to flip this coin many times; before each flip, you can guess whether it will be heads or tails. Your goal is to maximise the number of correct guesses. What is your strategy?&lt;/p&gt;

&lt;p&gt;The optimal strategy is to guess H every time, which should mean that 75% of your guesses are correct.&lt;/p&gt;

&lt;p&gt;But animals (humans included) seldom choose this strategy&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;. Rather, they guess H about 75% of the time, matching their guessing frequency to the probability – probability matching.&lt;/p&gt;

&lt;p&gt;Let the coin have a known $P(\text{heads}) \equiv p &amp;gt; 0.5$ and consider a strategy in which we choose heads with frequency &lt;em&gt;f&lt;/em&gt;. Assume that we receive a payout of +1 for a correct guess and -1 for an incorrect guess. The expected value per coin toss is $EV = 1 - 2(f+p-2fp)$.&lt;/p&gt;

&lt;p&gt;In the “optimal” case $f=1$ (i.e we always guess H provided $P(\text{heads}) &amp;gt; 0.5$), we gets $EV = 2p-1$. Probability matching, in which we set $f=p$, yields $(2p-1)^2$.&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/prob_matching/strat_comparison.png&quot; style=&quot;width:80%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;Hence probability matching is a cognitive bias to the extent that it is a mathematically suboptimal strategy that results in a lower expected value.&lt;/p&gt;

&lt;h2 id=&quot;the-rationality-of-probability-matching&quot;&gt;The rationality of probability matching&lt;/h2&gt;

&lt;p&gt;In their 2011 paper &lt;em&gt;The Origin of Behaviour&lt;/em&gt;, Brennan and Lo make the wonderful insight that while probability matching is suboptimal from an individual level, it may be rational from a &lt;em&gt;population&lt;/em&gt; level.&lt;/p&gt;

&lt;p&gt;To motivate this, we’ll move from coin flips to a more ecological example.&lt;/p&gt;

&lt;p&gt;You are an arctic fox trying to decide whether or not to change your coat from brown to white for the winter. If you have the wrong coat colour, i.e you turn white but it doesn’t snow, or you stay brown and it does snow, you get eaten by predators (sorry, I don’t make the rules). If you have the correct coat colour you survive and reproduce. The probability of no snow in winter is 75%. What is your strategy?&lt;/p&gt;

&lt;p&gt;The best strategy for an individual fox is to always stay brown as long as $P(\text{no snow}) &amp;gt; 0.5$.&lt;/p&gt;

&lt;p&gt;But consider the population perspective: if every fox stayed brown, then it would only take one winter with snow for the entire fox population to be wiped out. So the optimal strategy at a population level &lt;em&gt;cannot&lt;/em&gt; be for every fox to stay brown, even though that is optimal for an individual!&lt;/p&gt;

&lt;p&gt;Hopefully, this example illustrates intuitively why probability matching is a plausible strategy at the population level. In the next section, we’ll explore this idea mathematically.&lt;/p&gt;

&lt;h2 id=&quot;optimising-population-growth&quot;&gt;Optimising population growth&lt;/h2&gt;

&lt;p&gt;Let’s build out the fox model a little more. We make the following assumptions:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;The probability that it doesn’t snow in winter is $p$; the probability it does snow is $1-p$&lt;/li&gt;
  &lt;li&gt;If a fox survives the winter (by having the right coat colour), it reproduces to make $W-1$ offspring, such that one fox becomes &lt;em&gt;W&lt;/em&gt; foxes (&lt;em&gt;W&lt;/em&gt; for “win”!)&lt;/li&gt;
  &lt;li&gt;The “objective” is to maximise the population.&lt;/li&gt;
  &lt;li&gt;Before winter, we have a population of $n_0$. A fraction $f$ of this population remains with brown coats, the other $1-f$ change to white coats.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;How does the population evolve after one winter? If it doesn’t snow, the $fn_0$ brown foxes survive and reproduce to become $fn_0 \cdot W$ foxes. If the winter is snowy, the $(1-f) n_0$ white foxes reproduce to become $(1-f)n_0 \cdot W$ foxes.&lt;/p&gt;

&lt;p&gt;To summarise, the population dynamics are:&lt;/p&gt;

\[n_0 \to \begin{cases} f n_0 \cdot W,  &amp;amp; \text{no snow} \\ (1-f)n_0 \cdot W, &amp;amp; \text{snow} \end{cases}\]

&lt;p&gt;Over &lt;em&gt;T&lt;/em&gt; winters, we expect snow in $(1-p)T$ winters  and no snow in $pT$ winters. The overall population after these &lt;em&gt;T&lt;/em&gt; winters is then:&lt;/p&gt;

\[n = n_0 \cdot (fW)^{pT} \cdot ((1-f)W)^{(1-p)T}\]

&lt;p&gt;The key question – what fraction $f^* $ of the population should stay brown to maximise population growth – is then just a calculus problem. We just differentiate &lt;em&gt;n&lt;/em&gt; w.r.t &lt;em&gt;f&lt;/em&gt; and set it to zero to find the optimal $f^*$ (though for mathematical convenience, we first take logs):&lt;/p&gt;

\[\begin{aligned}
    \ln n &amp;amp;= \ln n_0 + pT (\ln f + \ln W)  + (1-p)T (\ln (1-f) + \ln W) \\
    \frac{\partial \ln n}{\partial f} &amp;amp;= \frac{pT}{f} - \frac{(1-p)T}{1-f} = 0 \\
    &amp;amp;\implies \frac p f = \frac{1-p}{1-f} \\
    &amp;amp;\implies \frac{p}{1-p} = \frac{f}{1-f} \\
    &amp;amp;\implies f^* = p
\end{aligned}\]

&lt;p&gt;So a fraction $f^* = p$ of the population should stay brown, leaving them best adapted to a winter without snow (which occurs with probability $p$) – this is exactly the probability matching rule!&lt;/p&gt;

&lt;h2 id=&quot;kelly-betting&quot;&gt;Kelly betting&lt;/h2&gt;

&lt;p&gt;We now modify the model slightly.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;In a snowy winter: brown foxes die, white foxes survive but don’t reproduce.&lt;/li&gt;
  &lt;li&gt;In a non-snowy winter: all foxes survive, but only the brown foxes reproduce (to make $W-1$ offspring as before)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This model is admittedly somewhat contrived, but is vaguely plausible:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;A snowy winter is likely a tougher environment with less food available (so even the well-adapted white foxes may not be able to reproduce easily)&lt;/li&gt;
  &lt;li&gt;In a non-snowy winter, adaptation still matters, so the maladapted white foxes may not be desirable mates.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With this model, the population dynamics are as follows (&lt;em&gt;f&lt;/em&gt; is again the fraction of brown foxes):&lt;/p&gt;

\[n_0 \to \begin{cases} n_0 (1 + fW), &amp;amp;\text{no snow} \\ n_0 (1-f), &amp;amp; \text{snow} \end{cases}\]

&lt;p&gt;The population growth equation is:&lt;/p&gt;

\[n = n_0 (1+fW)^{pT} (1 - f)^{(1-p)T}\]

&lt;p&gt;We repeat the procedure as before, choosing the $f^*$ that maximises $\ln n$.&lt;/p&gt;

\[\begin{aligned}
    \ln n &amp;amp;= \ln n_0 + pT \ln (1+fW)  + (1-p)T \ln (1-f) \\
    \frac{\partial \ln n}{\partial f} &amp;amp;= 0 \implies \frac{pW}{1+fW} - \frac{1-p}{1-f} = 0 \\
    &amp;amp;\implies f^* = p - \frac{(1-p)}{W}
\end{aligned}\]

&lt;p&gt;This is identical to Kelly betting, with the following mapping:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;em&gt;n&lt;/em&gt; is the value of the bankroll&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;f&lt;/em&gt; is the fraction of the bankroll that we bet&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;W&lt;/em&gt; is the odds of the bet – the factor by which the stake grows if we win.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We can then apply the plethora of known results for the Kelly betting scheme&lt;sup id=&quot;fnref:2&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:2&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;2&lt;/a&gt;&lt;/sup&gt; to understand the long-run growth trajectory of the population, the time it takes for this strategy to dominate others, etc.&lt;/p&gt;

&lt;p&gt;However, note that in this model, probability matching is &lt;em&gt;never&lt;/em&gt; optimal, since there is no finite &lt;em&gt;W&lt;/em&gt; such that $f^* = p$. That said, we see behaviour similar to probability matching: for $p = 0.75$ and a reproductive factor $W = 5$, the optimal fraction is $f^* = 0.7$.&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/prob_matching/kelly.png&quot; style=&quot;width:80%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;To take a step back, the reason why this ecological model is similar to Kelly betting is that in both cases the objective is to maximise the geometric growth rate – while Brennan and Lo (2011) observe the same, they don’t elucidate the link between probability matching and the Kelly frequency.&lt;/p&gt;

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

&lt;p&gt;We’ve explored some simple models to illustrate that probability matching is rational from a population perspective. The population seeks to maximise its geometric growth rate, and therein lies the link to Kelly betting.&lt;/p&gt;

&lt;p&gt;To clarify, probability matching is still irrational for an individual – if you are a fox you should change to a white coat if you think that there is a greater than 50% chance of a snowy winter. But your genes would be telling you otherwise!&lt;/p&gt;

&lt;p&gt;This topic is so interesting to me because it is a first-principles derivation of a cognitive bias: it would be awesome if we could explain the full spectrum of cognitive biases at a biological level (be that neuroscience or evolution). Prospect theory is really only a phenomenological theory – it uses the S-shaped utility curve to explain several empirically observed biases, without rigorously explaining where that utility function might come from.&lt;/p&gt;

&lt;p&gt;Lastly, a comment on an important extension. In this post, we found that the probability matching rule arises from uncertainty about the future state of the world, but we have completely neglected &lt;em&gt;parameter&lt;/em&gt; uncertainty. In other words, we assumed that we know &lt;em&gt;p&lt;/em&gt;, while in reality we may not know the bias of the coin, or the probability that it snows – these are parameters that must be estimated under uncertainty. In this case, answering questions about optimal strategies becomes difficult. It is an explore vs exploit tradeoff – you need to estimate &lt;em&gt;p&lt;/em&gt; while simultaneously making decisions whose payoff depends on &lt;em&gt;p&lt;/em&gt;! Techniques like reinforcement learning become applicable here, and ultimately these are likely to be a key part of understanding where cognitive biases come from.&lt;/p&gt;

&lt;h3 id=&quot;references&quot;&gt;References&lt;/h3&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;Benoit Hardy-Vallee gives a nice overview of some of the evidence in this &lt;a href=&quot;https://naturalrationality.blogspot.com/2007/11/probability-matching-brief-intro.html&quot;&gt;blog post&lt;/a&gt;. &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;li id=&quot;fn:2&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;see Thorp’s classic &lt;a href=&quot;https://www.eecs.harvard.edu/cs286r/courses/fall12/papers/Thorpe_KellyCriterion2007.pdf&quot;&gt;paper&lt;/a&gt; &lt;a href=&quot;#fnref:2&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;
</content>
 </entry>
 
 <entry>
   <title>How I use Notion</title>
   <link href="https://reasonabledeviations.com/2021/09/18/how-i-use-notion/"/>
   <updated>2021-09-18T00:00:00+00:00</updated>
   <id>https://reasonabledeviations.com/2021/09/18/how-i-use-notion</id>
   <content type="html">&lt;p&gt;This post is a fairly comprehensive discussion of how I use Notion (a free personal knowledge management app) to organise various aspects of my life: project management, reading, academics, plans/goals, investing, and more. The post is not designed to be read linearly – pick and choose the bits that are relevant to you.&lt;/p&gt;

&lt;!--more--&gt;

&lt;p&gt;Inevitably, this is going to sound like one massive ad for Notion. I have no affiliation with Notion, I’m just a very happy user. It’s certainly not a perfect piece of software, but it has radically improved my productivity and efficiency.&lt;/p&gt;

&lt;h2 id=&quot;the-philosophy-of-notion&quot;&gt;The philosophy of Notion&lt;/h2&gt;

&lt;p&gt;I firmly believe that a good piece of personal knowledge management (PKM) software is an integral aspect of productivity and general efficiency in the modern day. The reality is that we consume and generate vast quantities of knowledge, whether that’s personal plans, projects, or work stuff. PKM apps aim to help you organise this, freeing up RAM in your brain.&lt;/p&gt;

&lt;p&gt;We are fortunate to be living in the golden age of PKM. For several years, Evernote and OneNote were the only real players (and these were essentially just linear extensions of the classic “folders and files” paradigm). Recently, however, many apps have sprung up which have dramatically expanded the horizons of PKM – apps like Notion, Roam, Obsidian, RemNote, and many others.&lt;/p&gt;

&lt;p&gt;Although each of these apps serves a slightly different purpose, one commonality is that they have evolved far beyond simple note-taking. Notion provides extremely flexible “databases”, which can be viewed as tables, lists, or Kanban boards. Roam and Obsidian emphasise the linking of ideas, allowing you to build (and visualise!) your second brain. RemNote automatically generates flashcards based on your notes, allowing for optimal learning via spaced repetition.&lt;/p&gt;

&lt;p&gt;I use Notion, Obsidian, and RemNote – each for slightly different purposes. This post focuses on Notion. I don’t expect that my entire workflow will be interesting to everybody, so please use the table of contents below to navigate to the specific areas that are relevant to you. The list is in approximate descending order of how much I use each piece of functionality.&lt;/p&gt;

&lt;p&gt;A caveat: I am constantly refining my systems. There is no guarantee that my workflow will look the same a year from now. Similarly, if you are new to PKM, don’t be daunted by the apparent complexity – Rome wasn’t built in a day. I started with a blank slate, then incrementally added things that I thought would be useful; I watched youtube videos in which various productivity-tubers explained their workflows, and picked different parts that I like; I went down rabbit holes setting up complex Notion tables, then scrapped them in favour of simpler setups.&lt;/p&gt;

&lt;p&gt;Ultimately, the core philosophy of PKM is that you need to find a system that helps you achieve what you want to achieve. It is unlikely that what works best for me will also be optimal for you, though there may be certain motifs that come in handy.&lt;/p&gt;

&lt;p&gt;With all this out of the way, let’s begin!&lt;/p&gt;

&lt;h2 id=&quot;contents&quot;&gt;Contents&lt;/h2&gt;

&lt;!-- TOC --&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;#prelude-intro-to-my-notion-workspace&quot;&gt;Prelude: intro to my Notion workspace&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#how-i-use-notion-for-projects&quot;&gt;How I use Notion for projects&lt;/a&gt;
    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#pyportfolioopt&quot;&gt;PyPortfolioOpt&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#this-blog&quot;&gt;This blog&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#collaborative-projects&quot;&gt;Collaborative projects&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#how-i-use-notion-to-track-books&quot;&gt;How I use Notion to track books&lt;/a&gt;
    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#other-content-consumption&quot;&gt;Other content consumption&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#how-i-use-notion-for-short-term-plans&quot;&gt;How I use Notion for short term plans&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#how-i-use-notion-for-long-term-goals&quot;&gt;How I use Notion for long term goals&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#how-i-used-notion-for-academics&quot;&gt;How I used Notion for academics&lt;/a&gt;
    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#tracking-lecture-progress-and-problem-sheets&quot;&gt;Tracking lecture progress and problem sheets&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#exam-revision&quot;&gt;Exam revision&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#how-i-use-notion-as-a-second-brain&quot;&gt;How I use Notion as a second brain&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#how-i-use-notion-for-investing&quot;&gt;How I use Notion for investing&lt;/a&gt;
    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#tracking-themes&quot;&gt;Tracking themes&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#investment-research&quot;&gt;Investment research&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#investment-pitches&quot;&gt;Investment pitches&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#how-i-use-notion-to-track-my-learning&quot;&gt;How I use Notion to track my learning&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#conclusion&quot;&gt;Conclusion&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;!-- /TOC --&gt;

&lt;h2 id=&quot;prelude-intro-to-my-notion-workspace&quot;&gt;Prelude: intro to my Notion workspace&lt;/h2&gt;

&lt;p&gt;Before diving into specific use cases, I will briefly explain how Notion works and give a high-level overview of my setup.&lt;/p&gt;

&lt;p&gt;The fundamental unit in Notion is a &lt;strong&gt;content block&lt;/strong&gt;. Notion has a huge list of possible content blocks. Here is a small subset of them:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;text and text derivatives, e.g. headings, bulleted lists, toggle-able bulleted lists&lt;/li&gt;
  &lt;li&gt;all kinds of media (embedded images, videos, audio etc)&lt;/li&gt;
  &lt;li&gt;code and equations (typed in LaTeX)&lt;/li&gt;
  &lt;li&gt;tables and Kanban boards (popularised by Trello)&lt;/li&gt;
  &lt;li&gt;calendars&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A &lt;strong&gt;page&lt;/strong&gt; contains as many content blocks as you want, arranged however you want. What makes Notion so powerful is that pages can be arbitrarily grouped and nested. For example, each of the items in a Kanban board is itself a page, which you can click and open. If you wanted, you could therefore have a Kanban board where each item contains its own Kanban board etc (though I can’t think why you’d want that!). Below is an image of my Projects and Learning page (more details later), which illustrates nested pages and mixed block types.&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/notion/intro_mixed_blocks.png&quot; style=&quot;width:100%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;The page I visit the most is the Dashboard – a home page of sorts. It links to many of my other pages (which house subpages and/or link to other pages).&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/notion/dashboard.png&quot; style=&quot;width:100%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;Each page can be given an emoji and cover photo. I initially thought this was gimmicky, but it’s a little thing that truly sparks joy and encourages me to stay organised.&lt;/p&gt;

&lt;p&gt;I also have an Inbox page, which is essentially where I dump stuff that I may want to revisit later. This includes:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Media to process e.g. URLs, papers, podcasts episodes etc.&lt;/li&gt;
  &lt;li&gt;Useful web tools&lt;/li&gt;
  &lt;li&gt;Cool projects/startups I’ve come across&lt;/li&gt;
  &lt;li&gt;Random thoughts or ideas&lt;/li&gt;
  &lt;li&gt;A list of blogs that I like&lt;/li&gt;
&lt;/ul&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/notion/inbox.png&quot; style=&quot;width:80%;&quot; /&gt;
&lt;/center&gt;

&lt;h2 id=&quot;how-i-use-notion-for-projects&quot;&gt;How I use Notion for projects&lt;/h2&gt;

&lt;p&gt;One of Notion’s key strengths is project management. This is because administrating a project requires you to keep track of a tonne of information in different formats: important tasks, long-term goals, strategy brainstorming, admin details, historical progress, etc.&lt;/p&gt;

&lt;p&gt;Notion’s flexibility means that you can view each of these data types in the most natural way, for example: visualising tasks using Kanban (Trello) boards, creating tables to store a list of documents, or having simple text files to store deployment details.&lt;/p&gt;

&lt;p&gt;In this section, I give three examples of projects that I manage using Notion.&lt;/p&gt;

&lt;h3 id=&quot;pyportfolioopt&quot;&gt;PyPortfolioOpt&lt;/h3&gt;

&lt;p&gt;PyPortfolioOpt is an open-source python library that I developed. I use a Kanban board in a Notion page to keep track of the features I am working on.&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/notion/pf_kanban.png&quot; style=&quot;width:100%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;Each item in the Kanban board is itself a page, which can contain any other content blocks.&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/notion/pf_kanban_item.png&quot; style=&quot;width:80%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;In addition to the Kanban board, I use toggle lists to keep track of PyPortfolioOpt’s professional users and citations, and have various subpages to draft publicity or explore derivative projects.&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/notion/pf_misc.png&quot; style=&quot;width:100%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;The “Admin details” subpage contains instructions for deployment, builds etc. After a few months away from the project, this is the kind of stuff I forget!&lt;/p&gt;

&lt;h3 id=&quot;this-blog&quot;&gt;This blog&lt;/h3&gt;

&lt;p&gt;Notion is very useful for managing the writing process of blog posts. At any given time I have a bunch of ideas, half-written posts, etc. A Kanban board helps me prioritise which to work on.&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/notion/rd.png&quot; style=&quot;width:100%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;Within each Kanban subpage, I write out a rough plan for the post and jot down any relevant notes or ideas.&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/notion/rd_item.png&quot; style=&quot;width:100%;&quot; /&gt;
&lt;/center&gt;

&lt;h3 id=&quot;collaborative-projects&quot;&gt;Collaborative projects&lt;/h3&gt;

&lt;p&gt;Notion is also excellent for collaborative projects. In the free plan, a page can be shared with up to 5 guests.&lt;/p&gt;

&lt;p&gt;Earlier this year I was exploring a business opportunity with some colleagues (related to crypto market making). We used Notion to assign tasks (with due dates) to different members, and we could comment on each other’s pages and blocks.&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/notion/collab.png&quot; style=&quot;width:100%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;I also used Notion to build a resource library for the project. Each row represents a piece of content (e.g. a book or journal article). Once read, you add your name to the “read by” column. We can then perform complicated filtering on the database. In the example below, I filtered to find the resources that nobody had yet read and saved this as an “unread” view – allowing us to track our collective blind spots.&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/notion/collab2.png&quot; style=&quot;width:100%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;For the same resource database, I created another &lt;strong&gt;view&lt;/strong&gt; (same data but different way of looking at it), filtered to find the resources that &lt;em&gt;I&lt;/em&gt; hadn’t read (sorted in descending order of priority).&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/notion/collab3.png&quot; style=&quot;width:80%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;Although I’ve only used Notion for one collaborative project, it was a fantastic experience.&lt;/p&gt;

&lt;h2 id=&quot;how-i-use-notion-to-track-books&quot;&gt;How I use Notion to track books&lt;/h2&gt;

&lt;p&gt;I am obsessed with reading books. But I don’t just want to read, I want to &lt;em&gt;retain&lt;/em&gt; the information I consume and incorporate the ideas into my thinking. My overall workflow for reading books is as follows:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Read the book on Kindle, highlighting as much as possible. (When reading physical books, use sticky notes instead).&lt;/li&gt;
  &lt;li&gt;After reading the book, collate all the highlights. For kindle highlights, I’ve got a &lt;a href=&quot;https://github.com/robertmartin8/kindleclippings&quot;&gt;python script&lt;/a&gt; that pulls the notes into text files, though if most of your books are from Amazon it may be easier to use something like &lt;a href=&quot;https://readwise.io/&quot;&gt;Readwise&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;Copy-paste these highlights into a Notion page for the book.&lt;/li&gt;
  &lt;li&gt;Reorganise the highlights, grouping them by topic so it’s easier to revisit them in future.&lt;/li&gt;
  &lt;li&gt;Write a book review within the Notion page and post this to Goodreads.&lt;/li&gt;
  &lt;li&gt;Summarise the book in my own words (ideally in ~5 bullet points) and put this summary into Obsidian.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This workflow is not as efficient as it could be – for instance, I could probably automate the transfer of highlights from my Kindle to Notion. But actually, I appreciate the “slowness” because it forces me to pay more attention to the highlights, improving my retention.&lt;/p&gt;

&lt;p&gt;This is my Books database: it contains all of the book pages, tagged by genre (along with some other metadata).&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/notion/books_db.png&quot; style=&quot;width:80%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;Below is a page for &lt;em&gt;The Success Equation&lt;/em&gt; by Michael Mauboussin. Some things to note:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;I try to write a one-sentence headline summarising my takeaway from the book. This can be difficult, but it forces me to internalise the key message from the book.&lt;/li&gt;
  &lt;li&gt;Notice that within the book review there are clickable links to other books. Making links between different things I read is &lt;em&gt;incredibly&lt;/em&gt; important to me – it makes me more creative, and invites a deeper contextual understanding of ideas.&lt;/li&gt;
  &lt;li&gt;The act of categorising quotes by topic can also be quite draining but it’s very worthwhile retrospectively when I’m trying to resurface a particular idea/theme.&lt;/li&gt;
  &lt;li&gt;Using author tags and genre tags allows the book to show up in filtered searches.&lt;/li&gt;
&lt;/ul&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/notion/book_eg.png&quot; style=&quot;width:80%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;The final step of my workflow is to write a summary (different to the book review) in an Obsidian page. Obsidian is my “second-brain”, containing all my linked ideas: the graph view shows how the particular piece of content fits into my network of content/ideas.&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/notion/obsidian_ex.png&quot; style=&quot;width:100%;&quot; /&gt;
&lt;/center&gt;

&lt;h3 id=&quot;other-content-consumption&quot;&gt;Other content consumption&lt;/h3&gt;

&lt;p&gt;My “Books” database is actually just a view into a more generalised “Information Media” database, containing web articles, blog posts, youtube videos etc that I’ve found interesting in addition to books.&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/notion/info_media.png&quot; style=&quot;width:100%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;I use the Notion web clipper to store articles I’ve read (or started reading) into this table. After consuming a piece of content, if I feel that it contains ideas that are relevant to me, I will make a short summary of it (ideally ~3 bullet points) then add it to Obsidian.&lt;/p&gt;

&lt;h2 id=&quot;how-i-use-notion-for-short-term-plans&quot;&gt;How I use Notion for short term plans&lt;/h2&gt;

&lt;p&gt;There are two primary ways I use Notion for short term plans: daily plans and daily intentions (the two are linked)&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;daily intention&lt;/strong&gt; is one well-defined thing that I hope to achieve on a given day. This can be work/school-related (tends to be the case for me), but occasionally I put in fun ones like “meet a new person” or “check in on a friend”.&lt;/p&gt;

&lt;p&gt;Caveat: I have likely bastardised the “daily intentions” concept. If you google “daily intentions” you may see things like “I intend to be present / to have fun today / to be valued today”. Rather than having such ambiguous and saccharine intentions, I prefer to have specific practical goals and I aim for a hit rate of about 50% – that is, I choose goals that are sufficiently stretching that I only achieve about half of them. This can be discouraging, but my view is that if you’re achieving all your goals, the goals aren’t ambitious enough.&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/notion/intentions.png&quot; style=&quot;width:100%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;These daily intentions link with my &lt;strong&gt;daily plans&lt;/strong&gt;. Every evening, I reflect on the previous day, write a comment, and give it a quality rating. I then create tomorrow’s daily plan and daily intention. The example below shows one of my daily plans shortly before final exams (“tripos Qs” means past exam questions).&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/notion/daily_plans.png&quot; style=&quot;width:80%;&quot; /&gt;
&lt;/center&gt;

&lt;h2 id=&quot;how-i-use-notion-for-long-term-goals&quot;&gt;How I use Notion for long term goals&lt;/h2&gt;

&lt;p&gt;I mentally break out long term goals into three categories:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Specific things I want to do (e.g. preparing for an internship, planning a holiday). I view this as a project management exercise, so use the structure described &lt;a href=&quot;#how-i-use-notion-for-projects&quot;&gt;above&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;General long term goals e.g. New Years resolutions. I use simple text pages for this (could also be done in any other software).&lt;/li&gt;
  &lt;li&gt;Blocks of time that I want to fill productively (e.g. term holidays, lockdowns etc). This merits some further discussion.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The below table is a list of things I wanted to get done during the “Rona holiday” – the period in May-July 2020 when Easter term at Cambridge was “cancelled” due to Covid.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Tasks were tagged by “purpose” – what long term area of my life do they contribute to? This encouraged me to stay balanced and approach personal development from a more holistic perspective (rather than solely focusing on e.g. careers).&lt;/li&gt;
  &lt;li&gt;I gave each task a deliverable. Instead of passively watching an online course or leafing through a textbook, I challenged myself to transmute that content into a blog post, set of public notes, or code.&lt;/li&gt;
&lt;/ul&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/notion/rona_holiday.png&quot; style=&quot;width:80%;&quot; /&gt;
&lt;/center&gt;

&lt;h2 id=&quot;how-i-used-notion-for-academics&quot;&gt;How I used Notion for academics&lt;/h2&gt;

&lt;p&gt;Notion played a &lt;em&gt;critical&lt;/em&gt; role in helping me achieve my academic goals. For context, this section pertains to the Cambridge Part II Astrophysics tripos (i.e 3rd-year Astrophysics).&lt;/p&gt;

&lt;p&gt;My overall study plan was as follows:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Watch lectures and make handwritten notes based on them (these notes need not be comprehensive).&lt;/li&gt;
  &lt;li&gt;Apply this knowledge to the weekly problem sheets, paying special attention to weaknesses.&lt;/li&gt;
  &lt;li&gt;Make comprehensive flashcards with everything that needs to be memorised.&lt;/li&gt;
  &lt;li&gt;Do a tonne of past papers and flashcards.&lt;/li&gt;
  &lt;li&gt;Profit&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Some caveats:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;I don’t use Notion for taking class notes – for that, I use Notability on my iPad to make handwritten notes, and RemNote to make flashcards. I only used Notion to help administrate the execution of the above plan.&lt;/li&gt;
  &lt;li&gt;The overall philosophy of a physics course may be quite different to that of other subjects, so the above plan may not be relevant to you (in which case the Notion setup will be even less relevant).&lt;/li&gt;
  &lt;li&gt;Even if you’re studying the same course, my usage of Notion was heavily optimised for the particular format of the Cambridge course.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here is a basic overview of the structure of the Astro course:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Astrophysics is split into eight compulsory lecture courses.
    &lt;ul&gt;
      &lt;li&gt;At the end of the year, each exam paper contains one question from every course. You may only submit up to six questions, but three complete questions can get you a 1st class.&lt;/li&gt;
      &lt;li&gt;You can thus choose not to study certain courses – the only downside is that you’ll have less choice in the exam.&lt;/li&gt;
      &lt;li&gt;Exam marks are nonlinear, rewarding complete answers. Getting 16/20 is &lt;em&gt;much&lt;/em&gt; more valuable than getting 14/20. This is a key factor in exam strategy, encouraging judicious specialisation before the exams.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;In terms of teaching, there were 12 lectures a week, supplemented by two supervisions. A supervision (supo) is a small group session wherein 2-3 students discuss their problem sheet attempts with a supervisor (normally a PhD student or professor). Supo work is handed in several days before the supo and represents the bulk of the workload during term.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Given these rules, my goal was to “game the system” to maximise my academic performance for a fixed amount of effort, strategising around the constraints to see where I could find a competitive advantage.&lt;/p&gt;

&lt;h3 id=&quot;tracking-lecture-progress-and-problem-sheets&quot;&gt;Tracking lecture progress and problem sheets&lt;/h3&gt;

&lt;p&gt;For each lecture course, I used Notion to track how many lectures I had watched, as well as how many lectures were required for me to complete the next problem sheet. This meant that even as I slipped behind in lectures (a common occurrence) I always knew how far behind I was.&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/notion/supo_tracker.png&quot; style=&quot;width:80%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;Each lecture course had its own page, which I used to track:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Areas that I wanted to revisit, e.g. concepts/techniques from lectures that I didn’t understand the first time around.&lt;/li&gt;
  &lt;li&gt;Important derivations that I’d need to be able to reproduce in an exam.&lt;/li&gt;
  &lt;li&gt;Which subtopics within the course I would “bin” (i.e not answer an exam question on), and which subtopics I was weak at (would only choose that exam question if there was nothing better).&lt;/li&gt;
&lt;/ul&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/notion/sdsg.png&quot; style=&quot;width:80%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;To organise my progress in making flashcards, I created a new view on the same table to track how many pages of my notes I had converted into flashcards. This screenshot was taken after the exams, hence the 100% progress.&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/notion/flashcards.png&quot; style=&quot;width:80%;&quot; /&gt;
&lt;/center&gt;

&lt;h3 id=&quot;exam-revision&quot;&gt;Exam revision&lt;/h3&gt;

&lt;p&gt;When it came to exam preparation, I needed a system that allowed me to:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Understand what type of questions tend to come up. Is there any pattern or periodicity that can be exploited? For example, perhaps every year there is always a question on perturbation theory, or every three years there’s a question about phase transitions. Are there certain derivations that frequently show up?&lt;/li&gt;
  &lt;li&gt;Figure out which questions I’m good at – in particular, which questions I can do quickly and accurately (time tends to be the limiting factor in the Astro exams).&lt;/li&gt;
  &lt;li&gt;Identify what I need to remember and what I should be able to derive.&lt;/li&gt;
  &lt;li&gt;Highlight weaknesses – either so that I can work on them, or so that I can avoid these questions in the exams.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To that end, I built a big Notion database that tracked my attempts at past exam questions.&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/notion/tripos_q.png&quot; style=&quot;width:80%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;Each exam question is a subpage in which I tracked:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Metadata, like what course and what year/paper the question belongs to&lt;/li&gt;
  &lt;li&gt;The course chapter to which the questions belong&lt;/li&gt;
  &lt;li&gt;How long it took me to do the question and how hard I found it&lt;/li&gt;
  &lt;li&gt;Whether I did the question “properly” (exam conditions)&lt;/li&gt;
  &lt;li&gt;Whether I had subsequently revisited the question. There is no point in doing exam questions if you don’t revisit your attempts and learn from them.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Furthermore, I summarised what each part of the question is asking for, and noted down anything specific I needed to revisit.&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/notion/pqm_page.png&quot; style=&quot;width:80%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;Having a rigorous tracking methodology helped me get more out of the past paper questions by ensuring that I actively reflected on the question content and my attempts, building up a mental picture of my strengths and weaknesses.&lt;/p&gt;

&lt;p&gt;However, the game-changer was that after a certain point, I had a large Notion database containing both the content of the questions as well as my performance on these questions. I could then perform complex queries on this data to refine my exam strategy.&lt;/p&gt;

&lt;p&gt;For example, I could filter by course and by paper to identify patterns. The screenshot below shows a filter of the paper 1 quantum mechanics questions (across all years). This filter helped me identify that paper 1 QM questions typically involve harmonic oscillators, which were normally easy or medium difficulty.&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/notion/pqm_filter.png&quot; style=&quot;width:80%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;Another example: after completing the first couple of exams, I wanted to find all questions within the galaxies course related to topics that hadn’t yet come up, since these topics had a very high probability of coming up on the last papers. This is a simple filter:&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/notion/sdsg_filter.png&quot; style=&quot;width:80%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;The unfortunate truth is that anytime an exam format gives you discretion (e.g. about which questions you can answer), exam strategy becomes just as important as knowing the content. Notion is a fantastic tool for refining this strategy and making data-driven decisions.&lt;/p&gt;

&lt;h2 id=&quot;how-i-use-notion-as-a-second-brain&quot;&gt;How I use Notion as a second brain&lt;/h2&gt;

&lt;p&gt;After reading &lt;em&gt;How to Take Smart Notes&lt;/em&gt; by Sonke Ahrens, I became aware of the “zettelkasten” note-taking method and built it in Notion.&lt;/p&gt;

&lt;p&gt;There is a tonne of information about zettelkastens out there, so I will just link to a &lt;a href=&quot;https://www.youtube.com/watch?v=lOY-drtTJX0&amp;amp;ab_channel=MukulKhanna&quot;&gt;youtube video&lt;/a&gt; by Mukul Khanna that implements zettelkasten in Notion (I basically duplicated his setup).&lt;/p&gt;

&lt;p&gt;Full disclosure: I have since migrated my zettelkasten to Obsidian. Perhaps I’ll do a separate write-up on this in future!&lt;/p&gt;

&lt;h2 id=&quot;how-i-use-notion-for-investing&quot;&gt;How I use Notion for investing&lt;/h2&gt;

&lt;p&gt;Notion helps me manage several aspects of investing/trading:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Tracking ideas related to specific themes/styles (e.g. covid, spinoffs), as well as reflect on my trading performance.&lt;/li&gt;
  &lt;li&gt;Conducting investment research&lt;/li&gt;
  &lt;li&gt;Storing investment pitches for posterity, so I can better understand my strengths/weaknesses as a trader.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;tracking-themes&quot;&gt;Tracking themes&lt;/h3&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/notion/investing.png&quot; style=&quot;width:80%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;I use subpages to track specific themes. For example, covid obviously created major dislocations and I made a Notion subpage to track my thoughts on it. Separately, I became interested in spinoff stocks so created a table to track active situations:&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/notion/spinoffs.png&quot; style=&quot;width:80%;&quot; /&gt;
&lt;/center&gt;

&lt;h3 id=&quot;investment-research&quot;&gt;Investment research&lt;/h3&gt;

&lt;p&gt;To manage my investment research, I have a table containing subpages for each asset (tagged by asset class).&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/notion/investment_research.png&quot; style=&quot;width:80%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;Each subpage contains links to resources, as well as my thoughts on the investment.&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/notion/uranium.png&quot; style=&quot;width:80%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;For equities, I use the structure discussed in my &lt;a href=&quot;/2020/08/20/investing-checklist/&quot;&gt;Equity Investing Checklist&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;investment-pitches&quot;&gt;Investment pitches&lt;/h3&gt;

&lt;p&gt;After doing my research, if I want to add an idea to my portfolio, I write a pitch. This contains:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;One sentence introduction to the company (industry, current share price, current multiples)&lt;/li&gt;
  &lt;li&gt;Investment thesis and key levers.&lt;/li&gt;
  &lt;li&gt;How my view of the levers differs from consensus&lt;/li&gt;
  &lt;li&gt;Risk factors (identify risks using a pre-mortem analysis)&lt;/li&gt;
  &lt;li&gt;How the company fits into my portfolio (in terms of diversification or sector risk)&lt;/li&gt;
  &lt;li&gt;Price target and exit conditions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Putting pitches into writing is a fantastic habit to get into. It keeps you accountable and helps you distinguish between skill and luck.&lt;/p&gt;

&lt;p&gt;I keep all of these pitches in a table partitioned into open and closed positions. For the closed positions, I tag it based on whether it was a success or failure (note: if a stock I bought went up but for reasons other than what I pitched, I flag it as “neutral”).&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/notion/investments.png&quot; style=&quot;width:80%;&quot; /&gt;
&lt;/center&gt;

&lt;h2 id=&quot;how-i-use-notion-to-track-my-learning&quot;&gt;How I use Notion to track my learning&lt;/h2&gt;

&lt;p&gt;I use Notion to track things I want to self-study (e.g. using online courses or textbooks). There is nothing special here: I use a Kanban board, and each page contains links to various learning resources (not notes though – those would go in Notability, RemNote, or Obsidian).&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/notion/learning.png&quot; style=&quot;width:80%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;Sadly, free time to self-study things is hard to come by – hence the low position of this section within the post.&lt;/p&gt;

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

&lt;p&gt;I hope this post has given you some ideas as to the kind of things Notion can be useful for! Writing this post has made me realise just how important the app is for my productivity.&lt;/p&gt;

&lt;p&gt;At a philosophical level, I think Notion is a wonderful example of how “software eats the world”. In the past, note-taking apps like OneNote or Evernote were just digital equivalents of existing systems: folders, notebooks, pieces of paper. But at some point there was a phase change: apps like Notion let you do things that are &lt;em&gt;impossible&lt;/em&gt; in the physical world.&lt;/p&gt;

&lt;p&gt;I consider myself to be a Notion power user, but at times I feel like I’m only scratching the surface of what the app can do. For example, you can build and host websites in Notion (with no code), and with the recently-released API, the spectrum of possibilities has widened even further.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Hypothesis testing in quant finance</title>
   <link href="https://reasonabledeviations.com/2021/06/17/hypothesis-testing-quant/"/>
   <updated>2021-06-17T00:00:00+00:00</updated>
   <id>https://reasonabledeviations.com/2021/06/17/hypothesis-testing-quant</id>
   <content type="html">&lt;p&gt;At its core, science is about making falsifiable hypotheses about the world (Popper), testing them experimentally, then using the experiment outcomes to refute or refine the hypotheses. The scientific method is an integral part of quantitative finance; it provides a framework we can use to identify and analyse trading signals or anomalies.&lt;/p&gt;

&lt;p&gt;In this short post, we discuss a general method for hypothesis-testing in finance, using Monte Carlo simulations to compute the probability that an observed signal can be explained by random chance.&lt;/p&gt;

&lt;!--more--&gt;

&lt;p&gt;This post is an expanded version of a Twitter thread I wrote. The supporting code can be found in &lt;a href=&quot;https://github.com/robertmartin8/RandomWalks/blob/master/ETF_seasonality_test.ipynb&quot;&gt;this notebook&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;hypothesis-testing-and-p-values&quot;&gt;Hypothesis testing and p-values&lt;/h2&gt;

&lt;p&gt;As a necessary prelude, we first discuss a somewhat controversial topic: the p-value.&lt;/p&gt;

&lt;p&gt;Wikipedia defines the p-value as:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;the probability of obtaining test results at least as extreme as the results actually observed, under the assumption that the null hypothesis is correct&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;With a definition like this, it’s no surprise that p-values are so widely misinterpreted. We can try and understand the intuition behind p-values by considering an example.&lt;/p&gt;

&lt;p&gt;Let’s imagine that we want to find out whether some new teaching method X (e.g. forcing students to make flashcards) improves test scores versus the default method Y. A simple approach is to split the cohort of 100 students into two groups, making 50 of them do X and the other 50 do Y. We can then compare the average score between the two cohorts.&lt;/p&gt;

&lt;p&gt;The problem arises when we try to interpret the results. If the X students had an average grade of 70%, while the control group had an average of 60%, did X really improve test results or was it a random fluke? How about if it was 60.1% vs 60.0%? Or 80% vs 50%?&lt;/p&gt;

&lt;p&gt;This is where hypothesis testing comes in. We set up a &lt;strong&gt;null hypothesis&lt;/strong&gt; and test whether there is sufficient evidence to reject the null hypothesis. In our example, the null might be that there is no difference between the mean scores of the X and Y groups.&lt;/p&gt;

&lt;p&gt;To test this, we &lt;em&gt;assume&lt;/em&gt; that the null hypothesis is true – that X doesn’t actually improve test scores – and compute the probability that we would see X have an average score of 70% vs the control group’s 60%. This probability is the p-value.&lt;/p&gt;

&lt;p&gt;At the risk of being imprecise (and thereby perpetuating the misinterpretations of p-values), I think of the p-value as the &lt;strong&gt;probability that random data can explain your results&lt;/strong&gt;.&lt;/p&gt;

&lt;h2 id=&quot;the-trouble-with-p-values&quot;&gt;The trouble with p-values&lt;/h2&gt;

&lt;p&gt;One of the major criticisms with p-values is that they answer the wrong question. We aren’t truly after “the probability that random data can explain your results”. We actually want to know the probability that X &lt;em&gt;causes&lt;/em&gt; test results to improve, given the observed data. To answer questions like this, a different box of tools is needed – I’d highly recommend Judea Pearl’s &lt;em&gt;The Book of Why&lt;/em&gt; if you’re interested in learning more – but for now we will proceed under the assumption that the p-value is indeed a statistic of interest.&lt;/p&gt;

&lt;p&gt;Even then, there is a subtle issue that we glossed over above. The p-value can only be calculated if we assume that the quantity of interest is drawn from a particular probability distribution. In our example, we can’t actually “compute the probability that we would see X have an average score of 70% vs the control group’s 60%” unless we make some assumption about the distribution of test score (typically a normal distribution or t-distribution is used).&lt;/p&gt;

&lt;p&gt;In finance, these types of assumptions rarely hold. Furthermore, we are often looking to test more complicated hypotheses, such as “this time series is stationary” or “this data is normally distributed”. You might be lucky enough to find that someone has already figured out how to test a particular hypothesis – for instance, the augmented Dickey-Fuller test can be used to test for stationarity and Jarque-Bera for normality. But generally, properly calculating p-values is hard!&lt;/p&gt;

&lt;p&gt;The good news is that we often don’t need to properly calculate a p-value. Monte Carlo simulations can give us “quick and dirty” estimates of the p-value that might be good enough.&lt;/p&gt;

&lt;h2 id=&quot;case-study-etf-seasonality&quot;&gt;Case study: ETF seasonality&lt;/h2&gt;

&lt;p&gt;I came across a tweet from Mitchell Rosenthal in which he observes that the iShares Singapore ETF (ticker EWS) shows positive returns in 89% of April months from 2002-2020. This seems like an interesting anomaly, but with only 19 Aprils, it certainly seems plausible that this anomaly could be generated by random chance alone (remember: the probability of this is quantified by the p-value).&lt;/p&gt;

&lt;p&gt;Firstly, let’s replicate his findings. I downloaded data for the EWS ETF going back to 1996 and calculated how often each month had a positive return.&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;compute_pct_up_months&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;df&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# df is a dataframe of daily close prices
&lt;/span&gt;    &lt;span class=&quot;n&quot;&gt;df_mth&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;df&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;resample&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;M&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;last&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;month_up&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;df_mth&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pct_change&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dropna&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;mi&quot;&gt;0&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;pct_up&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;month_up&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;groupby&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;month_up&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;index&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;month&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mean&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;pct_up&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;A bar chart showing the fraction of up-months is shown below:&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/hypo_testing/replicate.png&quot; style=&quot;width:100%;&quot; /&gt;
&lt;figcaption&gt;The percentage of years in which a given month showed a positive return (EWS)&lt;/figcaption&gt;

&lt;/center&gt;

&lt;p&gt;81% of April months showed positive returns. There is a discrepancy with respect to Mitchell’s results of 89%, likely because I used more data and a slightly different methodology for calculating returns. However, I think it is fair to say that we have replicated the anomaly.&lt;/p&gt;

&lt;p&gt;With this in mind, a suitable null hypothesis might be “there is no seasonality in EWS returns”. To estimate a p-value, we need to generate many alternate EWS price series assuming there is no seasonality, then count the number of runs in which we nevertheless see an 81% seasonality. It is worth taking a moment here to convince yourself that this procedure does indeed give you the p-value.&lt;/p&gt;

&lt;p&gt;The hard part here is simulating new EWS price series under the null hypothesis that there is no seasonality. How can we do this without assuming a distribution of returns? How can we create alternative realities in which everything is the same about EWS, &lt;em&gt;except&lt;/em&gt; that it has no seasonality?&lt;/p&gt;

&lt;p&gt;Here’s one solution: for each Monte Carlo run, construct a price series from the &lt;em&gt;shuffled&lt;/em&gt; daily returns of the EWS time series. This generates alternative paths of EWS with the same mean and volatility as the original EWS time series (since these formulae are invariant under reorderings), but by shuffling, we are ensuring that there is no seasonality in the data-generating process.&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;generate_path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rets&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# rets is the daily EWS returns
&lt;/span&gt;    &lt;span class=&quot;n&quot;&gt;ret_shuffled&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rets&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sample&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;frac&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;n&quot;&gt;ret_shuffled&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;index&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rets&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;index&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;px_new&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;1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ret_shuffled&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cumprod&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;px_new&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Note that this doesn’t mean that any given Monte Carlo run will lack seasonality – the whole point of this exercise is to see how often a path will show seasonality &lt;em&gt;given that there is no actual seasonality&lt;/em&gt; (i.e assuming the null hypothesis is true).&lt;/p&gt;

&lt;p&gt;I generated 10,000 paths by reshuffling returns. For each path, I stored the maximum seasonal return (i.e for each month, calculate the percentage of months which had positive returns, then take the maximum percentage).&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/hypo_testing/mc.png&quot; style=&quot;width:100%;&quot; /&gt;
&lt;figcaption&gt;100 price series made by shuffling EWS returns&lt;/figcaption&gt;
&lt;/center&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/hypo_testing/seasonality_hist.png&quot; style=&quot;width:80%;&quot; /&gt;
&lt;figcaption&gt;Histogram showing the distribution of maximum seasonalities across 10,000 paths&lt;/figcaption&gt;
&lt;/center&gt;

&lt;p&gt;It turns out that only 72 out of the 10,000 paths had seasonalities as high as 80% (remember that we observed 81% in real life).&lt;/p&gt;

&lt;p&gt;We therefore see that the p-value – the probability that we observe 80% seasonality purely by random chance (no actual seasonality) – is approximately equal to 0.7%.&lt;/p&gt;

&lt;p&gt;Using the standard 5% significance level, we reject the null hypothesis and conclude that this anomaly is statistically significant.&lt;/p&gt;

&lt;h2 id=&quot;advice-on-setting-up-experiments&quot;&gt;Advice on setting up experiments&lt;/h2&gt;

&lt;p&gt;There is a slight art in figuring out how to set up these Monte Carlo hypothesis tests – it may not be immediately obvious how to generate data assuming the null hypothesis is true.&lt;/p&gt;

&lt;p&gt;The question you need to answer is as follows: how can I randomly generate many paths of the time series with the same statistical properties as your observed data, except removing one particular property (whichever property you’re testing).&lt;/p&gt;

&lt;p&gt;For anything to related to autocorrelations or seasonal effects, shuffling the data is appropriate because it removes any phenomena related to ordering, while preserving the moments of the distribution.&lt;/p&gt;

&lt;p&gt;Testing regime shifts is simple – you just resample from the “old” distribution and see if it can explain the properties of the “new” distribution. For example, if you want to test whether post-GFC volatilities are different to pre-GFC volatilities, generating data under the null hypothesis is as easy as resampling from the pre-crisis return distribution, computing volatilities, and seeing how many paths generate have volatilities as high as the observed post-GFC volatility.&lt;/p&gt;

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

&lt;p&gt;The key takeway from this post is that we only have access to one run of history. It is immensely difficult to figure out which events/anomalies are idiosyncratic to this run of history, vs “true” anomalies that exist in the actual data-generating process.&lt;/p&gt;

&lt;p&gt;Distinguishing between the statistical artefacts and true anomalies is critically important because the former cannot be relied upon to reoccur in the future, so any strategy designed to capture them will look fantastic in backtests but fail in live trading (unfortunately a common occurrence in algo trading).&lt;/p&gt;

&lt;p&gt;To summarise: embrace counterfactuals. Figure out as much as you can about alternative realities if you want to have a better idea of how you’re going to fare in this one.&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>COVID-19 Beta</title>
   <link href="https://reasonabledeviations.com/2020/11/09/covid-beta/"/>
   <updated>2020-11-09T00:00:00+00:00</updated>
   <id>https://reasonabledeviations.com/2020/11/09/covid-beta</id>
   <content type="html">&lt;p&gt;In this short post, we compute and visualise “COVID-19 betas” for stocks in the S&amp;amp;P500 index, to quantitatively and visually understand which companies were most affected (positively and negatively) by COVID-19.&lt;/p&gt;

&lt;!--more--&gt;
&lt;p&gt;For those of you who just want to see the (interactive!) result, here it is. Click on any sector to zoom in on its constituents:&lt;/p&gt;

&lt;div&gt;&lt;script type=&quot;text/javascript&quot;&gt;window.PlotlyConfig = {MathJaxConfig: 'local'};&lt;/script&gt;              
        &lt;script src=&quot;/assets/plotly.min.js&quot;&gt;&lt;/script&gt;&lt;div id=&quot;d8c7bc3a-b23e-4a0f-bcc4-a37404436dbb&quot; class=&quot;plotly-graph-div&quot; style=&quot;height:100%; width:100%;&quot;&gt;&lt;/div&gt;            &lt;script type=&quot;text/javascript&quot;&gt;                                    window.PLOTLYENV=window.PLOTLYENV || {};                                    if (document.getElementById(&quot;d8c7bc3a-b23e-4a0f-bcc4-a37404436dbb&quot;)) {                    Plotly.newPlot(                        &quot;d8c7bc3a-b23e-4a0f-bcc4-a37404436dbb&quot;,                        [{&quot;branchvalues&quot;: &quot;total&quot;, &quot;customdata&quot;: [[27.45, 6.493563, 4.56, -0.005211944444846901], [9.92, 0.58022565, 6.03, 0.005146316851280463], [19.54, 1.1301061000000001, 2.51, 0.011968566534969001], [16.86, 3.4586093, 5.66, 0.005721207777088747], [19.41, 6.291571, 26.14, -0.011370893938062444], [15.54, 0.17396984, 9.73, 0.0025971937237106875], [22.51, 3.7404803999999996, 3.19, -0.002372214723871919], [25.47, 2.604117, 10.62, 0.005272199508263387], [52.31, 13.092818, 11.06, 0.014046171434700392], [17.67, 8.003910000000001, 3.13, 0.004856033766381212], [17.45, 0.52478915, 1.29, 0.004022635445282359], [29.34, 4.0483294, 12.86, 0.005636630473284431], [-77.07, 16.50682, 224.13, 0.012320552976079856], [20.38, 2.693119, 1.79, 0.003960345913192124], [17.32, 2.7489934, 1.81, 0.003949750462976146], [9.96, 0.65951383, 2.2, -0.0006535824688885981], [12.24, 1.5429343999999998, 1.53, 0.0016672729595150486], [23.22, 1.4686536000000001, 0.75, -0.011290153144923215], [15.6, 6.187621, 4.69, -0.02431644524435351], [33.27, 0.9821866, 1.12, 0.0009328985099824269], [21.05, 1.9400215, 2.92, -0.006870038787552745], [32.55, 5.854652400000001, 3.25, 0.014138768947978423], [26.03, 5.3666205, 2.98, -0.023835934103786035], [56.59, 13.138819, 16.44, 0.0016831583882636109], [9.66, 0.9801091999999999, 2.21, -0.003643770347538094], [13.26, 1.2085556, 1.67, 0.00760233594907773], [21.07, 4.265232, 20.48, 0.002494049639971337], [22.18, 9.720562, 2.82, 0.009950924944506312], [14.08, 4.7020154000000005, 5.44, 0.014560545778284356], [187.0, 2.1091955000000002, 21.47, 0.01940068129234403], [27.61, 4.015368, 4.54, -0.019078068960887203], [13.76, 5.581919999999999, 3.91, 0.01312129489586959], [12.41, 1.9493247, 3.73, -0.007470290940363182], [20.68, 11.954137, 9.22, -0.0038263277598695206], [296.16, 3.927053, 24.28, 0.019482404978986515], [42.53, 16.8134, 5.79, 0.013988299224205225], [19.23, 0.6696521, 2.28, -0.004564176217167694], [20.8, 3.4972682, 6.78, -0.015442096877633881], [27.76, 3.5754826, 6.35, 0.01304744011342674], [24.22, 4.1163682999999995, 3.35, -0.010212143410935905], [25.9, 3.85675, 6.64, -0.0014609973322396606], [69.74, 1.5025798000000001, 7.56, 0.0013056062916759498], [19.03, 10.492155, 2.07, -0.012467031090422463], [31.8, 10.59512, 5.16, 0.019359773463067796], [17.97, 10.251216000000001, 2.12, -0.019693595797618094], [15.94, 6.961892999999999, 4.4, 0.009483591955420617], [22.11, 1.5206499, 8.83, -0.0116649336393489], [26.23, 5.553833, 2.55, 0.007718224871634394], [15.0, 2.273575, 3.75, 0.0012004715043340687], [16.31, 1.8510429, 136.23, 0.013673830991563414], [27.29, 2.1560605, 182.86, 0.008771546681345586], [16.34, 3.2011874, 1.24, -0.017252009632601345], [25.12, 3.4394413999999998, 3.77, -0.007861734439547486], [19.22, 0.76715523, 4.79, 0.0023032043139229745], [22.28, 5.6912947, 3.98, -0.006443553574806066], [12.67, 3.3991010000000004, 1.83, -0.005208710836239727], [14.3, 5.737439, 5.51, 0.010976285251000676], [14.76, 3.3834257000000005, 1.49, -0.0038263289566380426], [22.49, 6.9158196, 2.95, -0.00185005677303454], [20.56, 1.5696398999999999, 3.62, -0.006256355222867376], [20.83, 6.5032309999999995, 6.75, -0.004723203491505887], [20.0, 4.0556849999999995, 4.78, -0.011609804743006703], [14.15, 1.5831376, 2.89, 0.006533944677330572], [18.05, 6.839459, 3.15, -0.011758013641352427], [13.48, 2.1857585999999998, 0.9, -0.014494205823317674], [18.2, 1.8294993999999998, 3.86, 0.021075781492984478], [12.29, 0.21131156, 3.03, 0.005660985266729233], [21.22, 2.0468428, 5.75, -0.01005152858693978], [17.5, 2.0985637, 1.35, -0.014630640149051377], [35.06, 8.249042, 5.15, -0.004186843466014379], [21.45, 10.125291, 3.54, -0.0028177378515077687], [17.48, 2.8147783, 2.0, -0.02007882668612668], [34.09, 5.751737, 10.98, 0.014678239162095529], [27.09, 5.5560412, 4.62, 0.0007976719115372684], [-59.46, 3.0044447999999995, 2.81, -0.008692078999113726], [16.04, 3.3917346, 1.08, -0.03238322668258034], [24.42, 3.1682447999999996, 6.28, -0.007955116411373761], [26.0, 0.85282737, 9.31, 0.00614884208453846], [162.92, 2.1208633999999997, 2.27, -0.0009546343850366932], [18.11, 1.550762, 3.39, 0.001058337992789738], [27.48, 2.756679, 1.6, -0.0037652756481916954], [24.02, 4.000737, 236.42, 0.00230998781569137], [23.44, 2.798683, 21.62, 0.016191180385520483], [18.89, 4.9054720000000005, 1.96, -0.03839986235413864], [18.54, 2.1797748, 2.65, 0.01183236173731168], [32.15, 14.881806, 2.51, -0.014052599571629213], [45.86, 2.5523853, 6.11, 0.006379417787741427], [16.83, 1.9406168000000001, 3.89, -0.012730418554944277], [21.42, 2.4711676, 2.65, 0.006885414858035954], [19.12, 0.5028663, 2.58, 0.0059810174249736265], [19.73, 1.6262617, 3.21, -0.02713147212375979], [11.85, 1.5931405, 0.94, -0.018322391834393464], [60.55, 8.636728999999999, 4.21, 0.027165001712310278], [22.96, 6.8538464999999995, 3.57, 0.0024197398754250304], [72.93, 2.0764177, 2.16, -0.038357302497295756], [30.69, 0.61203885, 7.24, 0.011477776333029741], [24.64, 2.0061796000000003, 1.37, 0.014012325608763202], [14.84, 2.3898203, 7.91, 0.0018117760775266853], [520.15, 10.3738785, 8.81, 0.004054902507402308], [17.87, 5.484418, 3.07, 0.016214058199933302], [21.94, 4.216355, 4.27, -0.012325226772562032], [32.75, 2.7614057, 6.53, -0.02634409349027191], [8.35, 1.4795983999999998, 1.39, -0.0035226686402254105], [22.44, 3.9678397000000003, 3.94, -0.012624390742221523], [20.37, 4.7538714, 6.73, 0.035769992225420856], [12.36, 0.549852, 2.13, 0.0023672682215463573], [27.52, 1.6489863, 1.71, -0.015302922743007145], [84.39, 12.271951, 2.61, -0.026566594029823167], [20.42, 3.7954214, 2.96, -0.013314158256335444], [10.37, 0.91792434, 2.67, 0.004115487088473426], [23.14, 2.3746447999999996, 5.31, 9.664664147547687e-05], [12.13, 2.3610325, 2.52, -0.005576736180525364], [21.18, 1.5283886, 4.53, 0.011482655647318393], [17.07, 1.7709883000000002, 2.82, 0.0022719931803685057], [16.32, 1.1931022, 2.17, -0.024398452513502887], [22.87, 3.688278, 2.65, -4.099481359057324e-05], [17.78, 3.9635562999999996, 3.82, 0.006209784440618377], [11.0, 1.8040880000000001, 1.5, 0.0009871167083228066], [10.37, 1.7147918000000002, 1.43, -0.006190276193262106], [16.09, 1.8521843, 3.76, -0.01949171430778312], [16.55, 12.434961, 2.32, -0.004943581670782496], [26.66, 1.6097589, 4.08, 0.02705939001097488], [23.87, 1.986176, 3.59, -0.001440242232707276], [19.77, 16.077257, 1.94, -5.2348968509810614e-05], [22.72, 1.5278828000000002, 5.89, 0.0013531746899852208], [19.12, 1.9091922, 1.95, -0.012368130145702216], [16.7, 2.9348857, 1.27, -0.0045192771169053365], [20.55, 1.1451396999999999, 2.79, 0.01500195787747678], [23.93, 2.0660408, 2.85, -0.04047937246501265], [79.93, 1.9598055, 2.26, -0.0180832202994249], [34.48, 7.4415816999999995, 8.54, 0.01929854568673626], [24.86, 4.6720185, 3.8, -0.0047585359660166715], [28.08, 3.7360687000000006, 5.28, -0.005398415133827148], [18.64, 2.960244, 1.58, -0.003971774933591539], [19.03, 5.633805799999999, 4.59, -0.003159623880077383], [13.23, 2.161095, 1.59, -0.008509337198586247], [37.78, 3.9522583, 10.82, -0.006204115653058076], [12.28, 1.4904785, 2.78, -0.017427212196990192], [25.74, 3.6707752, 5.01, -0.031035360920482296], [246.44, 8.023019999999999, 4.55, -0.04040250856652219], [23.06, 10.639136, 5.14, 0.006076612760791995], [17.6, 8.593619, 2.09, -0.008254964025256271], [18.22, 3.1134193, 1.68, -0.00793711853297717], [18.66, 14.1830845, 2.3, -0.015371717167891494], [17.08, 1.8161736000000002, 2.17, -0.01267592588905073], [10.65, 1.5543115, 1.54, -0.0026691850948117337], [32.57, 7.8283277, 8.48, -0.006672618467486239], [14.51, 1.3933871, 1.26, 0.002546239665763615], [25.26, 2.1903189999999997, 5.55, 0.0046728889915320935], [30.99, 2.5074897000000003, 4.14, -0.010138051676533269], [18.12, 12.204818, 4.54, -0.017028131686928363], [5.89, 0.27054095, 1.26, -0.022876709173270632], [27.02, 3.4729769999999998, 7.39, -0.0011765165884513257], [27.9, 13.241967, 7.08, 0.0014556290277680155], [20.27, 1.8434572, 3.76, -0.011718181587976726], [14.67, 1.6538345, 3.66, -0.006770270441361845], [19.45, 1.0751684, 3.9, 0.0007934566284133259], [11.18, 1.2994481000000002, 2.19, 0.001799617170908583], [21.21, 4.1203604, 6.86, -0.00016460199026139757], [22.08, 4.7038417, 3.25, 0.007523192081407915], [27.32, 6.518713, 11.54, 0.0010091901522352746], [16.9, 2.8802847999999996, 1.46, -0.02391965972170854], [25.53, 5.137961, 3.52, 0.02606490896695029], [26.17, 2.0803037, 3.15, -0.02975968459035388], [32.48, 6.008903500000001, 5.26, -0.0036094809290794184], [17.57, 3.1654842, 4.04, 0.009749274813400101], [17.81, 2.1080317, 4.11, 0.005992875915388915], [18.35, 12.898601, 3.98, -0.020055081217117337], [18.77, 1.561575, 1.06, -0.021380588742550454], [25.05, 5.168267299999999, 7.17, -0.016100201624195423], [20.71, 2.0481362, 5.45, -0.010560972773461361], [13.76, 1.0887611000000001, 1.7, -0.019622962971670457], [8.16, 5.208746400000001, 4.12, 0.010338335549579542], [17.53, 2.0389178, 7.26, -0.0017398360622333663], [16.45, 2.544682, 1.71, -0.0059483483233222405], [6.58, 0.5633218000000001, 1.33, -0.01940713768491655], [40.29, 6.772653, 4.67, 0.0026913649541678163], [31.48, 6.801692, 4.7, 0.002831401211770181], [21.17, 1.212236, 4.33, -0.006679291235580846], [27.86, 5.775833, 4.73, 0.0009841409352427104], [21.23, 5.319881, 3.14, -0.0031009608223211064], [12.44, 2.309415, 1.25, 0.0013612162920702854], [22.57, 1.3995146, 8.1, -0.006302337883429149], [38.86, 2.1424223999999996, 4.74, -0.04706940729863944], [20.57, 3.2375681, 5.82, 0.02304156761706902], [15.41, 3.5112102, 1.72, -0.022816977205098975], [10.04, 1.6339768, 6.17, -0.007195381559930038], [14.07, 0.7251920999999999, null, 0.0034897870119070173], [28.1, 2.8964397999999996, 84.08, 0.010827357272847057], [-9.33, 3.780475, 1.08, -0.04816605870806598], [14.34, 1.3939538, 1.15, -0.0034630604649506223], [18.75, 1.8924941000000002, 6.04, 0.0032868803998141994], [36.42, 3.7628453, 17.67, 0.0003785646019073803], [19.21, 4.80335, 4.0, -0.019933991414622945], [20.68, 2.8267898999999996, 5.71, -0.01132164673949048], [11.57, 1.4609766000000002, 1.06, -0.010555292433670259], [12.07, 0.8954903000000001, 0.66, -0.003466003438362531], [12.29, 2.0188456, 205.41, 0.004766809691879001], [20.39, 2.5174792, 3.49, 0.008129621013979474], [19.85, 1.262194, 3.74, -0.005173678360449004], [11.23, 3.5549128, 2.02, 0.007097297507087319], [20.51, 2.8396711, 25.64, 0.0014963929324440777], [22.39, 0.9256895, 3.33, 0.0001642544674747643], [10.67, 1.8171669999999998, 7.7, 0.009333061790035782], [22.95, 9.619987, 2.62, -0.006828871068624925], [53.57, 7.97525, 196.21, 0.003431716247592465], [24.17, 4.4208813, 6.34, -0.006721615834976336], [52.25, 11.713953, 10.89, 0.016311887783871354], [-119.89, 17.026989999999998, 10.25, 0.006089167330283835], [26.19, 5.0604, 2.24, -0.013410589633505625], [12.32, 3.348876, 3.34, 0.02921890594477179], [40.52, 9.633731, 33.5, 0.006419562906692098], [15.57, 1.0738261, 4.99, -0.014169395019772494], [15.46, 1.4712903, 4.01, 0.010025819508912], [21.74, 2.8477087000000005, 2.77, -0.008897690940054123], [19.38, 1.6147361, 3.34, -0.013823721027156698], [15.42, 3.2898626, 4.97, -0.005809820916215805], [48.58, 14.655261, 9.48, -0.006916568674655936], [36.86, 4.894499, 13.47, 0.003096575609895901], [23.62, 3.9286149, 10.99, -0.0007323925570659076], [11.87, 2.6513786, 1.62, -0.023058177737641333], [30.62, 1.7697401999999998, 8.55, -0.006105815100516674], [14.0, 1.1415336, 1.69, -0.010265431559030425], [17.31, 4.6326222, 4.74, 0.002195808669378473], [14.09, 1.8543326, 1.78, 0.003680015354494763], [15.43, 3.3714992999999995, 1.6, -0.01170215895156367], [16.5, 2.3173764, 11.5, 0.004072078443906747], [14.66, 3.270451, 1.55, -0.02446588686332279], [20.11, 4.678085299999999, 1.54, 0.002154506258865259], [9.28, 7.0508942999999995, 1.2, -0.03293056541309831], [16.59, 4.3395863, 12.93, 0.02948261218843561], [17.93, 2.1947837000000003, 151.66, 0.008152054369699074], [25.07, 2.8239360000000002, 1.07, -0.03183224522324732], [19.44, 0.6913859, 3.55, -0.01241379007502522], [22.8, 6.822138000000001, 8.65, -0.001199416829058241], [13.07, 0.27961263, 4.02, 0.010374842311749328], [16.01, 0.8557756999999999, 2.04, -0.047110865183773075], [19.8, 4.281897, 2.39, -0.0197302726478458], [15.99, 1.5809761, 0.85, 0.004232775594714922], [12.77, 1.7060977, 1403.38, -0.03536595462918194], [17.88, 2.0302901, 4.89, -0.009235549182613179], [15.35, 1.1468264, 1.76, -0.037544597114206595], [17.79, 2.2459624, 2.71, 0.0014087679022756537], [21.35, 1.723609, 3.02, -0.02690816199168261], [17.34, 3.7546779999999997, 5.73, 0.009439501651635712], [24.28, 1.8781451, 45.55, -0.009388131842584491], [9.49, 1.2019440000000001, 1.04, 0.004085785776588883], [19.86, 3.4331481, 2.13, 0.004569041699351878], [23.87, 1.5653384, 14.1, -0.00913971972907641], [16.29, 3.0405292999999998, 4.52, 0.015408561212144142], [15.89, 1.6294897, 3.84, 0.010441857580937911], [10.35, 1.3037611999999998, 5.86, -0.023675445366839652], [34.99, 15.020556, 26.93, 0.0039440335579645595], [14.38, 6.6291075, 1.6, -0.010657467937122139], [32.73, 2.9997249, 11.02, -0.010184733466565047], [22.12, 2.308266, 11.93, -0.01335095070469442], [23.83, 5.770237, 146.07, -0.009279655263833226], [21.77, 7.4475183000000005, 5.9, 0.023712270861135958], [11.68, 0.15318617, 2.68, 0.0031616805252892588], [26.55, 9.932974, 73.84, -0.00296788060072629], [19.85, 2.5708609, 2.52, 0.006595719449469191], [17.04, 4.944542, 2.14, -0.003589994217966392], [8.52, 1.0496864, 0.85, -0.013984346557428324], [29.65, 2.2276719, 3.09, -0.014993353594555765], [19.08, 2.7977824, 2.88, 0.01741276896312713], [23.91, 2.7788277000000003, 5.2, -0.007624898675210386], [30.38, 4.416998, 3.03, -0.01984931559677483], [20.23, 2.9848945, 5.85, -0.0039768186034041175], [24.31, 4.3902707, 11.34, -0.0014367784175169662], [42.17, 14.152586999999999, 9.56, 0.003009341799971624], [18.89, 4.945682, 10.32, -0.0061432898309723765], [23.66, 1.8161957999999998, 0.94, -0.012362865272747806], [16.06, 0.42713487, 2.43, -0.029999831516122222], [13.75, 5.0893345000000005, 4.02, 0.003181155356062412], [14.23, 2.2258842, 1.33, -0.005949437302202008], [25.76, 7.113096700000001, 9.49, 0.014079913775493105], [19.03, 2.6092633999999997, 6.81, -0.00540143048284831], [18.91, 4.6277885, 1.79, -0.02011971694261844], [35.56, 8.372307000000001, 31.69, -0.006273016296312799], [9.01, 2.1912806000000002, 2.02, 0.012994654255559093], [8.35, 2.5358944, 1.63, 0.019319568625946457], [105.96, 4.6976447, 1.44, -0.02486028505486566], [15.35, 3.2066103999999997, 2.35, -0.00723675969639415], [17.78, 3.2591083, 2.25, -0.0013859218107946686], [21.65, 4.091698, 2.75, 0.003440942164727904], [26.34, 3.7076392, 1.81, 0.011571989916356714], [200.08, 9.861594, 30.8, 0.021827105328401344], [19.57, 2.2361343, 1.82, 0.005301639328776007], [24.9, 3.0549932, 8.91, -0.005204270629748287], [18.73, 2.7288197999999997, 3.02, -0.021232080112079922], [24.4, 2.2095759999999998, 7.96, -0.0011901173389977802], [-77.71, 2.5209634, 0.91, -0.0230855041346655], [17.78, 0.9933021000000001, 6.73, -0.016324016071984002], [20.65, 3.8525907999999998, 3.09, -0.011086256547186714], [26.1, 3.6687025999999996, 5.46, 0.005744794758890263], [19.96, 4.031728, 2.44, -0.0009270297945173961], [17.01, 0.99837583, 2.34, -0.014475209384937078], [82.08, 20.094294, 20.39, 0.016505133531003607], [9.72, 1.2810479, 1.01, 0.006942228824096058], [44.03, 1.5718216, 0.86, -0.0032047963059406363], [43.47, 1.5518167, 0.85, -0.009717201704225858], [15.54, 15.588069, 1.92, -0.023597456624024158], [15.27, 1.5327156000000002, 6.66, 0.001773164898074631], [18.81, 5.256164599999999, 3.56, 0.012161178006862733], [21.91, 3.1934318999999998, 34.12, 0.012124694918575775], [27.49, 7.248487, 11.77, -0.002602194966189144], [18.02, 3.9272704, 1.17, -0.00048757850988621025], [15.72, 1.28693, 3.07, -0.00042657659893862343], [16.11, 3.4491453, 1.88, -0.0015911640979352109], [21.51, 3.6705062000000006, null, 0.00944620114219623], [12.69, 4.0020885, 3.4, 0.008146484147308676], [11.96, 1.7167546000000002, 1.53, -0.020251277805570133], [20.46, 3.1595004, 3.85, 0.0029940294974010965], [20.76, 1.1458876, 3.25, 0.003933568573636418], [21.6, 1.8652664, 4.39, -0.007881089623740971], [12.86, 1.0341544, 1.99, -0.03208002738278349], [18.57, 1.741522, 5.54, -0.002818124206272134], [24.68, 3.7116462999999995, 3.25, -0.0120329546899741], [20.76, 14.380373, 2.11, 0.002750006546750386], [22.36, 2.7574685000000003, 1318.7, 0.0011355560502218597], [17.55, 4.046530000000001, 1.53, -0.017913485156501235], [18.89, 2.5191512, 2.48, -0.01443953737103722], [16.06, 2.9931207000000004, 1.65, -0.0020108650655840093], [18.73, 1.9551051, 4.83, -0.013768245617619445], [12.83, 3.7497053, 1.95, -0.009757351780341088], [null, 3.3850067000000004, 2.03, 0.0187964441543274], [9.99, 1.083727, 0.93, -0.01901931192244597], [18.21, 16.164170000000002, 6.56, -0.005234360091881988], [21.11, 0.47499472, 2.12, -0.00867343603403108], [20.98, 1.7617522, 2.14, -0.017457106789277477], [17.54, 0.77893436, 1.43, -0.0219483543574864], [118.29, 6.7890477, 2.76, -0.043827293506679695], [49.53, 6.935696000000001, 5.81, 0.0010825507949443177], [16.51, 4.263658, 3.82, 0.011001484646967808], [19.71, 3.3891419999999997, 1.98, 0.005795453434319594], [16.26, 3.1026611, 2.56, -0.001391595661048713], [27.24, 2.0991988, 1.16, -0.017537793875044137], [15.27, 13.762162, 1.51, -0.006978265068693423], [27.93, 8.48541, 5.89, 0.01885642819880629], [17.9, 3.474771, 1.32, -0.023228165294318707], [20.18, 1.3330402, 5.98, -0.008718019641592249], [16.94, 1.9734555, 2.34, -0.008318083562560157], [17.74, 1.3948757999999999, 2.44, -0.009708907431549304], [31.54, 6.1640687000000005, 6.61, 0.020634724524799707], [26.48, 3.760594, 10.94, -0.013050491714356554], [27.57, 5.9715905, 4.23, -0.015522851875552374], [26.52, 2.9734495, 10.28, -0.0030903662706156377], [26.13, 2.8841186000000003, 2.83, -0.010359240255954156], [199.81, 15.189207000000001, 91.04, -0.0015030114373353284], [25.98, 3.4392443, 13.36, 0.004116013979666568], [29.82, 7.949615, 4.51, -0.01104421493669352], [21.31, 2.4700067000000003, 10.55, -0.010352757313619378], [26.75, 2.5276077, 13.42, 9.1706665877836e-05], [15.35, 2.496677, 1.88, 0.008144558168700662], [44.93, 3.2297827999999997, 2.64, -0.04394682402970106], [14.07, 6.5820084, 1.32, -0.024855605407227494], [15.81, 3.5114812999999994, 3.21, -0.002348781745213443], [67.72, 6.276264, 3.98, 0.0017035966500550783], [15.78, 2.5080602, 1.82, -0.0051256268765335995], [13.56, 8.754495, 13.24, -0.014665506912407944], [27.38, 10.254703999999998, 50.56, -0.0038681085021168332], [19.14, 3.1530299999999998, 1.98, -0.016243655785616273], [15.35, 3.263072, 1.95, -0.014283023234911986], [11.51, 1.3048558000000001, 13.33, 0.0035003608296690527], [30.92, 5.145596, 5.14, -0.018910561725526825], [20.57, 1.944325, 3.34, -0.013241068446344425], [16.45, 6.970443700000001, 4.25, 0.01340427710072182], [13.35, 2.1096623, 1.97, -0.023515501483424475], [23.46, 4.635566000000001, 5.54, -0.006937355714453126], [22.98, 0.7295535, 13.4, 0.0039413272836162595], [12.14, 1.4083152, 1.8, -0.0047819488446779485], [17.66, 1.2371694, 1.34, -0.006209883105524621], [23.76, 4.268832, null, -0.015375226889448472], [19.72, 2.5734365, 3.6, 0.001828845542070831], [14.19, 0.8047394, 3.45, 0.008497711098673415], [27.0, 4.516748000000001, 3.97, -0.008860878815813985], [21.01, 1.9293598, 10.35, 0.0004174320942352367], [21.84, 4.015666, 3.38, -0.007202762885780295], [null, 4.0785494, 4.35, -0.04828940525007715], [19.92, 7.3911157, 4.61, -0.0060624228765978035], [16.48, 1.33382, 1.63, -0.0029864074678875433], [19.57, 1.1642125, 6.01, -0.0021639572658213946], [13.92, 0.93730986, 2.55, 0.019242405742436793], [24.05, 6.957223399999999, 9.32, 0.011016626122169785], [22.13, 1.0645695, 2.57, -0.004444182664528521], [29.15, 1.4738787, 2.5, -0.016264252359868055], [32.05, 1.602308, 2.72, -0.015550714337290984], [8.85, 0.5020496, 2.09, -0.0029850004191367277], [17.79, 12.365829, 3.27, -0.012654322633433777], [15.36, 1.3158662, 2.21, 0.0033511618575198508], [32.07, 3.3779112999999996, 8.04, -0.0028379227702359684], [21.47, 1.0903316, 4.71, 0.004129111703455036], [11.51, 0.9984924000000001, 1.19, -0.010589347569006789], [22.06, 4.8605075, null, -0.01391231350295036], [18.27, 1.4744618, 64.7, 0.0021269144750821595], [16.33, 2.1782618, 4.53, 0.0065829786458879505], [15.35, 3.7773547, 2.09, -0.020473956048845648], [32.72, 14.433654, 9.4, -0.0018418837533784888], [29.93, 3.9652252, 7.32, -0.01268207769855442], [25.9, 3.7110943999999995, 7.89, -0.01176635834565386], [18.74, 0.42119179999999995, 1.93, -0.009163805209150092], [40.9, 5.7576876, 3.54, -0.0067819996734434645], [16.22, 7.1491003, 3.63, -0.008980816172689235], [28.84, 9.935474000000001, 9.34, -0.0015079104677364942], [29.5, 12.401829, 10.82, 0.005344507525759112], [252.67, 16.185404000000002, 21.91, 0.01469448885212424], [12.21, 7.074049499999999, 1.76, -0.0045997584717260695], [13.08, 1.6452543999999998, 7.96, 0.007629492017963376], [26.64, 6.9824114, 6.15, 0.0023448517684944085], [13.38, 0.5904388, 3.06, 0.013593261042162026], [10.19, 1.245503, 2.0, 0.020555458651332282], [19.57, 2.4834197000000002, 2.1, 0.0030359440975371438], [13.58, 2.8728192000000004, 1.58, -0.007094343462732031], [11.77, 0.58064795, 2.57, -0.03088312940650026], [18.67, 3.4484663, 2.1, 0.00015933889095972117], [25.36, 3.2787669000000004, 6.73, -0.011309607785439364], [48.41, 4.2982845, 3.01, -0.012663840035949534], [23.1, 0.82099426, 3.89, 0.02127575067156738], [23.26, 1.075785, 1.58, 0.009018830219523847], [10.17, 2.22677, 12.65, 6.315524674316549e-05], [34.29, 3.6396794000000003, 2.9, -0.01640729118118777], [31.7, 2.4786580000000002, 51.69, 0.007420185868078578], [18.14, 2.5204560000000003, 1.94, 0.006318139386562491], [27.19, 7.1012059999999995, 7.22, 0.010253854498463177], [21.37, 1.7701944, 1.85, -0.009476366380592365], [22.65, 4.6262620000000005, 1.8, -0.006798807046316282], [8.87, 0.7877656, 1.49, -0.015514751471628483], [30.94, 2.7262093999999997, 5.31, -0.0015528394995825737], [27.25, 6.3136363, 212.08, -0.0049114501085677385], [14.32, 3.1648953, 2.39, -0.012867204325537788], [17.73, 3.7945792999999997, 1.42, -0.02075611510579531], [32.8, 9.280896, 18.09, -0.01256373658423224], [&quot;(?)&quot;, &quot;(?)&quot;, &quot;(?)&quot;, 0.003521204420418416], [&quot;(?)&quot;, &quot;(?)&quot;, &quot;(?)&quot;, 0.005719012949767131], [&quot;(?)&quot;, &quot;(?)&quot;, &quot;(?)&quot;, -0.022086897475122217], [&quot;(?)&quot;, &quot;(?)&quot;, &quot;(?)&quot;, -0.010657175920663503], [&quot;(?)&quot;, &quot;(?)&quot;, &quot;(?)&quot;, 0.0015152601909513225], [&quot;(?)&quot;, &quot;(?)&quot;, &quot;(?)&quot;, -0.006402095050403783], [&quot;(?)&quot;, &quot;(?)&quot;, &quot;(?)&quot;, 0.0074087890498917485], [&quot;(?)&quot;, &quot;(?)&quot;, &quot;(?)&quot;, -0.009221511024211728], [&quot;(?)&quot;, &quot;(?)&quot;, &quot;(?)&quot;, -0.00792355419240285], [&quot;(?)&quot;, &quot;(?)&quot;, &quot;(?)&quot;, 0.000969585125463593], [&quot;(?)&quot;, &quot;(?)&quot;, &quot;(?)&quot;, -0.0032122956448452345], [&quot;(?)&quot;, &quot;(?)&quot;, &quot;(?)&quot;, -0.00023398907171855293]], &quot;domain&quot;: {&quot;x&quot;: [0.0, 1.0], &quot;y&quot;: [0.0, 1.0]}, &quot;hovertemplate&quot;: &quot;labels=%{label}&lt;br&gt;Market Cap=%{value}&lt;br&gt;parent=%{parent}&lt;br&gt;id=%{id}&lt;br&gt;Price/Earnings=%{customdata[0]}&lt;br&gt;Price/Sales=%{customdata[1]}&lt;br&gt;Price/Book=%{customdata[2]}&lt;br&gt;COVID beta=%{color}&lt;extra&gt;&lt;/extra&gt;&quot;, &quot;ids&quot;: [&quot;S&amp;P500/Health Care/A&quot;, &quot;S&amp;P500/Industrials/AAL&quot;, &quot;S&amp;P500/Consumer Discretionary/AAP&quot;, &quot;S&amp;P500/Information Technology/AAPL&quot;, &quot;S&amp;P500/Health Care/ABBV&quot;, &quot;S&amp;P500/Health Care/ABC&quot;, &quot;S&amp;P500/Health Care/ABT&quot;, &quot;S&amp;P500/Information Technology/ACN&quot;, &quot;S&amp;P500/Information Technology/ADBE&quot;, &quot;S&amp;P500/Information Technology/ADI&quot;, &quot;S&amp;P500/Consumer Staples/ADM&quot;, &quot;S&amp;P500/Information Technology/ADP&quot;, &quot;S&amp;P500/Information Technology/ADSK&quot;, &quot;S&amp;P500/Utilities/AEE&quot;, &quot;S&amp;P500/Utilities/AEP&quot;, &quot;S&amp;P500/Utilities/AES&quot;, &quot;S&amp;P500/Financials/AFL&quot;, &quot;S&amp;P500/Financials/AIG&quot;, &quot;S&amp;P500/Real Estate/AIV&quot;, &quot;S&amp;P500/Financials/AIZ&quot;, &quot;S&amp;P500/Financials/AJG&quot;, &quot;S&amp;P500/Information Technology/AKAM&quot;, &quot;S&amp;P500/Materials/ALB&quot;, &quot;S&amp;P500/Health Care/ALGN&quot;, &quot;S&amp;P500/Industrials/ALK&quot;, &quot;S&amp;P500/Financials/ALL&quot;, &quot;S&amp;P500/Industrials/ALLE&quot;, &quot;S&amp;P500/Health Care/ALXN&quot;, &quot;S&amp;P500/Information Technology/AMAT&quot;, &quot;S&amp;P500/Information Technology/AMD&quot;, &quot;S&amp;P500/Industrials/AME&quot;, &quot;S&amp;P500/Health Care/AMGN&quot;, &quot;S&amp;P500/Financials/AMP&quot;, &quot;S&amp;P500/Real Estate/AMT&quot;, &quot;S&amp;P500/Consumer Discretionary/AMZN&quot;, &quot;S&amp;P500/Information Technology/ANSS&quot;, &quot;S&amp;P500/Health Care/ANTM&quot;, &quot;S&amp;P500/Financials/AON&quot;, &quot;S&amp;P500/Industrials/AOS&quot;, &quot;S&amp;P500/Materials/APD&quot;, &quot;S&amp;P500/Information Technology/APH&quot;, &quot;S&amp;P500/Consumer Discretionary/APTV&quot;, &quot;S&amp;P500/Real Estate/ARE&quot;, &quot;S&amp;P500/Information Technology/ATVI&quot;, &quot;S&amp;P500/Real Estate/AVB&quot;, &quot;S&amp;P500/Information Technology/AVGO&quot;, &quot;S&amp;P500/Materials/AVY&quot;, &quot;S&amp;P500/Utilities/AWK&quot;, &quot;S&amp;P500/Financials/AXP&quot;, &quot;S&amp;P500/Consumer Discretionary/AZO&quot;, &quot;S&amp;P500/Industrials/BA&quot;, &quot;S&amp;P500/Financials/BAC&quot;, &quot;S&amp;P500/Health Care/BAX&quot;, &quot;S&amp;P500/Consumer Discretionary/BBY&quot;, &quot;S&amp;P500/Health Care/BDX&quot;, &quot;S&amp;P500/Financials/BEN&quot;, &quot;S&amp;P500/Health Care/BIIB&quot;, &quot;S&amp;P500/Financials/BK&quot;, &quot;S&amp;P500/Financials/BLK&quot;, &quot;S&amp;P500/Materials/BLL&quot;, &quot;S&amp;P500/Health Care/BMY&quot;, &quot;S&amp;P500/Health Care/BSX&quot;, &quot;S&amp;P500/Consumer Discretionary/BWA&quot;, &quot;S&amp;P500/Real Estate/BXP&quot;, &quot;S&amp;P500/Financials/C&quot;, &quot;S&amp;P500/Consumer Staples/CAG&quot;, &quot;S&amp;P500/Health Care/CAH&quot;, &quot;S&amp;P500/Industrials/CAT&quot;, &quot;S&amp;P500/Financials/CB&quot;, &quot;S&amp;P500/Financials/CBOE&quot;, &quot;S&amp;P500/Real Estate/CCI&quot;, &quot;S&amp;P500/Consumer Discretionary/CCL&quot;, &quot;S&amp;P500/Information Technology/CDNS&quot;, &quot;S&amp;P500/Health Care/CERN&quot;, &quot;S&amp;P500/Materials/CF&quot;, &quot;S&amp;P500/Financials/CFG&quot;, &quot;S&amp;P500/Consumer Staples/CHD&quot;, &quot;S&amp;P500/Industrials/CHRW&quot;, &quot;S&amp;P500/Consumer Discretionary/CHTR&quot;, &quot;S&amp;P500/Health Care/CI&quot;, &quot;S&amp;P500/Financials/CINF&quot;, &quot;S&amp;P500/Consumer Staples/CL&quot;, &quot;S&amp;P500/Consumer Staples/CLX&quot;, &quot;S&amp;P500/Financials/CMA&quot;, &quot;S&amp;P500/Consumer Discretionary/CMCSA&quot;, &quot;S&amp;P500/Financials/CME&quot;, &quot;S&amp;P500/Consumer Discretionary/CMG&quot;, &quot;S&amp;P500/Industrials/CMI&quot;, &quot;S&amp;P500/Utilities/CMS&quot;, &quot;S&amp;P500/Health Care/CNC&quot;, &quot;S&amp;P500/Utilities/CNP&quot;, &quot;S&amp;P500/Financials/COF&quot;, &quot;S&amp;P500/Energy/COG&quot;, &quot;S&amp;P500/Health Care/COO&quot;, &quot;S&amp;P500/Energy/COP&quot;, &quot;S&amp;P500/Consumer Staples/COST&quot;, &quot;S&amp;P500/Consumer Staples/COTY&quot;, &quot;S&amp;P500/Consumer Staples/CPB&quot;, &quot;S&amp;P500/Information Technology/CRM&quot;, &quot;S&amp;P500/Information Technology/CSCO&quot;, &quot;S&amp;P500/Industrials/CSX&quot;, &quot;S&amp;P500/Industrials/CTAS&quot;, &quot;S&amp;P500/Telecommunication Services/CTL&quot;, &quot;S&amp;P500/Information Technology/CTSH&quot;, &quot;S&amp;P500/Information Technology/CTXS&quot;, &quot;S&amp;P500/Consumer Staples/CVS&quot;, &quot;S&amp;P500/Energy/CVX&quot;, &quot;S&amp;P500/Energy/CXO&quot;, &quot;S&amp;P500/Utilities/D&quot;, &quot;S&amp;P500/Industrials/DAL&quot;, &quot;S&amp;P500/Industrials/DE&quot;, &quot;S&amp;P500/Financials/DFS&quot;, &quot;S&amp;P500/Consumer Discretionary/DG&quot;, &quot;S&amp;P500/Health Care/DGX&quot;, &quot;S&amp;P500/Consumer Discretionary/DHI&quot;, &quot;S&amp;P500/Health Care/DHR&quot;, &quot;S&amp;P500/Consumer Discretionary/DIS&quot;, &quot;S&amp;P500/Consumer Discretionary/DISCA&quot;, &quot;S&amp;P500/Consumer Discretionary/DISCK&quot;, &quot;S&amp;P500/Consumer Discretionary/DISH&quot;, &quot;S&amp;P500/Real Estate/DLR&quot;, &quot;S&amp;P500/Consumer Discretionary/DLTR&quot;, &quot;S&amp;P500/Industrials/DOV&quot;, &quot;S&amp;P500/Real Estate/DRE&quot;, &quot;S&amp;P500/Consumer Discretionary/DRI&quot;, &quot;S&amp;P500/Utilities/DTE&quot;, &quot;S&amp;P500/Utilities/DUK&quot;, &quot;S&amp;P500/Health Care/DVA&quot;, &quot;S&amp;P500/Energy/DVN&quot;, &quot;S&amp;P500/Information Technology/DXC&quot;, &quot;S&amp;P500/Information Technology/EA&quot;, &quot;S&amp;P500/Information Technology/EBAY&quot;, &quot;S&amp;P500/Materials/ECL&quot;, &quot;S&amp;P500/Utilities/ED&quot;, &quot;S&amp;P500/Industrials/EFX&quot;, &quot;S&amp;P500/Utilities/EIX&quot;, &quot;S&amp;P500/Consumer Staples/EL&quot;, &quot;S&amp;P500/Materials/EMN&quot;, &quot;S&amp;P500/Industrials/EMR&quot;, &quot;S&amp;P500/Energy/EOG&quot;, &quot;S&amp;P500/Real Estate/EQIX&quot;, &quot;S&amp;P500/Real Estate/EQR&quot;, &quot;S&amp;P500/Utilities/ES&quot;, &quot;S&amp;P500/Real Estate/ESS&quot;, &quot;S&amp;P500/Industrials/ETN&quot;, &quot;S&amp;P500/Utilities/ETR&quot;, &quot;S&amp;P500/Health Care/EW&quot;, &quot;S&amp;P500/Utilities/EXC&quot;, &quot;S&amp;P500/Industrials/EXPD&quot;, &quot;S&amp;P500/Consumer Discretionary/EXPE&quot;, &quot;S&amp;P500/Real Estate/EXR&quot;, &quot;S&amp;P500/Consumer Discretionary/F&quot;, &quot;S&amp;P500/Industrials/FAST&quot;, &quot;S&amp;P500/Information Technology/FB&quot;, &quot;S&amp;P500/Industrials/FBHS&quot;, &quot;S&amp;P500/Materials/FCX&quot;, &quot;S&amp;P500/Industrials/FDX&quot;, &quot;S&amp;P500/Utilities/FE&quot;, &quot;S&amp;P500/Information Technology/FFIV&quot;, &quot;S&amp;P500/Information Technology/FIS&quot;, &quot;S&amp;P500/Information Technology/FISV&quot;, &quot;S&amp;P500/Financials/FITB&quot;, &quot;S&amp;P500/Information Technology/FLIR&quot;, &quot;S&amp;P500/Industrials/FLS&quot;, &quot;S&amp;P500/Materials/FMC&quot;, &quot;S&amp;P500/Consumer Discretionary/FOX&quot;, &quot;S&amp;P500/Consumer Discretionary/FOXA&quot;, &quot;S&amp;P500/Real Estate/FRT&quot;, &quot;S&amp;P500/Energy/FTI&quot;, &quot;S&amp;P500/Industrials/FTV&quot;, &quot;S&amp;P500/Industrials/GD&quot;, &quot;S&amp;P500/Industrials/GE&quot;, &quot;S&amp;P500/Health Care/GILD&quot;, &quot;S&amp;P500/Consumer Staples/GIS&quot;, &quot;S&amp;P500/Information Technology/GLW&quot;, &quot;S&amp;P500/Consumer Discretionary/GM&quot;, &quot;S&amp;P500/Information Technology/GOOG&quot;, &quot;S&amp;P500/Information Technology/GOOGL&quot;, &quot;S&amp;P500/Consumer Discretionary/GPC&quot;, &quot;S&amp;P500/Information Technology/GPN&quot;, &quot;S&amp;P500/Consumer Discretionary/GRMN&quot;, &quot;S&amp;P500/Financials/GS&quot;, &quot;S&amp;P500/Industrials/GWW&quot;, &quot;S&amp;P500/Energy/HAL&quot;, &quot;S&amp;P500/Consumer Discretionary/HAS&quot;, &quot;S&amp;P500/Financials/HBAN&quot;, &quot;S&amp;P500/Consumer Discretionary/HBI&quot;, &quot;S&amp;P500/Health Care/HCA&quot;, &quot;S&amp;P500/Consumer Discretionary/HD&quot;, &quot;S&amp;P500/Energy/HES&quot;, &quot;S&amp;P500/Financials/HIG&quot;, &quot;S&amp;P500/Industrials/HII&quot;, &quot;S&amp;P500/Consumer Discretionary/HLT&quot;, &quot;S&amp;P500/Health Care/HOLX&quot;, &quot;S&amp;P500/Industrials/HON&quot;, &quot;S&amp;P500/Information Technology/HPE&quot;, &quot;S&amp;P500/Information Technology/HPQ&quot;, &quot;S&amp;P500/Financials/HRB&quot;, &quot;S&amp;P500/Consumer Staples/HRL&quot;, &quot;S&amp;P500/Health Care/HSIC&quot;, &quot;S&amp;P500/Real Estate/HST&quot;, &quot;S&amp;P500/Consumer Staples/HSY&quot;, &quot;S&amp;P500/Health Care/HUM&quot;, &quot;S&amp;P500/Information Technology/IBM&quot;, &quot;S&amp;P500/Financials/ICE&quot;, &quot;S&amp;P500/Health Care/IDXX&quot;, &quot;S&amp;P500/Materials/IFF&quot;, &quot;S&amp;P500/Health Care/ILMN&quot;, &quot;S&amp;P500/Health Care/INCY&quot;, &quot;S&amp;P500/Industrials/INFO&quot;, &quot;S&amp;P500/Information Technology/INTC&quot;, &quot;S&amp;P500/Information Technology/INTU&quot;, &quot;S&amp;P500/Materials/IP&quot;, &quot;S&amp;P500/Consumer Discretionary/IPG&quot;, &quot;S&amp;P500/Health Care/IQV&quot;, &quot;S&amp;P500/Industrials/IR&quot;, &quot;S&amp;P500/Real Estate/IRM&quot;, &quot;S&amp;P500/Health Care/ISRG&quot;, &quot;S&amp;P500/Information Technology/IT&quot;, &quot;S&amp;P500/Industrials/ITW&quot;, &quot;S&amp;P500/Financials/IVZ&quot;, &quot;S&amp;P500/Industrials/JBHT&quot;, &quot;S&amp;P500/Industrials/JCI&quot;, &quot;S&amp;P500/Health Care/JNJ&quot;, &quot;S&amp;P500/Information Technology/JNPR&quot;, &quot;S&amp;P500/Financials/JPM&quot;, &quot;S&amp;P500/Consumer Staples/K&quot;, &quot;S&amp;P500/Financials/KEY&quot;, &quot;S&amp;P500/Consumer Staples/KHC&quot;, &quot;S&amp;P500/Real Estate/KIM&quot;, &quot;S&amp;P500/Information Technology/KLAC&quot;, &quot;S&amp;P500/Consumer Staples/KMB&quot;, &quot;S&amp;P500/Energy/KMI&quot;, &quot;S&amp;P500/Consumer Discretionary/KMX&quot;, &quot;S&amp;P500/Consumer Staples/KO&quot;, &quot;S&amp;P500/Consumer Staples/KR&quot;, &quot;S&amp;P500/Consumer Discretionary/KSS&quot;, &quot;S&amp;P500/Industrials/KSU&quot;, &quot;S&amp;P500/Financials/L&quot;, &quot;S&amp;P500/Consumer Discretionary/LB&quot;, &quot;S&amp;P500/Consumer Discretionary/LEG&quot;, &quot;S&amp;P500/Consumer Discretionary/LEN&quot;, &quot;S&amp;P500/Health Care/LH&quot;, &quot;S&amp;P500/Consumer Discretionary/LKQ&quot;, &quot;S&amp;P500/Health Care/LLY&quot;, &quot;S&amp;P500/Industrials/LMT&quot;, &quot;S&amp;P500/Financials/LNC&quot;, &quot;S&amp;P500/Utilities/LNT&quot;, &quot;S&amp;P500/Consumer Discretionary/LOW&quot;, &quot;S&amp;P500/Information Technology/LRCX&quot;, &quot;S&amp;P500/Industrials/LUV&quot;, &quot;S&amp;P500/Materials/LYB&quot;, &quot;S&amp;P500/Information Technology/MA&quot;, &quot;S&amp;P500/Real Estate/MAA&quot;, &quot;S&amp;P500/Consumer Discretionary/MAR&quot;, &quot;S&amp;P500/Industrials/MAS&quot;, &quot;S&amp;P500/Consumer Discretionary/MCD&quot;, &quot;S&amp;P500/Information Technology/MCHP&quot;, &quot;S&amp;P500/Health Care/MCK&quot;, &quot;S&amp;P500/Financials/MCO&quot;, &quot;S&amp;P500/Consumer Staples/MDLZ&quot;, &quot;S&amp;P500/Health Care/MDT&quot;, &quot;S&amp;P500/Financials/MET&quot;, &quot;S&amp;P500/Consumer Discretionary/MGM&quot;, &quot;S&amp;P500/Consumer Discretionary/MHK&quot;, &quot;S&amp;P500/Consumer Staples/MKC&quot;, &quot;S&amp;P500/Materials/MLM&quot;, &quot;S&amp;P500/Financials/MMC&quot;, &quot;S&amp;P500/Industrials/MMM&quot;, &quot;S&amp;P500/Consumer Staples/MNST&quot;, &quot;S&amp;P500/Consumer Staples/MO&quot;, &quot;S&amp;P500/Materials/MOS&quot;, &quot;S&amp;P500/Energy/MPC&quot;, &quot;S&amp;P500/Health Care/MRK&quot;, &quot;S&amp;P500/Financials/MS&quot;, &quot;S&amp;P500/Information Technology/MSFT&quot;, &quot;S&amp;P500/Information Technology/MSI&quot;, &quot;S&amp;P500/Financials/MTB&quot;, &quot;S&amp;P500/Health Care/MTD&quot;, &quot;S&amp;P500/Information Technology/MU&quot;, &quot;S&amp;P500/Health Care/MYL&quot;, &quot;S&amp;P500/Energy/NBL&quot;, &quot;S&amp;P500/Consumer Discretionary/NCLH&quot;, &quot;S&amp;P500/Financials/NDAQ&quot;, &quot;S&amp;P500/Utilities/NEE&quot;, &quot;S&amp;P500/Materials/NEM&quot;, &quot;S&amp;P500/Information Technology/NFLX&quot;, &quot;S&amp;P500/Utilities/NI&quot;, &quot;S&amp;P500/Consumer Discretionary/NKE&quot;, &quot;S&amp;P500/Industrials/NLSN&quot;, &quot;S&amp;P500/Industrials/NOC&quot;, &quot;S&amp;P500/Energy/NOV&quot;, &quot;S&amp;P500/Utilities/NRG&quot;, &quot;S&amp;P500/Industrials/NSC&quot;, &quot;S&amp;P500/Information Technology/NTAP&quot;, &quot;S&amp;P500/Financials/NTRS&quot;, &quot;S&amp;P500/Materials/NUE&quot;, &quot;S&amp;P500/Information Technology/NVDA&quot;, &quot;S&amp;P500/Consumer Discretionary/NWL&quot;, &quot;S&amp;P500/Consumer Discretionary/NWS&quot;, &quot;S&amp;P500/Consumer Discretionary/NWSA&quot;, &quot;S&amp;P500/Real Estate/O&quot;, &quot;S&amp;P500/Consumer Discretionary/OMC&quot;, &quot;S&amp;P500/Information Technology/ORCL&quot;, &quot;S&amp;P500/Consumer Discretionary/ORLY&quot;, &quot;S&amp;P500/Information Technology/PAYX&quot;, &quot;S&amp;P500/Financials/PBCT&quot;, &quot;S&amp;P500/Industrials/PCAR&quot;, &quot;S&amp;P500/Utilities/PEG&quot;, &quot;S&amp;P500/Consumer Staples/PEP&quot;, &quot;S&amp;P500/Health Care/PFE&quot;, &quot;S&amp;P500/Financials/PFG&quot;, &quot;S&amp;P500/Consumer Staples/PG&quot;, &quot;S&amp;P500/Financials/PGR&quot;, &quot;S&amp;P500/Industrials/PH&quot;, &quot;S&amp;P500/Consumer Discretionary/PHM&quot;, &quot;S&amp;P500/Materials/PKG&quot;, &quot;S&amp;P500/Health Care/PKI&quot;, &quot;S&amp;P500/Real Estate/PLD&quot;, &quot;S&amp;P500/Consumer Staples/PM&quot;, &quot;S&amp;P500/Financials/PNC&quot;, &quot;S&amp;P500/Industrials/PNR&quot;, &quot;S&amp;P500/Utilities/PNW&quot;, &quot;S&amp;P500/Materials/PPG&quot;, &quot;S&amp;P500/Utilities/PPL&quot;, &quot;S&amp;P500/Health Care/PRGO&quot;, &quot;S&amp;P500/Financials/PRU&quot;, &quot;S&amp;P500/Real Estate/PSA&quot;, &quot;S&amp;P500/Energy/PSX&quot;, &quot;S&amp;P500/Consumer Discretionary/PVH&quot;, &quot;S&amp;P500/Industrials/PWR&quot;, &quot;S&amp;P500/Energy/PXD&quot;, &quot;S&amp;P500/Information Technology/PYPL&quot;, &quot;S&amp;P500/Information Technology/QCOM&quot;, &quot;S&amp;P500/Information Technology/QRVO&quot;, &quot;S&amp;P500/Consumer Discretionary/RCL&quot;, &quot;S&amp;P500/Financials/RE&quot;, &quot;S&amp;P500/Real Estate/REG&quot;, &quot;S&amp;P500/Health Care/REGN&quot;, &quot;S&amp;P500/Financials/RF&quot;, &quot;S&amp;P500/Industrials/RHI&quot;, &quot;S&amp;P500/Financials/RJF&quot;, &quot;S&amp;P500/Consumer Discretionary/RL&quot;, &quot;S&amp;P500/Health Care/RMD&quot;, &quot;S&amp;P500/Industrials/ROK&quot;, &quot;S&amp;P500/Industrials/ROP&quot;, &quot;S&amp;P500/Consumer Discretionary/ROST&quot;, &quot;S&amp;P500/Industrials/RSG&quot;, &quot;S&amp;P500/Real Estate/SBAC&quot;, &quot;S&amp;P500/Consumer Discretionary/SBUX&quot;, &quot;S&amp;P500/Financials/SCHW&quot;, &quot;S&amp;P500/Materials/SEE&quot;, &quot;S&amp;P500/Materials/SHW&quot;, &quot;S&amp;P500/Consumer Staples/SJM&quot;, &quot;S&amp;P500/Energy/SLB&quot;, &quot;S&amp;P500/Real Estate/SLG&quot;, &quot;S&amp;P500/Consumer Discretionary/SNA&quot;, &quot;S&amp;P500/Information Technology/SNPS&quot;, &quot;S&amp;P500/Utilities/SO&quot;, &quot;S&amp;P500/Real Estate/SPG&quot;, &quot;S&amp;P500/Financials/SPGI&quot;, &quot;S&amp;P500/Utilities/SRE&quot;, &quot;S&amp;P500/Financials/STT&quot;, &quot;S&amp;P500/Information Technology/STX&quot;, &quot;S&amp;P500/Consumer Staples/STZ&quot;, &quot;S&amp;P500/Consumer Discretionary/SWK&quot;, &quot;S&amp;P500/Information Technology/SWKS&quot;, &quot;S&amp;P500/Financials/SYF&quot;, &quot;S&amp;P500/Health Care/SYK&quot;, &quot;S&amp;P500/Consumer Staples/SYY&quot;, &quot;S&amp;P500/Telecommunication Services/T&quot;, &quot;S&amp;P500/Consumer Staples/TAP&quot;, &quot;S&amp;P500/Industrials/TDG&quot;, &quot;S&amp;P500/Information Technology/TEL&quot;, &quot;S&amp;P500/Consumer Discretionary/TGT&quot;, &quot;S&amp;P500/Consumer Discretionary/TIF&quot;, &quot;S&amp;P500/Consumer Discretionary/TJX&quot;, &quot;S&amp;P500/Health Care/TMO&quot;, &quot;S&amp;P500/Consumer Discretionary/TPR&quot;, &quot;S&amp;P500/Financials/TROW&quot;, &quot;S&amp;P500/Financials/TRV&quot;, &quot;S&amp;P500/Consumer Discretionary/TSCO&quot;, &quot;S&amp;P500/Consumer Staples/TSN&quot;, &quot;S&amp;P500/Information Technology/TXN&quot;, &quot;S&amp;P500/Industrials/TXT&quot;, &quot;S&amp;P500/Consumer Discretionary/UA&quot;, &quot;S&amp;P500/Consumer Discretionary/UAA&quot;, &quot;S&amp;P500/Industrials/UAL&quot;, &quot;S&amp;P500/Real Estate/UDR&quot;, &quot;S&amp;P500/Health Care/UHS&quot;, &quot;S&amp;P500/Consumer Discretionary/ULTA&quot;, &quot;S&amp;P500/Health Care/UNH&quot;, &quot;S&amp;P500/Financials/UNM&quot;, &quot;S&amp;P500/Industrials/UNP&quot;, &quot;S&amp;P500/Industrials/UPS&quot;, &quot;S&amp;P500/Industrials/URI&quot;, &quot;S&amp;P500/Financials/USB&quot;, &quot;S&amp;P500/Information Technology/V&quot;, &quot;S&amp;P500/Health Care/VAR&quot;, &quot;S&amp;P500/Consumer Discretionary/VFC&quot;, &quot;S&amp;P500/Energy/VLO&quot;, &quot;S&amp;P500/Materials/VMC&quot;, &quot;S&amp;P500/Real Estate/VNO&quot;, &quot;S&amp;P500/Industrials/VRSK&quot;, &quot;S&amp;P500/Information Technology/VRSN&quot;, &quot;S&amp;P500/Health Care/VRTX&quot;, &quot;S&amp;P500/Real Estate/VTR&quot;, &quot;S&amp;P500/Telecommunication Services/VZ&quot;, &quot;S&amp;P500/Health Care/WAT&quot;, &quot;S&amp;P500/Consumer Staples/WBA&quot;, &quot;S&amp;P500/Information Technology/WDC&quot;, &quot;S&amp;P500/Utilities/WEC&quot;, &quot;S&amp;P500/Financials/WFC&quot;, &quot;S&amp;P500/Consumer Discretionary/WHR&quot;, &quot;S&amp;P500/Financials/WLTW&quot;, &quot;S&amp;P500/Industrials/WM&quot;, &quot;S&amp;P500/Energy/WMB&quot;, &quot;S&amp;P500/Consumer Staples/WMT&quot;, &quot;S&amp;P500/Materials/WRK&quot;, &quot;S&amp;P500/Information Technology/WU&quot;, &quot;S&amp;P500/Real Estate/WY&quot;, &quot;S&amp;P500/Consumer Discretionary/WYNN&quot;, &quot;S&amp;P500/Utilities/XEL&quot;, &quot;S&amp;P500/Information Technology/XLNX&quot;, &quot;S&amp;P500/Energy/XOM&quot;, &quot;S&amp;P500/Health Care/XRAY&quot;, &quot;S&amp;P500/Information Technology/XRX&quot;, &quot;S&amp;P500/Industrials/XYL&quot;, &quot;S&amp;P500/Consumer Discretionary/YUM&quot;, &quot;S&amp;P500/Health Care/ZBH&quot;, &quot;S&amp;P500/Financials/ZION&quot;, &quot;S&amp;P500/Health Care/ZTS&quot;, &quot;S&amp;P500/Consumer Discretionary&quot;, &quot;S&amp;P500/Consumer Staples&quot;, &quot;S&amp;P500/Energy&quot;, &quot;S&amp;P500/Financials&quot;, &quot;S&amp;P500/Health Care&quot;, &quot;S&amp;P500/Industrials&quot;, &quot;S&amp;P500/Information Technology&quot;, &quot;S&amp;P500/Materials&quot;, &quot;S&amp;P500/Real Estate&quot;, &quot;S&amp;P500/Telecommunication Services&quot;, &quot;S&amp;P500/Utilities&quot;, &quot;S&amp;P500&quot;], &quot;labels&quot;: [&quot;A&quot;, &quot;AAL&quot;, &quot;AAP&quot;, &quot;AAPL&quot;, &quot;ABBV&quot;, &quot;ABC&quot;, &quot;ABT&quot;, &quot;ACN&quot;, &quot;ADBE&quot;, &quot;ADI&quot;, &quot;ADM&quot;, &quot;ADP&quot;, &quot;ADSK&quot;, &quot;AEE&quot;, &quot;AEP&quot;, &quot;AES&quot;, &quot;AFL&quot;, &quot;AIG&quot;, &quot;AIV&quot;, &quot;AIZ&quot;, &quot;AJG&quot;, &quot;AKAM&quot;, &quot;ALB&quot;, &quot;ALGN&quot;, &quot;ALK&quot;, &quot;ALL&quot;, &quot;ALLE&quot;, &quot;ALXN&quot;, &quot;AMAT&quot;, &quot;AMD&quot;, &quot;AME&quot;, &quot;AMGN&quot;, &quot;AMP&quot;, &quot;AMT&quot;, &quot;AMZN&quot;, &quot;ANSS&quot;, &quot;ANTM&quot;, &quot;AON&quot;, &quot;AOS&quot;, &quot;APD&quot;, &quot;APH&quot;, &quot;APTV&quot;, &quot;ARE&quot;, &quot;ATVI&quot;, &quot;AVB&quot;, &quot;AVGO&quot;, &quot;AVY&quot;, &quot;AWK&quot;, &quot;AXP&quot;, &quot;AZO&quot;, &quot;BA&quot;, &quot;BAC&quot;, &quot;BAX&quot;, &quot;BBY&quot;, &quot;BDX&quot;, &quot;BEN&quot;, &quot;BIIB&quot;, &quot;BK&quot;, &quot;BLK&quot;, &quot;BLL&quot;, &quot;BMY&quot;, &quot;BSX&quot;, &quot;BWA&quot;, &quot;BXP&quot;, &quot;C&quot;, &quot;CAG&quot;, &quot;CAH&quot;, &quot;CAT&quot;, &quot;CB&quot;, &quot;CBOE&quot;, &quot;CCI&quot;, &quot;CCL&quot;, &quot;CDNS&quot;, &quot;CERN&quot;, &quot;CF&quot;, &quot;CFG&quot;, &quot;CHD&quot;, &quot;CHRW&quot;, &quot;CHTR&quot;, &quot;CI&quot;, &quot;CINF&quot;, &quot;CL&quot;, &quot;CLX&quot;, &quot;CMA&quot;, &quot;CMCSA&quot;, &quot;CME&quot;, &quot;CMG&quot;, &quot;CMI&quot;, &quot;CMS&quot;, &quot;CNC&quot;, &quot;CNP&quot;, &quot;COF&quot;, &quot;COG&quot;, &quot;COO&quot;, &quot;COP&quot;, &quot;COST&quot;, &quot;COTY&quot;, &quot;CPB&quot;, &quot;CRM&quot;, &quot;CSCO&quot;, &quot;CSX&quot;, &quot;CTAS&quot;, &quot;CTL&quot;, &quot;CTSH&quot;, &quot;CTXS&quot;, &quot;CVS&quot;, &quot;CVX&quot;, &quot;CXO&quot;, &quot;D&quot;, &quot;DAL&quot;, &quot;DE&quot;, &quot;DFS&quot;, &quot;DG&quot;, &quot;DGX&quot;, &quot;DHI&quot;, &quot;DHR&quot;, &quot;DIS&quot;, &quot;DISCA&quot;, &quot;DISCK&quot;, &quot;DISH&quot;, &quot;DLR&quot;, &quot;DLTR&quot;, &quot;DOV&quot;, &quot;DRE&quot;, &quot;DRI&quot;, &quot;DTE&quot;, &quot;DUK&quot;, &quot;DVA&quot;, &quot;DVN&quot;, &quot;DXC&quot;, &quot;EA&quot;, &quot;EBAY&quot;, &quot;ECL&quot;, &quot;ED&quot;, &quot;EFX&quot;, &quot;EIX&quot;, &quot;EL&quot;, &quot;EMN&quot;, &quot;EMR&quot;, &quot;EOG&quot;, &quot;EQIX&quot;, &quot;EQR&quot;, &quot;ES&quot;, &quot;ESS&quot;, &quot;ETN&quot;, &quot;ETR&quot;, &quot;EW&quot;, &quot;EXC&quot;, &quot;EXPD&quot;, &quot;EXPE&quot;, &quot;EXR&quot;, &quot;F&quot;, &quot;FAST&quot;, &quot;FB&quot;, &quot;FBHS&quot;, &quot;FCX&quot;, &quot;FDX&quot;, &quot;FE&quot;, &quot;FFIV&quot;, &quot;FIS&quot;, &quot;FISV&quot;, &quot;FITB&quot;, &quot;FLIR&quot;, &quot;FLS&quot;, &quot;FMC&quot;, &quot;FOX&quot;, &quot;FOXA&quot;, &quot;FRT&quot;, &quot;FTI&quot;, &quot;FTV&quot;, &quot;GD&quot;, &quot;GE&quot;, &quot;GILD&quot;, &quot;GIS&quot;, &quot;GLW&quot;, &quot;GM&quot;, &quot;GOOG&quot;, &quot;GOOGL&quot;, &quot;GPC&quot;, &quot;GPN&quot;, &quot;GRMN&quot;, &quot;GS&quot;, &quot;GWW&quot;, &quot;HAL&quot;, &quot;HAS&quot;, &quot;HBAN&quot;, &quot;HBI&quot;, &quot;HCA&quot;, &quot;HD&quot;, &quot;HES&quot;, &quot;HIG&quot;, &quot;HII&quot;, &quot;HLT&quot;, &quot;HOLX&quot;, &quot;HON&quot;, &quot;HPE&quot;, &quot;HPQ&quot;, &quot;HRB&quot;, &quot;HRL&quot;, &quot;HSIC&quot;, &quot;HST&quot;, &quot;HSY&quot;, &quot;HUM&quot;, &quot;IBM&quot;, &quot;ICE&quot;, &quot;IDXX&quot;, &quot;IFF&quot;, &quot;ILMN&quot;, &quot;INCY&quot;, &quot;INFO&quot;, &quot;INTC&quot;, &quot;INTU&quot;, &quot;IP&quot;, &quot;IPG&quot;, &quot;IQV&quot;, &quot;IR&quot;, &quot;IRM&quot;, &quot;ISRG&quot;, &quot;IT&quot;, &quot;ITW&quot;, &quot;IVZ&quot;, &quot;JBHT&quot;, &quot;JCI&quot;, &quot;JNJ&quot;, &quot;JNPR&quot;, &quot;JPM&quot;, &quot;K&quot;, &quot;KEY&quot;, &quot;KHC&quot;, &quot;KIM&quot;, &quot;KLAC&quot;, &quot;KMB&quot;, &quot;KMI&quot;, &quot;KMX&quot;, &quot;KO&quot;, &quot;KR&quot;, &quot;KSS&quot;, &quot;KSU&quot;, &quot;L&quot;, &quot;LB&quot;, &quot;LEG&quot;, &quot;LEN&quot;, &quot;LH&quot;, &quot;LKQ&quot;, &quot;LLY&quot;, &quot;LMT&quot;, &quot;LNC&quot;, &quot;LNT&quot;, &quot;LOW&quot;, &quot;LRCX&quot;, &quot;LUV&quot;, &quot;LYB&quot;, &quot;MA&quot;, &quot;MAA&quot;, &quot;MAR&quot;, &quot;MAS&quot;, &quot;MCD&quot;, &quot;MCHP&quot;, &quot;MCK&quot;, &quot;MCO&quot;, &quot;MDLZ&quot;, &quot;MDT&quot;, &quot;MET&quot;, &quot;MGM&quot;, &quot;MHK&quot;, &quot;MKC&quot;, &quot;MLM&quot;, &quot;MMC&quot;, &quot;MMM&quot;, &quot;MNST&quot;, &quot;MO&quot;, &quot;MOS&quot;, &quot;MPC&quot;, &quot;MRK&quot;, &quot;MS&quot;, &quot;MSFT&quot;, &quot;MSI&quot;, &quot;MTB&quot;, &quot;MTD&quot;, &quot;MU&quot;, &quot;MYL&quot;, &quot;NBL&quot;, &quot;NCLH&quot;, &quot;NDAQ&quot;, &quot;NEE&quot;, &quot;NEM&quot;, &quot;NFLX&quot;, &quot;NI&quot;, &quot;NKE&quot;, &quot;NLSN&quot;, &quot;NOC&quot;, &quot;NOV&quot;, &quot;NRG&quot;, &quot;NSC&quot;, &quot;NTAP&quot;, &quot;NTRS&quot;, &quot;NUE&quot;, &quot;NVDA&quot;, &quot;NWL&quot;, &quot;NWS&quot;, &quot;NWSA&quot;, &quot;O&quot;, &quot;OMC&quot;, &quot;ORCL&quot;, &quot;ORLY&quot;, &quot;PAYX&quot;, &quot;PBCT&quot;, &quot;PCAR&quot;, &quot;PEG&quot;, &quot;PEP&quot;, &quot;PFE&quot;, &quot;PFG&quot;, &quot;PG&quot;, &quot;PGR&quot;, &quot;PH&quot;, &quot;PHM&quot;, &quot;PKG&quot;, &quot;PKI&quot;, &quot;PLD&quot;, &quot;PM&quot;, &quot;PNC&quot;, &quot;PNR&quot;, &quot;PNW&quot;, &quot;PPG&quot;, &quot;PPL&quot;, &quot;PRGO&quot;, &quot;PRU&quot;, &quot;PSA&quot;, &quot;PSX&quot;, &quot;PVH&quot;, &quot;PWR&quot;, &quot;PXD&quot;, &quot;PYPL&quot;, &quot;QCOM&quot;, &quot;QRVO&quot;, &quot;RCL&quot;, &quot;RE&quot;, &quot;REG&quot;, &quot;REGN&quot;, &quot;RF&quot;, &quot;RHI&quot;, &quot;RJF&quot;, &quot;RL&quot;, &quot;RMD&quot;, &quot;ROK&quot;, &quot;ROP&quot;, &quot;ROST&quot;, &quot;RSG&quot;, &quot;SBAC&quot;, &quot;SBUX&quot;, &quot;SCHW&quot;, &quot;SEE&quot;, &quot;SHW&quot;, &quot;SJM&quot;, &quot;SLB&quot;, &quot;SLG&quot;, &quot;SNA&quot;, &quot;SNPS&quot;, &quot;SO&quot;, &quot;SPG&quot;, &quot;SPGI&quot;, &quot;SRE&quot;, &quot;STT&quot;, &quot;STX&quot;, &quot;STZ&quot;, &quot;SWK&quot;, &quot;SWKS&quot;, &quot;SYF&quot;, &quot;SYK&quot;, &quot;SYY&quot;, &quot;T&quot;, &quot;TAP&quot;, &quot;TDG&quot;, &quot;TEL&quot;, &quot;TGT&quot;, &quot;TIF&quot;, &quot;TJX&quot;, &quot;TMO&quot;, &quot;TPR&quot;, &quot;TROW&quot;, &quot;TRV&quot;, &quot;TSCO&quot;, &quot;TSN&quot;, &quot;TXN&quot;, &quot;TXT&quot;, &quot;UA&quot;, &quot;UAA&quot;, &quot;UAL&quot;, &quot;UDR&quot;, &quot;UHS&quot;, &quot;ULTA&quot;, &quot;UNH&quot;, &quot;UNM&quot;, &quot;UNP&quot;, &quot;UPS&quot;, &quot;URI&quot;, &quot;USB&quot;, &quot;V&quot;, &quot;VAR&quot;, &quot;VFC&quot;, &quot;VLO&quot;, &quot;VMC&quot;, &quot;VNO&quot;, &quot;VRSK&quot;, &quot;VRSN&quot;, &quot;VRTX&quot;, &quot;VTR&quot;, &quot;VZ&quot;, &quot;WAT&quot;, &quot;WBA&quot;, &quot;WDC&quot;, &quot;WEC&quot;, &quot;WFC&quot;, &quot;WHR&quot;, &quot;WLTW&quot;, &quot;WM&quot;, &quot;WMB&quot;, &quot;WMT&quot;, &quot;WRK&quot;, &quot;WU&quot;, &quot;WY&quot;, &quot;WYNN&quot;, &quot;XEL&quot;, &quot;XLNX&quot;, &quot;XOM&quot;, &quot;XRAY&quot;, &quot;XRX&quot;, &quot;XYL&quot;, &quot;YUM&quot;, &quot;ZBH&quot;, &quot;ZION&quot;, &quot;ZTS&quot;, &quot;Consumer Discretionary&quot;, &quot;Consumer Staples&quot;, &quot;Energy&quot;, &quot;Financials&quot;, &quot;Health Care&quot;, &quot;Industrials&quot;, &quot;Information Technology&quot;, &quot;Materials&quot;, &quot;Real Estate&quot;, &quot;Telecommunication Services&quot;, &quot;Utilities&quot;, &quot;S&amp;P500&quot;], &quot;marker&quot;: {&quot;coloraxis&quot;: &quot;coloraxis&quot;, &quot;colors&quot;: [-0.005211944444846901, 0.005146316851280463, 0.011968566534969001, 0.005721207777088747, -0.011370893938062444, 0.0025971937237106875, -0.002372214723871919, 0.005272199508263387, 0.014046171434700392, 0.004856033766381212, 0.004022635445282359, 0.005636630473284431, 0.012320552976079856, 0.003960345913192124, 0.003949750462976146, -0.0006535824688885981, 0.0016672729595150486, -0.011290153144923215, -0.02431644524435351, 0.0009328985099824269, -0.006870038787552745, 0.014138768947978423, -0.023835934103786035, 0.0016831583882636109, -0.003643770347538094, 0.00760233594907773, 0.002494049639971337, 0.009950924944506312, 0.014560545778284356, 0.01940068129234403, -0.019078068960887203, 0.01312129489586959, -0.007470290940363182, -0.0038263277598695206, 0.019482404978986515, 0.013988299224205225, -0.004564176217167694, -0.015442096877633881, 0.01304744011342674, -0.010212143410935905, -0.0014609973322396606, 0.0013056062916759498, -0.012467031090422463, 0.019359773463067796, -0.019693595797618094, 0.009483591955420617, -0.0116649336393489, 0.007718224871634394, 0.0012004715043340687, 0.013673830991563414, 0.008771546681345586, -0.017252009632601345, -0.007861734439547486, 0.0023032043139229745, -0.006443553574806066, -0.005208710836239727, 0.010976285251000676, -0.0038263289566380426, -0.00185005677303454, -0.006256355222867376, -0.004723203491505887, -0.011609804743006703, 0.006533944677330572, -0.011758013641352427, -0.014494205823317674, 0.021075781492984478, 0.005660985266729233, -0.01005152858693978, -0.014630640149051377, -0.004186843466014379, -0.0028177378515077687, -0.02007882668612668, 0.014678239162095529, 0.0007976719115372684, -0.008692078999113726, -0.03238322668258034, -0.007955116411373761, 0.00614884208453846, -0.0009546343850366932, 0.001058337992789738, -0.0037652756481916954, 0.00230998781569137, 0.016191180385520483, -0.03839986235413864, 0.01183236173731168, -0.014052599571629213, 0.006379417787741427, -0.012730418554944277, 0.006885414858035954, 0.0059810174249736265, -0.02713147212375979, -0.018322391834393464, 0.027165001712310278, 0.0024197398754250304, -0.038357302497295756, 0.011477776333029741, 0.014012325608763202, 0.0018117760775266853, 0.004054902507402308, 0.016214058199933302, -0.012325226772562032, -0.02634409349027191, -0.0035226686402254105, -0.012624390742221523, 0.035769992225420856, 0.0023672682215463573, -0.015302922743007145, -0.026566594029823167, -0.013314158256335444, 0.004115487088473426, 9.664664147547687e-05, -0.005576736180525364, 0.011482655647318393, 0.0022719931803685057, -0.024398452513502887, -4.099481359057324e-05, 0.006209784440618377, 0.0009871167083228066, -0.006190276193262106, -0.01949171430778312, -0.004943581670782496, 0.02705939001097488, -0.001440242232707276, -5.2348968509810614e-05, 0.0013531746899852208, -0.012368130145702216, -0.0045192771169053365, 0.01500195787747678, -0.04047937246501265, -0.0180832202994249, 0.01929854568673626, -0.0047585359660166715, -0.005398415133827148, -0.003971774933591539, -0.003159623880077383, -0.008509337198586247, -0.006204115653058076, -0.017427212196990192, -0.031035360920482296, -0.04040250856652219, 0.006076612760791995, -0.008254964025256271, -0.00793711853297717, -0.015371717167891494, -0.01267592588905073, -0.0026691850948117337, -0.006672618467486239, 0.002546239665763615, 0.0046728889915320935, -0.010138051676533269, -0.017028131686928363, -0.022876709173270632, -0.0011765165884513257, 0.0014556290277680155, -0.011718181587976726, -0.006770270441361845, 0.0007934566284133259, 0.001799617170908583, -0.00016460199026139757, 0.007523192081407915, 0.0010091901522352746, -0.02391965972170854, 0.02606490896695029, -0.02975968459035388, -0.0036094809290794184, 0.009749274813400101, 0.005992875915388915, -0.020055081217117337, -0.021380588742550454, -0.016100201624195423, -0.010560972773461361, -0.019622962971670457, 0.010338335549579542, -0.0017398360622333663, -0.0059483483233222405, -0.01940713768491655, 0.0026913649541678163, 0.002831401211770181, -0.006679291235580846, 0.0009841409352427104, -0.0031009608223211064, 0.0013612162920702854, -0.006302337883429149, -0.04706940729863944, 0.02304156761706902, -0.022816977205098975, -0.007195381559930038, 0.0034897870119070173, 0.010827357272847057, -0.04816605870806598, -0.0034630604649506223, 0.0032868803998141994, 0.0003785646019073803, -0.019933991414622945, -0.01132164673949048, -0.010555292433670259, -0.003466003438362531, 0.004766809691879001, 0.008129621013979474, -0.005173678360449004, 0.007097297507087319, 0.0014963929324440777, 0.0001642544674747643, 0.009333061790035782, -0.006828871068624925, 0.003431716247592465, -0.006721615834976336, 0.016311887783871354, 0.006089167330283835, -0.013410589633505625, 0.02921890594477179, 0.006419562906692098, -0.014169395019772494, 0.010025819508912, -0.008897690940054123, -0.013823721027156698, -0.005809820916215805, -0.006916568674655936, 0.003096575609895901, -0.0007323925570659076, -0.023058177737641333, -0.006105815100516674, -0.010265431559030425, 0.002195808669378473, 0.003680015354494763, -0.01170215895156367, 0.004072078443906747, -0.02446588686332279, 0.002154506258865259, -0.03293056541309831, 0.02948261218843561, 0.008152054369699074, -0.03183224522324732, -0.01241379007502522, -0.001199416829058241, 0.010374842311749328, -0.047110865183773075, -0.0197302726478458, 0.004232775594714922, -0.03536595462918194, -0.009235549182613179, -0.037544597114206595, 0.0014087679022756537, -0.02690816199168261, 0.009439501651635712, -0.009388131842584491, 0.004085785776588883, 0.004569041699351878, -0.00913971972907641, 0.015408561212144142, 0.010441857580937911, -0.023675445366839652, 0.0039440335579645595, -0.010657467937122139, -0.010184733466565047, -0.01335095070469442, -0.009279655263833226, 0.023712270861135958, 0.0031616805252892588, -0.00296788060072629, 0.006595719449469191, -0.003589994217966392, -0.013984346557428324, -0.014993353594555765, 0.01741276896312713, -0.007624898675210386, -0.01984931559677483, -0.0039768186034041175, -0.0014367784175169662, 0.003009341799971624, -0.0061432898309723765, -0.012362865272747806, -0.029999831516122222, 0.003181155356062412, -0.005949437302202008, 0.014079913775493105, -0.00540143048284831, -0.02011971694261844, -0.006273016296312799, 0.012994654255559093, 0.019319568625946457, -0.02486028505486566, -0.00723675969639415, -0.0013859218107946686, 0.003440942164727904, 0.011571989916356714, 0.021827105328401344, 0.005301639328776007, -0.005204270629748287, -0.021232080112079922, -0.0011901173389977802, -0.0230855041346655, -0.016324016071984002, -0.011086256547186714, 0.005744794758890263, -0.0009270297945173961, -0.014475209384937078, 0.016505133531003607, 0.006942228824096058, -0.0032047963059406363, -0.009717201704225858, -0.023597456624024158, 0.001773164898074631, 0.012161178006862733, 0.012124694918575775, -0.002602194966189144, -0.00048757850988621025, -0.00042657659893862343, -0.0015911640979352109, 0.00944620114219623, 0.008146484147308676, -0.020251277805570133, 0.0029940294974010965, 0.003933568573636418, -0.007881089623740971, -0.03208002738278349, -0.002818124206272134, -0.0120329546899741, 0.002750006546750386, 0.0011355560502218597, -0.017913485156501235, -0.01443953737103722, -0.0020108650655840093, -0.013768245617619445, -0.009757351780341088, 0.0187964441543274, -0.01901931192244597, -0.005234360091881988, -0.00867343603403108, -0.017457106789277477, -0.0219483543574864, -0.043827293506679695, 0.0010825507949443177, 0.011001484646967808, 0.005795453434319594, -0.001391595661048713, -0.017537793875044137, -0.006978265068693423, 0.01885642819880629, -0.023228165294318707, -0.008718019641592249, -0.008318083562560157, -0.009708907431549304, 0.020634724524799707, -0.013050491714356554, -0.015522851875552374, -0.0030903662706156377, -0.010359240255954156, -0.0015030114373353284, 0.004116013979666568, -0.01104421493669352, -0.010352757313619378, 9.1706665877836e-05, 0.008144558168700662, -0.04394682402970106, -0.024855605407227494, -0.002348781745213443, 0.0017035966500550783, -0.0051256268765335995, -0.014665506912407944, -0.0038681085021168332, -0.016243655785616273, -0.014283023234911986, 0.0035003608296690527, -0.018910561725526825, -0.013241068446344425, 0.01340427710072182, -0.023515501483424475, -0.006937355714453126, 0.0039413272836162595, -0.0047819488446779485, -0.006209883105524621, -0.015375226889448472, 0.001828845542070831, 0.008497711098673415, -0.008860878815813985, 0.0004174320942352367, -0.007202762885780295, -0.04828940525007715, -0.0060624228765978035, -0.0029864074678875433, -0.0021639572658213946, 0.019242405742436793, 0.011016626122169785, -0.004444182664528521, -0.016264252359868055, -0.015550714337290984, -0.0029850004191367277, -0.012654322633433777, 0.0033511618575198508, -0.0028379227702359684, 0.004129111703455036, -0.010589347569006789, -0.01391231350295036, 0.0021269144750821595, 0.0065829786458879505, -0.020473956048845648, -0.0018418837533784888, -0.01268207769855442, -0.01176635834565386, -0.009163805209150092, -0.0067819996734434645, -0.008980816172689235, -0.0015079104677364942, 0.005344507525759112, 0.01469448885212424, -0.0045997584717260695, 0.007629492017963376, 0.0023448517684944085, 0.013593261042162026, 0.020555458651332282, 0.0030359440975371438, -0.007094343462732031, -0.03088312940650026, 0.00015933889095972117, -0.011309607785439364, -0.012663840035949534, 0.02127575067156738, 0.009018830219523847, 6.315524674316549e-05, -0.01640729118118777, 0.007420185868078578, 0.006318139386562491, 0.010253854498463177, -0.009476366380592365, -0.006798807046316282, -0.015514751471628483, -0.0015528394995825737, -0.0049114501085677385, -0.012867204325537788, -0.02075611510579531, -0.01256373658423224, 0.003521204420418416, 0.005719012949767131, -0.022086897475122217, -0.010657175920663503, 0.0015152601909513225, -0.006402095050403783, 0.0074087890498917485, -0.009221511024211728, -0.00792355419240285, 0.000969585125463593, -0.0032122956448452345, -0.00023398907171855293]}, &quot;name&quot;: &quot;&quot;, &quot;parents&quot;: [&quot;S&amp;P500/Health Care&quot;, &quot;S&amp;P500/Industrials&quot;, &quot;S&amp;P500/Consumer Discretionary&quot;, &quot;S&amp;P500/Information Technology&quot;, &quot;S&amp;P500/Health Care&quot;, &quot;S&amp;P500/Health Care&quot;, &quot;S&amp;P500/Health Care&quot;, &quot;S&amp;P500/Information Technology&quot;, &quot;S&amp;P500/Information Technology&quot;, &quot;S&amp;P500/Information Technology&quot;, &quot;S&amp;P500/Consumer Staples&quot;, &quot;S&amp;P500/Information Technology&quot;, &quot;S&amp;P500/Information Technology&quot;, &quot;S&amp;P500/Utilities&quot;, &quot;S&amp;P500/Utilities&quot;, &quot;S&amp;P500/Utilities&quot;, &quot;S&amp;P500/Financials&quot;, &quot;S&amp;P500/Financials&quot;, &quot;S&amp;P500/Real Estate&quot;, &quot;S&amp;P500/Financials&quot;, &quot;S&amp;P500/Financials&quot;, &quot;S&amp;P500/Information Technology&quot;, &quot;S&amp;P500/Materials&quot;, &quot;S&amp;P500/Health Care&quot;, &quot;S&amp;P500/Industrials&quot;, &quot;S&amp;P500/Financials&quot;, &quot;S&amp;P500/Industrials&quot;, &quot;S&amp;P500/Health Care&quot;, &quot;S&amp;P500/Information Technology&quot;, &quot;S&amp;P500/Information Technology&quot;, &quot;S&amp;P500/Industrials&quot;, &quot;S&amp;P500/Health Care&quot;, &quot;S&amp;P500/Financials&quot;, &quot;S&amp;P500/Real Estate&quot;, &quot;S&amp;P500/Consumer Discretionary&quot;, &quot;S&amp;P500/Information Technology&quot;, &quot;S&amp;P500/Health Care&quot;, &quot;S&amp;P500/Financials&quot;, &quot;S&amp;P500/Industrials&quot;, &quot;S&amp;P500/Materials&quot;, &quot;S&amp;P500/Information Technology&quot;, &quot;S&amp;P500/Consumer Discretionary&quot;, &quot;S&amp;P500/Real Estate&quot;, &quot;S&amp;P500/Information Technology&quot;, &quot;S&amp;P500/Real Estate&quot;, &quot;S&amp;P500/Information Technology&quot;, &quot;S&amp;P500/Materials&quot;, &quot;S&amp;P500/Utilities&quot;, &quot;S&amp;P500/Financials&quot;, &quot;S&amp;P500/Consumer Discretionary&quot;, &quot;S&amp;P500/Industrials&quot;, &quot;S&amp;P500/Financials&quot;, &quot;S&amp;P500/Health Care&quot;, &quot;S&amp;P500/Consumer Discretionary&quot;, &quot;S&amp;P500/Health Care&quot;, &quot;S&amp;P500/Financials&quot;, &quot;S&amp;P500/Health Care&quot;, &quot;S&amp;P500/Financials&quot;, &quot;S&amp;P500/Financials&quot;, &quot;S&amp;P500/Materials&quot;, &quot;S&amp;P500/Health Care&quot;, &quot;S&amp;P500/Health Care&quot;, &quot;S&amp;P500/Consumer Discretionary&quot;, &quot;S&amp;P500/Real Estate&quot;, &quot;S&amp;P500/Financials&quot;, &quot;S&amp;P500/Consumer Staples&quot;, &quot;S&amp;P500/Health Care&quot;, &quot;S&amp;P500/Industrials&quot;, &quot;S&amp;P500/Financials&quot;, &quot;S&amp;P500/Financials&quot;, &quot;S&amp;P500/Real Estate&quot;, &quot;S&amp;P500/Consumer Discretionary&quot;, &quot;S&amp;P500/Information Technology&quot;, &quot;S&amp;P500/Health Care&quot;, &quot;S&amp;P500/Materials&quot;, &quot;S&amp;P500/Financials&quot;, &quot;S&amp;P500/Consumer Staples&quot;, &quot;S&amp;P500/Industrials&quot;, &quot;S&amp;P500/Consumer Discretionary&quot;, &quot;S&amp;P500/Health Care&quot;, &quot;S&amp;P500/Financials&quot;, &quot;S&amp;P500/Consumer Staples&quot;, &quot;S&amp;P500/Consumer Staples&quot;, &quot;S&amp;P500/Financials&quot;, &quot;S&amp;P500/Consumer Discretionary&quot;, &quot;S&amp;P500/Financials&quot;, &quot;S&amp;P500/Consumer Discretionary&quot;, &quot;S&amp;P500/Industrials&quot;, &quot;S&amp;P500/Utilities&quot;, &quot;S&amp;P500/Health Care&quot;, &quot;S&amp;P500/Utilities&quot;, &quot;S&amp;P500/Financials&quot;, &quot;S&amp;P500/Energy&quot;, &quot;S&amp;P500/Health Care&quot;, &quot;S&amp;P500/Energy&quot;, &quot;S&amp;P500/Consumer Staples&quot;, &quot;S&amp;P500/Consumer Staples&quot;, &quot;S&amp;P500/Consumer Staples&quot;, &quot;S&amp;P500/Information Technology&quot;, &quot;S&amp;P500/Information Technology&quot;, &quot;S&amp;P500/Industrials&quot;, &quot;S&amp;P500/Industrials&quot;, &quot;S&amp;P500/Telecommunication Services&quot;, &quot;S&amp;P500/Information Technology&quot;, &quot;S&amp;P500/Information Technology&quot;, &quot;S&amp;P500/Consumer Staples&quot;, &quot;S&amp;P500/Energy&quot;, &quot;S&amp;P500/Energy&quot;, &quot;S&amp;P500/Utilities&quot;, &quot;S&amp;P500/Industrials&quot;, &quot;S&amp;P500/Industrials&quot;, &quot;S&amp;P500/Financials&quot;, &quot;S&amp;P500/Consumer Discretionary&quot;, &quot;S&amp;P500/Health Care&quot;, &quot;S&amp;P500/Consumer Discretionary&quot;, &quot;S&amp;P500/Health Care&quot;, &quot;S&amp;P500/Consumer Discretionary&quot;, &quot;S&amp;P500/Consumer Discretionary&quot;, &quot;S&amp;P500/Consumer Discretionary&quot;, &quot;S&amp;P500/Consumer Discretionary&quot;, &quot;S&amp;P500/Real Estate&quot;, &quot;S&amp;P500/Consumer Discretionary&quot;, &quot;S&amp;P500/Industrials&quot;, &quot;S&amp;P500/Real Estate&quot;, &quot;S&amp;P500/Consumer Discretionary&quot;, &quot;S&amp;P500/Utilities&quot;, &quot;S&amp;P500/Utilities&quot;, &quot;S&amp;P500/Health Care&quot;, &quot;S&amp;P500/Energy&quot;, &quot;S&amp;P500/Information Technology&quot;, &quot;S&amp;P500/Information Technology&quot;, &quot;S&amp;P500/Information Technology&quot;, &quot;S&amp;P500/Materials&quot;, &quot;S&amp;P500/Utilities&quot;, &quot;S&amp;P500/Industrials&quot;, &quot;S&amp;P500/Utilities&quot;, &quot;S&amp;P500/Consumer Staples&quot;, &quot;S&amp;P500/Materials&quot;, &quot;S&amp;P500/Industrials&quot;, &quot;S&amp;P500/Energy&quot;, &quot;S&amp;P500/Real Estate&quot;, &quot;S&amp;P500/Real Estate&quot;, &quot;S&amp;P500/Utilities&quot;, &quot;S&amp;P500/Real Estate&quot;, &quot;S&amp;P500/Industrials&quot;, &quot;S&amp;P500/Utilities&quot;, &quot;S&amp;P500/Health Care&quot;, &quot;S&amp;P500/Utilities&quot;, &quot;S&amp;P500/Industrials&quot;, &quot;S&amp;P500/Consumer Discretionary&quot;, &quot;S&amp;P500/Real Estate&quot;, &quot;S&amp;P500/Consumer Discretionary&quot;, &quot;S&amp;P500/Industrials&quot;, &quot;S&amp;P500/Information Technology&quot;, &quot;S&amp;P500/Industrials&quot;, &quot;S&amp;P500/Materials&quot;, &quot;S&amp;P500/Industrials&quot;, &quot;S&amp;P500/Utilities&quot;, &quot;S&amp;P500/Information Technology&quot;, &quot;S&amp;P500/Information Technology&quot;, &quot;S&amp;P500/Information Technology&quot;, &quot;S&amp;P500/Financials&quot;, &quot;S&amp;P500/Information Technology&quot;, &quot;S&amp;P500/Industrials&quot;, &quot;S&amp;P500/Materials&quot;, &quot;S&amp;P500/Consumer Discretionary&quot;, &quot;S&amp;P500/Consumer Discretionary&quot;, &quot;S&amp;P500/Real Estate&quot;, &quot;S&amp;P500/Energy&quot;, &quot;S&amp;P500/Industrials&quot;, &quot;S&amp;P500/Industrials&quot;, &quot;S&amp;P500/Industrials&quot;, &quot;S&amp;P500/Health Care&quot;, &quot;S&amp;P500/Consumer Staples&quot;, &quot;S&amp;P500/Information Technology&quot;, &quot;S&amp;P500/Consumer Discretionary&quot;, &quot;S&amp;P500/Information Technology&quot;, &quot;S&amp;P500/Information Technology&quot;, &quot;S&amp;P500/Consumer Discretionary&quot;, &quot;S&amp;P500/Information Technology&quot;, &quot;S&amp;P500/Consumer Discretionary&quot;, &quot;S&amp;P500/Financials&quot;, &quot;S&amp;P500/Industrials&quot;, &quot;S&amp;P500/Energy&quot;, &quot;S&amp;P500/Consumer Discretionary&quot;, &quot;S&amp;P500/Financials&quot;, &quot;S&amp;P500/Consumer Discretionary&quot;, &quot;S&amp;P500/Health Care&quot;, &quot;S&amp;P500/Consumer Discretionary&quot;, &quot;S&amp;P500/Energy&quot;, &quot;S&amp;P500/Financials&quot;, &quot;S&amp;P500/Industrials&quot;, &quot;S&amp;P500/Consumer Discretionary&quot;, &quot;S&amp;P500/Health Care&quot;, &quot;S&amp;P500/Industrials&quot;, &quot;S&amp;P500/Information Technology&quot;, &quot;S&amp;P500/Information Technology&quot;, &quot;S&amp;P500/Financials&quot;, &quot;S&amp;P500/Consumer Staples&quot;, &quot;S&amp;P500/Health Care&quot;, &quot;S&amp;P500/Real Estate&quot;, &quot;S&amp;P500/Consumer Staples&quot;, &quot;S&amp;P500/Health Care&quot;, &quot;S&amp;P500/Information Technology&quot;, &quot;S&amp;P500/Financials&quot;, &quot;S&amp;P500/Health Care&quot;, &quot;S&amp;P500/Materials&quot;, &quot;S&amp;P500/Health Care&quot;, &quot;S&amp;P500/Health Care&quot;, &quot;S&amp;P500/Industrials&quot;, &quot;S&amp;P500/Information Technology&quot;, &quot;S&amp;P500/Information Technology&quot;, &quot;S&amp;P500/Materials&quot;, &quot;S&amp;P500/Consumer Discretionary&quot;, &quot;S&amp;P500/Health Care&quot;, &quot;S&amp;P500/Industrials&quot;, &quot;S&amp;P500/Real Estate&quot;, &quot;S&amp;P500/Health Care&quot;, &quot;S&amp;P500/Information Technology&quot;, &quot;S&amp;P500/Industrials&quot;, &quot;S&amp;P500/Financials&quot;, &quot;S&amp;P500/Industrials&quot;, &quot;S&amp;P500/Industrials&quot;, &quot;S&amp;P500/Health Care&quot;, &quot;S&amp;P500/Information Technology&quot;, &quot;S&amp;P500/Financials&quot;, &quot;S&amp;P500/Consumer Staples&quot;, &quot;S&amp;P500/Financials&quot;, &quot;S&amp;P500/Consumer Staples&quot;, &quot;S&amp;P500/Real Estate&quot;, &quot;S&amp;P500/Information Technology&quot;, &quot;S&amp;P500/Consumer Staples&quot;, &quot;S&amp;P500/Energy&quot;, &quot;S&amp;P500/Consumer Discretionary&quot;, &quot;S&amp;P500/Consumer Staples&quot;, &quot;S&amp;P500/Consumer Staples&quot;, &quot;S&amp;P500/Consumer Discretionary&quot;, &quot;S&amp;P500/Industrials&quot;, &quot;S&amp;P500/Financials&quot;, &quot;S&amp;P500/Consumer Discretionary&quot;, &quot;S&amp;P500/Consumer Discretionary&quot;, &quot;S&amp;P500/Consumer Discretionary&quot;, &quot;S&amp;P500/Health Care&quot;, &quot;S&amp;P500/Consumer Discretionary&quot;, &quot;S&amp;P500/Health Care&quot;, &quot;S&amp;P500/Industrials&quot;, &quot;S&amp;P500/Financials&quot;, &quot;S&amp;P500/Utilities&quot;, &quot;S&amp;P500/Consumer Discretionary&quot;, &quot;S&amp;P500/Information Technology&quot;, &quot;S&amp;P500/Industrials&quot;, &quot;S&amp;P500/Materials&quot;, &quot;S&amp;P500/Information Technology&quot;, &quot;S&amp;P500/Real Estate&quot;, &quot;S&amp;P500/Consumer Discretionary&quot;, &quot;S&amp;P500/Industrials&quot;, &quot;S&amp;P500/Consumer Discretionary&quot;, &quot;S&amp;P500/Information Technology&quot;, &quot;S&amp;P500/Health Care&quot;, &quot;S&amp;P500/Financials&quot;, &quot;S&amp;P500/Consumer Staples&quot;, &quot;S&amp;P500/Health Care&quot;, &quot;S&amp;P500/Financials&quot;, &quot;S&amp;P500/Consumer Discretionary&quot;, &quot;S&amp;P500/Consumer Discretionary&quot;, &quot;S&amp;P500/Consumer Staples&quot;, &quot;S&amp;P500/Materials&quot;, &quot;S&amp;P500/Financials&quot;, &quot;S&amp;P500/Industrials&quot;, &quot;S&amp;P500/Consumer Staples&quot;, &quot;S&amp;P500/Consumer Staples&quot;, &quot;S&amp;P500/Materials&quot;, &quot;S&amp;P500/Energy&quot;, &quot;S&amp;P500/Health Care&quot;, &quot;S&amp;P500/Financials&quot;, &quot;S&amp;P500/Information Technology&quot;, &quot;S&amp;P500/Information Technology&quot;, &quot;S&amp;P500/Financials&quot;, &quot;S&amp;P500/Health Care&quot;, &quot;S&amp;P500/Information Technology&quot;, &quot;S&amp;P500/Health Care&quot;, &quot;S&amp;P500/Energy&quot;, &quot;S&amp;P500/Consumer Discretionary&quot;, &quot;S&amp;P500/Financials&quot;, &quot;S&amp;P500/Utilities&quot;, &quot;S&amp;P500/Materials&quot;, &quot;S&amp;P500/Information Technology&quot;, &quot;S&amp;P500/Utilities&quot;, &quot;S&amp;P500/Consumer Discretionary&quot;, &quot;S&amp;P500/Industrials&quot;, &quot;S&amp;P500/Industrials&quot;, &quot;S&amp;P500/Energy&quot;, &quot;S&amp;P500/Utilities&quot;, &quot;S&amp;P500/Industrials&quot;, &quot;S&amp;P500/Information Technology&quot;, &quot;S&amp;P500/Financials&quot;, &quot;S&amp;P500/Materials&quot;, &quot;S&amp;P500/Information Technology&quot;, &quot;S&amp;P500/Consumer Discretionary&quot;, &quot;S&amp;P500/Consumer Discretionary&quot;, &quot;S&amp;P500/Consumer Discretionary&quot;, &quot;S&amp;P500/Real Estate&quot;, &quot;S&amp;P500/Consumer Discretionary&quot;, &quot;S&amp;P500/Information Technology&quot;, &quot;S&amp;P500/Consumer Discretionary&quot;, &quot;S&amp;P500/Information Technology&quot;, &quot;S&amp;P500/Financials&quot;, &quot;S&amp;P500/Industrials&quot;, &quot;S&amp;P500/Utilities&quot;, &quot;S&amp;P500/Consumer Staples&quot;, &quot;S&amp;P500/Health Care&quot;, &quot;S&amp;P500/Financials&quot;, &quot;S&amp;P500/Consumer Staples&quot;, &quot;S&amp;P500/Financials&quot;, &quot;S&amp;P500/Industrials&quot;, &quot;S&amp;P500/Consumer Discretionary&quot;, &quot;S&amp;P500/Materials&quot;, &quot;S&amp;P500/Health Care&quot;, &quot;S&amp;P500/Real Estate&quot;, &quot;S&amp;P500/Consumer Staples&quot;, &quot;S&amp;P500/Financials&quot;, &quot;S&amp;P500/Industrials&quot;, &quot;S&amp;P500/Utilities&quot;, &quot;S&amp;P500/Materials&quot;, &quot;S&amp;P500/Utilities&quot;, &quot;S&amp;P500/Health Care&quot;, &quot;S&amp;P500/Financials&quot;, &quot;S&amp;P500/Real Estate&quot;, &quot;S&amp;P500/Energy&quot;, &quot;S&amp;P500/Consumer Discretionary&quot;, &quot;S&amp;P500/Industrials&quot;, &quot;S&amp;P500/Energy&quot;, &quot;S&amp;P500/Information Technology&quot;, &quot;S&amp;P500/Information Technology&quot;, &quot;S&amp;P500/Information Technology&quot;, &quot;S&amp;P500/Consumer Discretionary&quot;, &quot;S&amp;P500/Financials&quot;, &quot;S&amp;P500/Real Estate&quot;, &quot;S&amp;P500/Health Care&quot;, &quot;S&amp;P500/Financials&quot;, &quot;S&amp;P500/Industrials&quot;, &quot;S&amp;P500/Financials&quot;, &quot;S&amp;P500/Consumer Discretionary&quot;, &quot;S&amp;P500/Health Care&quot;, &quot;S&amp;P500/Industrials&quot;, &quot;S&amp;P500/Industrials&quot;, &quot;S&amp;P500/Consumer Discretionary&quot;, &quot;S&amp;P500/Industrials&quot;, &quot;S&amp;P500/Real Estate&quot;, &quot;S&amp;P500/Consumer Discretionary&quot;, &quot;S&amp;P500/Financials&quot;, &quot;S&amp;P500/Materials&quot;, &quot;S&amp;P500/Materials&quot;, &quot;S&amp;P500/Consumer Staples&quot;, &quot;S&amp;P500/Energy&quot;, &quot;S&amp;P500/Real Estate&quot;, &quot;S&amp;P500/Consumer Discretionary&quot;, &quot;S&amp;P500/Information Technology&quot;, &quot;S&amp;P500/Utilities&quot;, &quot;S&amp;P500/Real Estate&quot;, &quot;S&amp;P500/Financials&quot;, &quot;S&amp;P500/Utilities&quot;, &quot;S&amp;P500/Financials&quot;, &quot;S&amp;P500/Information Technology&quot;, &quot;S&amp;P500/Consumer Staples&quot;, &quot;S&amp;P500/Consumer Discretionary&quot;, &quot;S&amp;P500/Information Technology&quot;, &quot;S&amp;P500/Financials&quot;, &quot;S&amp;P500/Health Care&quot;, &quot;S&amp;P500/Consumer Staples&quot;, &quot;S&amp;P500/Telecommunication Services&quot;, &quot;S&amp;P500/Consumer Staples&quot;, &quot;S&amp;P500/Industrials&quot;, &quot;S&amp;P500/Information Technology&quot;, &quot;S&amp;P500/Consumer Discretionary&quot;, &quot;S&amp;P500/Consumer Discretionary&quot;, &quot;S&amp;P500/Consumer Discretionary&quot;, &quot;S&amp;P500/Health Care&quot;, &quot;S&amp;P500/Consumer Discretionary&quot;, &quot;S&amp;P500/Financials&quot;, &quot;S&amp;P500/Financials&quot;, &quot;S&amp;P500/Consumer Discretionary&quot;, &quot;S&amp;P500/Consumer Staples&quot;, &quot;S&amp;P500/Information Technology&quot;, &quot;S&amp;P500/Industrials&quot;, &quot;S&amp;P500/Consumer Discretionary&quot;, &quot;S&amp;P500/Consumer Discretionary&quot;, &quot;S&amp;P500/Industrials&quot;, &quot;S&amp;P500/Real Estate&quot;, &quot;S&amp;P500/Health Care&quot;, &quot;S&amp;P500/Consumer Discretionary&quot;, &quot;S&amp;P500/Health Care&quot;, &quot;S&amp;P500/Financials&quot;, &quot;S&amp;P500/Industrials&quot;, &quot;S&amp;P500/Industrials&quot;, &quot;S&amp;P500/Industrials&quot;, &quot;S&amp;P500/Financials&quot;, &quot;S&amp;P500/Information Technology&quot;, &quot;S&amp;P500/Health Care&quot;, &quot;S&amp;P500/Consumer Discretionary&quot;, &quot;S&amp;P500/Energy&quot;, &quot;S&amp;P500/Materials&quot;, &quot;S&amp;P500/Real Estate&quot;, &quot;S&amp;P500/Industrials&quot;, &quot;S&amp;P500/Information Technology&quot;, &quot;S&amp;P500/Health Care&quot;, &quot;S&amp;P500/Real Estate&quot;, &quot;S&amp;P500/Telecommunication Services&quot;, &quot;S&amp;P500/Health Care&quot;, &quot;S&amp;P500/Consumer Staples&quot;, &quot;S&amp;P500/Information Technology&quot;, &quot;S&amp;P500/Utilities&quot;, &quot;S&amp;P500/Financials&quot;, &quot;S&amp;P500/Consumer Discretionary&quot;, &quot;S&amp;P500/Financials&quot;, &quot;S&amp;P500/Industrials&quot;, &quot;S&amp;P500/Energy&quot;, &quot;S&amp;P500/Consumer Staples&quot;, &quot;S&amp;P500/Materials&quot;, &quot;S&amp;P500/Information Technology&quot;, &quot;S&amp;P500/Real Estate&quot;, &quot;S&amp;P500/Consumer Discretionary&quot;, &quot;S&amp;P500/Utilities&quot;, &quot;S&amp;P500/Information Technology&quot;, &quot;S&amp;P500/Energy&quot;, &quot;S&amp;P500/Health Care&quot;, &quot;S&amp;P500/Information Technology&quot;, &quot;S&amp;P500/Industrials&quot;, &quot;S&amp;P500/Consumer Discretionary&quot;, &quot;S&amp;P500/Health Care&quot;, &quot;S&amp;P500/Financials&quot;, &quot;S&amp;P500/Health Care&quot;, &quot;S&amp;P500&quot;, &quot;S&amp;P500&quot;, &quot;S&amp;P500&quot;, &quot;S&amp;P500&quot;, &quot;S&amp;P500&quot;, &quot;S&amp;P500&quot;, &quot;S&amp;P500&quot;, &quot;S&amp;P500&quot;, &quot;S&amp;P500&quot;, &quot;S&amp;P500&quot;, &quot;S&amp;P500&quot;, &quot;&quot;], &quot;type&quot;: &quot;treemap&quot;, &quot;values&quot;: [21984606918, 24594852352, 8123611867, 809508034020, 181386347059, 20587704101, 102121042306, 98765855553, 94550214268, 31811578855, 23594770663, 50337702249, 24348294504, 12905744906, 31701916517, 6920851212, 33422948000, 54360073164, 6156884142, 4653993594, 11968488290, 10906904066, 11782151266, 18788041378, 7903173734, 34759468905, 7599609494, 26172439795, 51296481503, 11191663795, 17139651923, 128133340000, 23472126000, 59213892640, 685873374731, 13155919129, 61221978627, 35123123422, 10783419933, 34638387128, 26955335395, 24906530300, 12043374429, 52518668144, 21856547430, 92791974933, 10104814319, 13906146184, 80410990000, 19922021415, 205617405233, 321478200969, 35713732553, 20831186176, 50910180308, 21759187973, 69157726427, 56083904906, 85907759858, 13767688518, 102506501960, 36142506007, 11596117445, 17799878487, 192709302000, 14379717835, 20493281175, 91822049046, 68424670566, 12998295607, 44183023189, 49180044050, 10890625200, 21101697598, 9209106695, 22008050974, 11838963451, 12932483889, 86708878113, 47680910480, 11916533018, 61616643498, 16540418002, 16274969256, 186476996883, 54423298745, 7685283970, 28669230787, 11873960824, 18012494506, 11362043297, 47637260000, 10808821635, 11297958140, 65482462410, 80439804508, 13101112504, 13467193376, 79489115000, 199425716482, 47340511707, 16676145923, 18237196861, 45119684067, 13199167493, 75323141722, 218978820159, 22021882339, 47543571860, 38393603535, 52186628646, 27433540000, 26580644874, 13578300000, 17390873686, 66351150000, 157817273295, 8763756733, 8320262123, 21032719056, 21400952517, 25151198417, 15566645713, 9035293365, 11745595320, 17808073691, 52078185682, 13685178000, 19317380000, 27408621020, 37890226992, 44216696399, 38460272282, 23335777662, 14121334618, 19447670886, 49543264457, 14226830813, 44040298425, 61164030149, 33333813618, 20925508143, 18027633617, 14383525286, 35961772000, 13116894887, 27447099863, 34734816899, 11040678071, 19722178609, 10133547517, 42414328338, 15311373377, 523423036576, 9624169008, 25853969330, 67280478816, 13706075072, 8744185796, 32308459680, 26918949723, 22437653700, 6706992926, 5427884956, 11273961835, 66135313503, 44027094922, 8077368506, 14163064455, 24916503061, 64180390701, 132249296250, 108106822109, 31098243069, 25759280346, 61536606173, 728535558140, 733823966137, 14554321748, 16920023264, 11840331607, 96978500251, 14639308205, 43356557470, 12732072001, 16766497291, 8006268615, 34449052800, 223378633329, 14016129999, 19926305632, 10628247899, 26242415796, 11181493750, 114422168609, 24800859640, 34895294088, 5381433872, 17338613096, 11452961984, 14394715334, 20867272020, 36973617235, 142433003505, 41373051167, 15422885020, 11270040447, 32295200000, 18220961259, 17969275816, 211536000000, 41233771565, 24465996443, 8277363031, 20426488713, 22785450609, 9410249279, 44866621303, 10828314389, 55994378108, 13620847614, 12945366350, 34822224800, 353062464971, 9267350000, 386613611000, 22182794875, 22589744920, 89618309338, 6180487499, 16078622033, 39449596000, 38612712234, 11827453706, 189855335601, 25471355847, 10570861198, 11037040988, 16111166935, 13862042842, 6034600480, 14615967194, 17271388000, 12469931896, 84475986228, 98102120000, 17123031000, 8670163500, 82909678852, 27967534829, 34351211637, 43556650000, 187102014193, 9992628990, 50910130358, 13428792315, 132101623787, 19393095636, 31534840262, 30189978000, 65827817742, 110107062300, 48679364276, 19633674337, 19897356456, 13459353253, 13756812736, 41819440000, 138721055226, 36403831015, 126985101434, 9726962131, 31633740000, 152241530340, 97535400000, 689978437468, 16626039679, 27840827434, 16420774443, 48576791974, 21698849265, 13177325251, 13191507318, 12844304115, 69661177770, 19749449484, 114805404842, 7776566371, 106776113744, 13377670080, 58782413951, 12940096785, 8030036023, 40543547441, 15375210915, 22908130223, 20003317128, 138652800000, 14438346000, 9496735699, 9356906461, 13784942453, 17377551986, 202302349740, 21433781860, 23253666810, 6527616000, 24152102921, 24138050331, 161413271020, 208505541949, 18457199721, 206318943299, 31062780000, 24421668509, 8792572352, 11051273948, 8351767268, 31953288000, 153580671803, 73315000000, 12466660892, 8397609889, 29043337549, 20839814845, 12326379902, 47136080000, 32258539942, 47996220000, 11478625926, 5330131216, 29983119693, 90708000000, 96282828902, 9877885146, 27418147452, 10131892523, 9858367494, 35950369241, 21500640000, 7047165475, 13216271700, 8753430477, 13233622689, 24123216432, 27247789759, 29803566306, 21590903863, 19572031314, 76548976000, 69750188843, 8001938397, 37730994828, 13612394896, 96529311126, 8617714345, 9499107736, 12767067883, 43497224128, 48139839531, 46585950000, 26163862235, 38059113300, 14113197720, 41697453163, 24496399600, 18493080922, 28893750000, 57509096756, 30445320778, 226713270000, 12396862128, 15241203731, 34983666316, 39816696539, 12810515320, 48181450881, 83226586345, 14247199374, 25810865035, 38903131815, 8459271203, 26957526800, 100262526470, 15254672353, 5366628950, 5856913571, 19363059152, 9050154422, 11116075286, 13300000127, 218834811333, 11256432318, 101513290382, 96436356833, 14654954091, 90940115897, 270038723213, 10692681720, 31797645904, 39312309113, 16964162228, 12778779911, 15594677147, 10754983829, 39369386348, 18865999082, 208092277044, 16064078572, 70862541911, 24760297793, 18890296993, 281463620775, 12177920000, 20780269334, 35488486675, 24802396470, 304680931618, 16433340688, 8951609207, 26070297960, 18225400525, 21559611927, 17064975551, 326148660000, 13390513478, 7938833340, 12915021000, 27003303098, 24454698119, 10670678640, 35991109776, 2917911331641, 2060368570722, 1130445039288, 3042427068578, 3003699935965, 2166771223663, 6621023169253, 431071458193, 549471644825, 453042743905, 586093777208, 22962325963241]}],                        {&quot;coloraxis&quot;: {&quot;cmid&quot;: -0.00023398907171855293, &quot;colorbar&quot;: {&quot;title&quot;: {&quot;text&quot;: &quot;COVID beta&quot;}}, &quot;colorscale&quot;: [[0.0, &quot;rgb(103,0,31)&quot;], [0.1, &quot;rgb(178,24,43)&quot;], [0.2, &quot;rgb(214,96,77)&quot;], [0.3, &quot;rgb(244,165,130)&quot;], [0.4, &quot;rgb(253,219,199)&quot;], [0.5, &quot;rgb(247,247,247)&quot;], [0.6, &quot;rgb(209,229,240)&quot;], [0.7, &quot;rgb(146,197,222)&quot;], [0.8, &quot;rgb(67,147,195)&quot;], [0.9, &quot;rgb(33,102,172)&quot;], [1.0, &quot;rgb(5,48,97)&quot;]]}, &quot;legend&quot;: {&quot;tracegroupgap&quot;: 0}, &quot;margin&quot;: {&quot;b&quot;: 0, &quot;l&quot;: 0, &quot;r&quot;: 0, &quot;t&quot;: 20}, &quot;template&quot;: {&quot;data&quot;: {&quot;bar&quot;: [{&quot;error_x&quot;: {&quot;color&quot;: &quot;#2a3f5f&quot;}, &quot;error_y&quot;: {&quot;color&quot;: &quot;#2a3f5f&quot;}, &quot;marker&quot;: {&quot;line&quot;: {&quot;color&quot;: &quot;#E5ECF6&quot;, &quot;width&quot;: 0.5}}, &quot;type&quot;: &quot;bar&quot;}], &quot;barpolar&quot;: [{&quot;marker&quot;: {&quot;line&quot;: {&quot;color&quot;: &quot;#E5ECF6&quot;, &quot;width&quot;: 0.5}}, &quot;type&quot;: &quot;barpolar&quot;}], &quot;carpet&quot;: [{&quot;aaxis&quot;: {&quot;endlinecolor&quot;: &quot;#2a3f5f&quot;, &quot;gridcolor&quot;: &quot;white&quot;, &quot;linecolor&quot;: &quot;white&quot;, &quot;minorgridcolor&quot;: &quot;white&quot;, &quot;startlinecolor&quot;: &quot;#2a3f5f&quot;}, &quot;baxis&quot;: {&quot;endlinecolor&quot;: &quot;#2a3f5f&quot;, &quot;gridcolor&quot;: &quot;white&quot;, &quot;linecolor&quot;: &quot;white&quot;, &quot;minorgridcolor&quot;: &quot;white&quot;, &quot;startlinecolor&quot;: &quot;#2a3f5f&quot;}, &quot;type&quot;: &quot;carpet&quot;}], &quot;choropleth&quot;: [{&quot;colorbar&quot;: {&quot;outlinewidth&quot;: 0, &quot;ticks&quot;: &quot;&quot;}, &quot;type&quot;: &quot;choropleth&quot;}], &quot;contour&quot;: [{&quot;colorbar&quot;: {&quot;outlinewidth&quot;: 0, &quot;ticks&quot;: &quot;&quot;}, &quot;colorscale&quot;: [[0.0, &quot;#0d0887&quot;], [0.1111111111111111, &quot;#46039f&quot;], [0.2222222222222222, &quot;#7201a8&quot;], [0.3333333333333333, &quot;#9c179e&quot;], [0.4444444444444444, &quot;#bd3786&quot;], [0.5555555555555556, &quot;#d8576b&quot;], [0.6666666666666666, &quot;#ed7953&quot;], [0.7777777777777778, &quot;#fb9f3a&quot;], [0.8888888888888888, &quot;#fdca26&quot;], [1.0, &quot;#f0f921&quot;]], &quot;type&quot;: &quot;contour&quot;}], &quot;contourcarpet&quot;: [{&quot;colorbar&quot;: {&quot;outlinewidth&quot;: 0, &quot;ticks&quot;: &quot;&quot;}, &quot;type&quot;: &quot;contourcarpet&quot;}], &quot;heatmap&quot;: [{&quot;colorbar&quot;: {&quot;outlinewidth&quot;: 0, &quot;ticks&quot;: &quot;&quot;}, &quot;colorscale&quot;: [[0.0, &quot;#0d0887&quot;], [0.1111111111111111, &quot;#46039f&quot;], [0.2222222222222222, &quot;#7201a8&quot;], [0.3333333333333333, &quot;#9c179e&quot;], [0.4444444444444444, &quot;#bd3786&quot;], [0.5555555555555556, &quot;#d8576b&quot;], [0.6666666666666666, &quot;#ed7953&quot;], [0.7777777777777778, &quot;#fb9f3a&quot;], [0.8888888888888888, &quot;#fdca26&quot;], [1.0, &quot;#f0f921&quot;]], &quot;type&quot;: &quot;heatmap&quot;}], &quot;heatmapgl&quot;: [{&quot;colorbar&quot;: {&quot;outlinewidth&quot;: 0, &quot;ticks&quot;: &quot;&quot;}, &quot;colorscale&quot;: [[0.0, &quot;#0d0887&quot;], [0.1111111111111111, &quot;#46039f&quot;], [0.2222222222222222, &quot;#7201a8&quot;], [0.3333333333333333, &quot;#9c179e&quot;], [0.4444444444444444, &quot;#bd3786&quot;], [0.5555555555555556, &quot;#d8576b&quot;], [0.6666666666666666, &quot;#ed7953&quot;], [0.7777777777777778, &quot;#fb9f3a&quot;], [0.8888888888888888, &quot;#fdca26&quot;], [1.0, &quot;#f0f921&quot;]], &quot;type&quot;: &quot;heatmapgl&quot;}], &quot;histogram&quot;: [{&quot;marker&quot;: {&quot;colorbar&quot;: {&quot;outlinewidth&quot;: 0, &quot;ticks&quot;: &quot;&quot;}}, &quot;type&quot;: &quot;histogram&quot;}], &quot;histogram2d&quot;: [{&quot;colorbar&quot;: {&quot;outlinewidth&quot;: 0, &quot;ticks&quot;: &quot;&quot;}, &quot;colorscale&quot;: [[0.0, &quot;#0d0887&quot;], [0.1111111111111111, &quot;#46039f&quot;], [0.2222222222222222, &quot;#7201a8&quot;], [0.3333333333333333, &quot;#9c179e&quot;], [0.4444444444444444, &quot;#bd3786&quot;], [0.5555555555555556, &quot;#d8576b&quot;], [0.6666666666666666, &quot;#ed7953&quot;], [0.7777777777777778, &quot;#fb9f3a&quot;], [0.8888888888888888, &quot;#fdca26&quot;], [1.0, &quot;#f0f921&quot;]], &quot;type&quot;: &quot;histogram2d&quot;}], &quot;histogram2dcontour&quot;: [{&quot;colorbar&quot;: {&quot;outlinewidth&quot;: 0, &quot;ticks&quot;: &quot;&quot;}, &quot;colorscale&quot;: [[0.0, &quot;#0d0887&quot;], [0.1111111111111111, &quot;#46039f&quot;], [0.2222222222222222, &quot;#7201a8&quot;], [0.3333333333333333, &quot;#9c179e&quot;], [0.4444444444444444, &quot;#bd3786&quot;], [0.5555555555555556, &quot;#d8576b&quot;], [0.6666666666666666, &quot;#ed7953&quot;], [0.7777777777777778, &quot;#fb9f3a&quot;], [0.8888888888888888, &quot;#fdca26&quot;], [1.0, &quot;#f0f921&quot;]], &quot;type&quot;: &quot;histogram2dcontour&quot;}], &quot;mesh3d&quot;: [{&quot;colorbar&quot;: {&quot;outlinewidth&quot;: 0, &quot;ticks&quot;: &quot;&quot;}, &quot;type&quot;: &quot;mesh3d&quot;}], &quot;parcoords&quot;: [{&quot;line&quot;: {&quot;colorbar&quot;: {&quot;outlinewidth&quot;: 0, &quot;ticks&quot;: &quot;&quot;}}, &quot;type&quot;: &quot;parcoords&quot;}], &quot;pie&quot;: [{&quot;automargin&quot;: true, &quot;type&quot;: &quot;pie&quot;}], &quot;scatter&quot;: [{&quot;marker&quot;: {&quot;colorbar&quot;: {&quot;outlinewidth&quot;: 0, &quot;ticks&quot;: &quot;&quot;}}, &quot;type&quot;: &quot;scatter&quot;}], &quot;scatter3d&quot;: [{&quot;line&quot;: {&quot;colorbar&quot;: {&quot;outlinewidth&quot;: 0, &quot;ticks&quot;: &quot;&quot;}}, &quot;marker&quot;: {&quot;colorbar&quot;: {&quot;outlinewidth&quot;: 0, &quot;ticks&quot;: &quot;&quot;}}, &quot;type&quot;: &quot;scatter3d&quot;}], &quot;scattercarpet&quot;: [{&quot;marker&quot;: {&quot;colorbar&quot;: {&quot;outlinewidth&quot;: 0, &quot;ticks&quot;: &quot;&quot;}}, &quot;type&quot;: &quot;scattercarpet&quot;}], &quot;scattergeo&quot;: [{&quot;marker&quot;: {&quot;colorbar&quot;: {&quot;outlinewidth&quot;: 0, &quot;ticks&quot;: &quot;&quot;}}, &quot;type&quot;: &quot;scattergeo&quot;}], &quot;scattergl&quot;: [{&quot;marker&quot;: {&quot;colorbar&quot;: {&quot;outlinewidth&quot;: 0, &quot;ticks&quot;: &quot;&quot;}}, &quot;type&quot;: &quot;scattergl&quot;}], &quot;scattermapbox&quot;: [{&quot;marker&quot;: {&quot;colorbar&quot;: {&quot;outlinewidth&quot;: 0, &quot;ticks&quot;: &quot;&quot;}}, &quot;type&quot;: &quot;scattermapbox&quot;}], &quot;scatterpolar&quot;: [{&quot;marker&quot;: {&quot;colorbar&quot;: {&quot;outlinewidth&quot;: 0, &quot;ticks&quot;: &quot;&quot;}}, &quot;type&quot;: &quot;scatterpolar&quot;}], &quot;scatterpolargl&quot;: [{&quot;marker&quot;: {&quot;colorbar&quot;: {&quot;outlinewidth&quot;: 0, &quot;ticks&quot;: &quot;&quot;}}, &quot;type&quot;: &quot;scatterpolargl&quot;}], &quot;scatterternary&quot;: [{&quot;marker&quot;: {&quot;colorbar&quot;: {&quot;outlinewidth&quot;: 0, &quot;ticks&quot;: &quot;&quot;}}, &quot;type&quot;: &quot;scatterternary&quot;}], &quot;surface&quot;: [{&quot;colorbar&quot;: {&quot;outlinewidth&quot;: 0, &quot;ticks&quot;: &quot;&quot;}, &quot;colorscale&quot;: [[0.0, &quot;#0d0887&quot;], [0.1111111111111111, &quot;#46039f&quot;], [0.2222222222222222, &quot;#7201a8&quot;], [0.3333333333333333, &quot;#9c179e&quot;], [0.4444444444444444, &quot;#bd3786&quot;], [0.5555555555555556, &quot;#d8576b&quot;], [0.6666666666666666, &quot;#ed7953&quot;], [0.7777777777777778, &quot;#fb9f3a&quot;], [0.8888888888888888, &quot;#fdca26&quot;], [1.0, &quot;#f0f921&quot;]], &quot;type&quot;: &quot;surface&quot;}], &quot;table&quot;: [{&quot;cells&quot;: {&quot;fill&quot;: {&quot;color&quot;: &quot;#EBF0F8&quot;}, &quot;line&quot;: {&quot;color&quot;: &quot;white&quot;}}, &quot;header&quot;: {&quot;fill&quot;: {&quot;color&quot;: &quot;#C8D4E3&quot;}, &quot;line&quot;: {&quot;color&quot;: &quot;white&quot;}}, &quot;type&quot;: &quot;table&quot;}]}, &quot;layout&quot;: {&quot;annotationdefaults&quot;: {&quot;arrowcolor&quot;: &quot;#2a3f5f&quot;, &quot;arrowhead&quot;: 0, &quot;arrowwidth&quot;: 1}, &quot;coloraxis&quot;: {&quot;colorbar&quot;: {&quot;outlinewidth&quot;: 0, &quot;ticks&quot;: &quot;&quot;}}, &quot;colorscale&quot;: {&quot;diverging&quot;: [[0, &quot;#8e0152&quot;], [0.1, &quot;#c51b7d&quot;], [0.2, &quot;#de77ae&quot;], [0.3, &quot;#f1b6da&quot;], [0.4, &quot;#fde0ef&quot;], [0.5, &quot;#f7f7f7&quot;], [0.6, &quot;#e6f5d0&quot;], [0.7, &quot;#b8e186&quot;], [0.8, &quot;#7fbc41&quot;], [0.9, &quot;#4d9221&quot;], [1, &quot;#276419&quot;]], &quot;sequential&quot;: [[0.0, &quot;#0d0887&quot;], [0.1111111111111111, &quot;#46039f&quot;], [0.2222222222222222, &quot;#7201a8&quot;], [0.3333333333333333, &quot;#9c179e&quot;], [0.4444444444444444, &quot;#bd3786&quot;], [0.5555555555555556, &quot;#d8576b&quot;], [0.6666666666666666, &quot;#ed7953&quot;], [0.7777777777777778, &quot;#fb9f3a&quot;], [0.8888888888888888, &quot;#fdca26&quot;], [1.0, &quot;#f0f921&quot;]], &quot;sequentialminus&quot;: [[0.0, &quot;#0d0887&quot;], [0.1111111111111111, &quot;#46039f&quot;], [0.2222222222222222, &quot;#7201a8&quot;], [0.3333333333333333, &quot;#9c179e&quot;], [0.4444444444444444, &quot;#bd3786&quot;], [0.5555555555555556, &quot;#d8576b&quot;], [0.6666666666666666, &quot;#ed7953&quot;], [0.7777777777777778, &quot;#fb9f3a&quot;], [0.8888888888888888, &quot;#fdca26&quot;], [1.0, &quot;#f0f921&quot;]]}, &quot;colorway&quot;: [&quot;#636efa&quot;, &quot;#EF553B&quot;, &quot;#00cc96&quot;, &quot;#ab63fa&quot;, &quot;#FFA15A&quot;, &quot;#19d3f3&quot;, &quot;#FF6692&quot;, &quot;#B6E880&quot;, &quot;#FF97FF&quot;, &quot;#FECB52&quot;], &quot;font&quot;: {&quot;color&quot;: &quot;#2a3f5f&quot;}, &quot;geo&quot;: {&quot;bgcolor&quot;: &quot;white&quot;, &quot;lakecolor&quot;: &quot;white&quot;, &quot;landcolor&quot;: &quot;#E5ECF6&quot;, &quot;showlakes&quot;: true, &quot;showland&quot;: true, &quot;subunitcolor&quot;: &quot;white&quot;}, &quot;hoverlabel&quot;: {&quot;align&quot;: &quot;left&quot;}, &quot;hovermode&quot;: &quot;closest&quot;, &quot;mapbox&quot;: {&quot;style&quot;: &quot;light&quot;}, &quot;paper_bgcolor&quot;: &quot;white&quot;, &quot;plot_bgcolor&quot;: &quot;#E5ECF6&quot;, &quot;polar&quot;: {&quot;angularaxis&quot;: {&quot;gridcolor&quot;: &quot;white&quot;, &quot;linecolor&quot;: &quot;white&quot;, &quot;ticks&quot;: &quot;&quot;}, &quot;bgcolor&quot;: &quot;#E5ECF6&quot;, &quot;radialaxis&quot;: {&quot;gridcolor&quot;: &quot;white&quot;, &quot;linecolor&quot;: &quot;white&quot;, &quot;ticks&quot;: &quot;&quot;}}, &quot;scene&quot;: {&quot;xaxis&quot;: {&quot;backgroundcolor&quot;: &quot;#E5ECF6&quot;, &quot;gridcolor&quot;: &quot;white&quot;, &quot;gridwidth&quot;: 2, &quot;linecolor&quot;: &quot;white&quot;, &quot;showbackground&quot;: true, &quot;ticks&quot;: &quot;&quot;, &quot;zerolinecolor&quot;: &quot;white&quot;}, &quot;yaxis&quot;: {&quot;backgroundcolor&quot;: &quot;#E5ECF6&quot;, &quot;gridcolor&quot;: &quot;white&quot;, &quot;gridwidth&quot;: 2, &quot;linecolor&quot;: &quot;white&quot;, &quot;showbackground&quot;: true, &quot;ticks&quot;: &quot;&quot;, &quot;zerolinecolor&quot;: &quot;white&quot;}, &quot;zaxis&quot;: {&quot;backgroundcolor&quot;: &quot;#E5ECF6&quot;, &quot;gridcolor&quot;: &quot;white&quot;, &quot;gridwidth&quot;: 2, &quot;linecolor&quot;: &quot;white&quot;, &quot;showbackground&quot;: true, &quot;ticks&quot;: &quot;&quot;, &quot;zerolinecolor&quot;: &quot;white&quot;}}, &quot;shapedefaults&quot;: {&quot;line&quot;: {&quot;color&quot;: &quot;#2a3f5f&quot;}}, &quot;ternary&quot;: {&quot;aaxis&quot;: {&quot;gridcolor&quot;: &quot;white&quot;, &quot;linecolor&quot;: &quot;white&quot;, &quot;ticks&quot;: &quot;&quot;}, &quot;baxis&quot;: {&quot;gridcolor&quot;: &quot;white&quot;, &quot;linecolor&quot;: &quot;white&quot;, &quot;ticks&quot;: &quot;&quot;}, &quot;bgcolor&quot;: &quot;#E5ECF6&quot;, &quot;caxis&quot;: {&quot;gridcolor&quot;: &quot;white&quot;, &quot;linecolor&quot;: &quot;white&quot;, &quot;ticks&quot;: &quot;&quot;}}, &quot;title&quot;: {&quot;x&quot;: 0.05}, &quot;xaxis&quot;: {&quot;automargin&quot;: true, &quot;gridcolor&quot;: &quot;white&quot;, &quot;linecolor&quot;: &quot;white&quot;, &quot;ticks&quot;: &quot;&quot;, &quot;title&quot;: {&quot;standoff&quot;: 15}, &quot;zerolinecolor&quot;: &quot;white&quot;, &quot;zerolinewidth&quot;: 2}, &quot;yaxis&quot;: {&quot;automargin&quot;: true, &quot;gridcolor&quot;: &quot;white&quot;, &quot;linecolor&quot;: &quot;white&quot;, &quot;ticks&quot;: &quot;&quot;, &quot;title&quot;: {&quot;standoff&quot;: 15}, &quot;zerolinecolor&quot;: &quot;white&quot;, &quot;zerolinewidth&quot;: 2}}}},                        {&quot;responsive&quot;: true}                    )                };                            &lt;/script&gt;        &lt;/div&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;If you would like to generate this plot for yourself, perhaps using a different basket of stocks, the Jupyter notebook is &lt;a href=&quot;https://github.com/robertmartin8/RandomWalks/blob/master/CovidBeta/CovidBeta.ipynb&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note: most of this post was written before November 9th 2020, the day on which Pfizer announced incredibly encouraging results regarding their vaccine.&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;Let’s go back in time to the start of 2020. Despite aggressive trade rhetoric, 2019 has been a great year for markets, with the S&amp;amp;P500 up about 30%. Thanks to low rates (and the Fed’s commitment to loose monetary policy), the “blip” of 2018Q4 is nothing more than a distant bad dream. Big tech is killing it; multiples are expanding; volatility is at a comfortable low.&lt;/p&gt;

&lt;p&gt;On January 4th, the World Health Organisation (WHO) &lt;a href=&quot;https://twitter.com/WHO/status/1213523866703814656?s=20&quot;&gt;tweets&lt;/a&gt; that there is a cluster of pneumonia cases in Wuhan, China. As far as the West is concerned, this is a non-event, happening way “over there” in the East. On January 13th, a case is recorded in Thailand. What follows is two months of health officials gathering and mulling over the evidence (on January 23rd an independent committee reports that there is insufficient evidence to make a decision), while the rest of the world continues on blissfully unaware of the worsening situation. The markets continue their steady rise, largely unperturbed.&lt;/p&gt;

&lt;p&gt;In February, the situation becomes hard to ignore; by February 13th, COVID-19 is present in 25 countries, with more than 60,000 cases (&lt;a href=&quot;https://www.thinkglobalhealth.org/article/updated-timeline-coronavirus&quot;&gt;source&lt;/a&gt;). On the 14th of February, market participants suddenly seem to appreciate the gravity of the situation; in one short month, the S&amp;amp;P500 loses 1/3 of its value. People who have been diligently following the prevailing personal finance advice and investing their money in the stock markets are suddenly faced with an unprecedentedly rapid loss of net worth. Speculators, who have been riding the rally with leverage, are caught with their trousers down.&lt;/p&gt;

&lt;p&gt;By March, there is full-on panic as the worldwide number of cases hits 100,000 and nations start to impose heavy travel restrictions.  What had started as whispers of a viral cough “far away in the East” has now blown up into the terrifying spectre of an omnipresent contagion – $R_0$ numbers suggest that before long, the majority of the world population will be infected by a virus whose severity is still unknown. Readers of mainstream news could be forgiven for thinking that it is the end of the world as we know it.&lt;/p&gt;

&lt;p&gt;Cut to 18th August – 6 months later. The S&amp;amp;P500 has just posted a new all-time high, having appreciated about 50% from the low of March 20th.&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/covid_beta/spy_covid.png&quot; style=&quot;width:80%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;I have written this dramatised account largely as a note-to-self. With the rose-tinted spectacles of hindsight, it is now clear that the widespread and indiscriminate risk-off in March created a fantastic opportunity for level-headed investors to pick up high-quality companies, well suited to a social-distanced society, at steep discounts. Many of us are now kicking ourselves for not having bought more, but we must remember that in the moment, the future was murky indeed.&lt;/p&gt;

&lt;p&gt;All this said, things certainly aren’t back to where they were. Only a handful of companies (mostly big tech) have been responsible for the majority of the market’s rebound. Whether you are optimistic or pessimistic about how the pandemic plays out from here, as long as you think COVID-19 is a key driver for stock markets it is important to know how different sectors or companies are affected by the virus. Optimists might be keen to identify which stocks have been hardest hit, to play the rebound, while those who think that the situation will stay unresolved for longer than consensus expects may want to remain overweight the companies that thrive in a pandemic environment.&lt;/p&gt;

&lt;p&gt;The goal of this investigation, therefore, is to develop a highly intuitive tool to allow the viewer to understand, at a glance, which companies respond &lt;em&gt;well&lt;/em&gt; and &lt;em&gt;badly&lt;/em&gt; to COVID-19 news.&lt;/p&gt;

&lt;h2 id=&quot;methodology&quot;&gt;Methodology&lt;/h2&gt;

&lt;p&gt;The standard approach is “armchair reasoning”: sitting down and logically thinking through what the impacts of COVID-19 have been / will be.
On April 2nd 2020, I made this brainstorming mindmap to reason about some of the second-order implications of the pandemic. Several of the companies I mentioned, which may seem obvious in hindsight, have done very well:&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/covid_beta/covid_brainstorming.png&quot; style=&quot;width:80%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;Alternatively, we might adopt the “data-driven” approach: let market performance tell you which stocks benefitted most and least from COVID-19. Naively, we could simply look at which stocks/sectors have performed best/worst this year. This is a reasonable starting point but contains a lot of noise because stocks move up and down for all sorts of reasons.&lt;/p&gt;

&lt;p&gt;A better methodology for understanding how some factor (in our case, COVID-19) affects stock prices is is to compute the &lt;strong&gt;beta&lt;/strong&gt; of the asset returns to the factor. Concretely, we examine the correlation between the daily change in stock prices and the daily increases in COVID-19 cases: if a stock tends to have negative returns whenever COVID cases rise, we may believe that there is some association between COVID and the stock. The nice thing about this approach is that we can remove the effects of as many other variables as we want by introducing them as additional regression variables.&lt;/p&gt;

&lt;p&gt;In this post, we will regress stock returns against both COVID-19 cases and the overall S&amp;amp;P500 index (the latter being what people typically call &lt;em&gt;the&lt;/em&gt; beta, though it is really just &lt;em&gt;a&lt;/em&gt; beta). This serves to identify the effect of COVID-19 on the stock &lt;em&gt;in excess&lt;/em&gt; of the overall market effects, which should give a more accurate picture of how COVID-19 is affecting a stock.&lt;/p&gt;

&lt;p&gt;I pulled COVID data from the New York Times’ &lt;a href=&quot;https://github.com/nytimes/covid-19-data&quot;&gt;GitHub repo&lt;/a&gt;, and as usual, I used Yahoo Finance (via the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;yfinance&lt;/code&gt; python library) for stock/index pricing data. The “heavy lifting” – regressing stock returns against SPY returns and the change in daily cases – was done by the linear regression class within &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;scikit-learn&lt;/code&gt;.&lt;/p&gt;

&lt;h2 id=&quot;analysis-and-visualisation&quot;&gt;Analysis and Visualisation&lt;/h2&gt;

&lt;p&gt;A higher COVID beta means that a stock’s returns were &lt;em&gt;positively&lt;/em&gt; correlated with the change in COVID cases, i.e. more COVID cases helped the stock. Perhaps unsurprisingly, among the stocks with the highest COVID betas were Netflix, Walmart, Hasbro, Intel, and Citrix (enterprise technology). Conversely, the stocks with the most negative COVID betas include hard-hit companies in the energy and consumer discretionary sectors. This is encouraging, as our methodology passes the basic sanity check. Tech companies like Netflix were clear winners from national lockdowns, while energy companies faced a massive demand shock as people no longer drove to work or travelled overseas.&lt;/p&gt;

&lt;p&gt;To summarise the betas in one clear diagram, I drew inspiration from the Bloomberg terminal heatmap, which gives an instant “pulse” of the market with respect to one key variable (with the size of a rectangle representing market caps):&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/covid_beta/CovidBeta.png&quot; style=&quot;width:100%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;(Note: we don’t see a large blue rectangle corresponding to Zoom because Zoom isn’t yet in the S&amp;amp;P500 index)&lt;/p&gt;

&lt;p&gt;At a first glance, this graphic seems to accurately describe much of our intuition regarding what COVID has benefitted/harmed. Tech is largely blue (benefitting), as are healthcare and consumer staples. Energy has been particularly hard hit, with financials having a tough time also. But the effect of COVID on other sectors may not be as obvious, and it is here where the visualisation becomes especially helpful.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note regarding the Pfizer news of November 9th 2020&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;On 9 November, while I was halfway through writing this post, Pfizer announced very promising results from their vaccine. I found the heatmap to be a very useful reference – the red companies/sectors posted stunning returns, while many of the blue companies (e.g. big tech) had a lacklustre day – and actually ended up using the heatmap to help identify some stocks to trade. Based on my qualitative judgment post-hoc, the betas on the heatmap do seem to properly reflect the economic link between COVID-19 and the companies.&lt;/p&gt;

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

&lt;p&gt;In this post, we have constructed a “quick and dirty” method of understanding and visualising the effects of COVID-19 on different companies. There are several important caveats which should be considered before you use the heatmap for anything important.&lt;/p&gt;

&lt;p&gt;Firstly, betas are essentially correlations, so we must be careful about using them to retroactively create narratives to explain &lt;em&gt;why&lt;/em&gt; certain stocks did well/badly with respect to COVID-19. One must exercise judgment in determining which betas represent broad economic impacts due to COVID-19, rather than idiosyncratic company effects. For example, I noticed that within the financials sector, Goldman Sachs stood out as having a positive COVID beta – I reasoned that this was &lt;em&gt;because&lt;/em&gt; GS doesn’t have significant commercial banking exposure, instead being focused on trading (which benefits from volatility) and investment banking. However, this narrative is somewhat contradicted by the fact that Morgan Stanley also lacks commercial banking exposure, yet had a negative COVID beta anyway.&lt;/p&gt;

&lt;p&gt;Secondly, this analysis treats beta as if it is a static parameter. In reality, to compute beta (or any other time-series property), one must decide on a rolling window for the calculation. In this post, we used all year-to-date stock price data, but the plot below shows how the 2-month rolling beta for the 10 highest/lowest beta stocks (averaged) varied over time.&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/covid_beta/rolling_beta.png&quot; style=&quot;width:80%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;Lastly, we have all likely heard the many complaints about the market diverging from reality, along with the standard response that “markets are forward-looking”. Our methodology is not at all forward-looking, as it is simply regressing returns against the daily change in cases. A more meaningful calculation would be to calculate the beta with respect to some estimate of future COVID cases – possibly the beta to $R_0$.&lt;/p&gt;

&lt;p&gt;These issues aside, I have personally found the heatmap to be a useful visualisation tool; at the very least, the plot is somewhat pleasant to look at and play around with.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Option-implied probability distributions, part 2</title>
   <link href="https://reasonabledeviations.com/2020/10/10/option-implied-pdfs-2/"/>
   <updated>2020-10-10T00:00:00+00:00</updated>
   <id>https://reasonabledeviations.com/2020/10/10/option-implied-pdfs-2</id>
   <content type="html">&lt;p&gt;In &lt;a href=&quot;/2020/10/01/option-implied-pdfs/&quot;&gt;Part 1&lt;/a&gt; of this series, we demonstrated that the prices of option butterfly spreads imply a probability distribution of prices for the underlying asset. In this post, we will first examine the limiting case of butterfly spreads. Then, we will tackle the industry-standard approach for constructing PDFs from option prices: interpolating in volatility space to generate a volatility surface, converting this into a continuous set of option prices, then applying the Breeden-Litzenberger formula to find the PDF.
&lt;!--more--&gt;&lt;/p&gt;

&lt;p&gt;This post will be slightly more technical than &lt;a href=&quot;/2020/10/01/option-implied-pdfs/&quot;&gt;Part 1&lt;/a&gt;, but as before, I endeavour to explain the motivation behind the maths.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The supporting code for this post can be found in this &lt;a href=&quot;https://github.com/robertmartin8/pValuation/blob/master/ProbabilisticValuation/OptionImpliedPDF.ipynb&quot;&gt;Jupyter notebook&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2 id=&quot;the-limiting-case-of-narrow-butterflies&quot;&gt;The limiting case of narrow butterflies&lt;/h2&gt;

&lt;p&gt;As mentioned in the conclusion to &lt;a href=&quot;/2020/10/01/option-implied-pdfs/&quot;&gt;Part 1&lt;/a&gt;, deriving the PDF from butterfly prices is not a perfectly accurate method, even assuming we had  perfect pricing data with negligible bid-ask spreads. This is because we made the approximation that the payoff of a butterfly is binary: either the maximum payoff $\Delta K$ if the expiration stock price is on the central strike, or zero otherwise. This payoff, of course, is not binary as it contains a sloped portion.&lt;/p&gt;

&lt;p&gt;However, the approximation gets better and better as we use narrower butterflies because the sloped portion becomes less significant. For example, to calculate the probability that the expiry price of the stock is \$340, it would be better to use the 339/340/341 butterfly (as we did in the previous post) than to use the 335/340/345 butterfly, even though both of them are valid butterflies centred on \$340. Unfortunately, in the real world, we are not able to construct arbitrarily narrow butterflies because there is a limit to the granularity of strike prices on exchanges (with a \$1 granularity being typical for retail brokerages).&lt;/p&gt;

&lt;p&gt;Let’s pretend we &lt;em&gt;could&lt;/em&gt; construct arbitrarily narrow spreads. We will now show that in this limit $\Delta K \to 0$, the butterfly-implied probability converges to the PDF. The argument is rather mathematical and can be skipped, but do take a moment to appreciate the result. I find this little fact to be extremely exciting because it’s a clear case in which an initial intuitive approach (inferring the probability from the prices of butterfly spreads) actually converges to the right answer.&lt;/p&gt;

&lt;p&gt;Recall that for a butterfly with price &lt;em&gt;X&lt;/em&gt; and maximum payoff $\Delta K$, the probability $p_K$ that the underlying is at the central strike &lt;em&gt;K&lt;/em&gt; at expiry (in which case the maximum payoff $\Delta K$ is received) can be found by an expected value calculation:&lt;/p&gt;

\[p \Delta K = X \implies p = \frac{X}{\Delta K}\]

&lt;p&gt;Probability density is not the same as probability; the general intuition is that probability is given by the area under a PDF – in fact, we can define the PDF as the function $f(x)$ such that:&lt;/p&gt;

\[P(A \leq x \leq B) = \int_A^B f(x)dx.\]

&lt;p&gt;With this in mind, the probability that the underlying price is within a small band $\Delta K$ of a given strike $K_0$ at expiry is:&lt;/p&gt;

\[P(K_0 \leq K \leq K_0 + \Delta K) \approx f(K_0)\Delta k.\]

&lt;center&gt;
&lt;img src=&quot;/assets/images/options/pdf_approx.png&quot; style=&quot;width:50%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;The approximation becomes an equality in the limit of small $\Delta K$. Hence, denoting $P(K_0 \leq K \leq K_0 + \Delta K)$ as $p_K$, we have:&lt;/p&gt;

\[p_K = \lim_{\Delta K \to 0} \left\{f(K)\Delta K \right\} \implies f(K) = \lim_{\Delta K \to 0} \frac{p_K}{\Delta K}.\]

&lt;p&gt;But we have an expression for $p_K$ in terms of the price, and can express the price of the butterfly in terms of the prices of the underlying call options (two short calls with strike $K$; long calls at strikes $K \pm \Delta K$):&lt;/p&gt;

\[\begin{aligned}
f(K) &amp;amp;= \lim_{\Delta K \to 0} \left\{\frac{X / \Delta K}{\Delta K} \right\} = \lim_{\Delta K \to 0} \left\{ \frac{X}{(\Delta K) ^2} \right\} \\
&amp;amp;= \lim_{\Delta K \to 0} \left\{ \frac{C(K + \Delta K) - C(K) - C(K) + C(K- \Delta K))}{(\Delta K) ^2} \right\}
\end{aligned}\]

&lt;p&gt;The astute reader will note that the RHS of this expression has a very familiar structure – it is the second (partial) derivative of the call price with respect to the strike price. The PDF can thus be expressed as:&lt;/p&gt;

\[f(K) = \frac{\partial^2 C(K)}{\partial K^2}\]

&lt;p&gt;This is an incredibly powerful result that relates the PDF directly to the prices of call options; furthermore, it shows that the limit of narrow butterfly spreads does indeed give the correct answer.&lt;/p&gt;

&lt;h2 id=&quot;an-alternative-derivation&quot;&gt;An alternative derivation&lt;/h2&gt;

&lt;p&gt;For those of you who are not intellectually satisfied by the intuitive derivation above, there is a slightly more rigorous argument (equally elegant, in my view).&lt;/p&gt;

&lt;p&gt;By definition of the PDF, $P(S_T &amp;gt; K) = \int_K^\infty f(x)dx$ (here we are using &lt;em&gt;x&lt;/em&gt; as a dummy variable for the strikes). As discussed in &lt;a href=&quot;/2020/10/01/option-implied-pdfs/&quot;&gt;Part 1&lt;/a&gt;, the fair value of a call option (with time $\tau$ until expiry) is the present value of the expected payoff:&lt;/p&gt;

\[\begin{aligned}
C(K, \tau) &amp;amp;= e^{-r\tau} \mathbb{E}(\max[S_T-K, 0]) \\
&amp;amp;= e^{-r\tau} \int_0^\infty \max[S_T-K, 0] f(S_T) dS_T \\
&amp;amp;= e^{-r\tau} \int_K^\infty (x-K)f(x)dx
\end{aligned}\]

&lt;p&gt;The goal is to &lt;em&gt;invert&lt;/em&gt; this to find $f(x)$ as a function of &lt;em&gt;C.&lt;/em&gt; This can be done by expanding the integral and taking partial derivatives with respect to strike.&lt;/p&gt;

\[\begin{aligned}
C(K, \tau) &amp;amp;= e^{-r\tau} \left[ \int_K^\infty xf(x)dx - K\int_K^\infty f(x)dx\right] \\
\implies \frac{\partial C}{\partial K} &amp;amp;= -e^{-r \tau} \int_K^\infty f(x)dx \\
&amp;amp;= e^{-r \tau} \left[ \int_{-\infty}^K f(x)dx -1 \right]
\end{aligned}\]

&lt;p&gt;We now observe that $\int_{-\infty}^K f(x)dx$ is the definition of the &lt;strong&gt;cumulative distribution function&lt;/strong&gt; $F(x)$, the derivative of which gives the PDF. Hence:&lt;/p&gt;

\[\frac{\partial^2 C}{\partial K^2} = e^{-r\tau} \frac{d}{dk} F(K) \implies f(K) = e^{r\tau} \frac{\partial^2 C}{\partial K^2}\]

&lt;p&gt;We have now derived the same result as before, generalised to include the time to expiry. The proper name for this result is the &lt;strong&gt;Breeden-Litzenberger formula&lt;/strong&gt; (derived in their &lt;a href=&quot;https://faculty.baruch.cuny.edu/lwu/890/BreedenLitzenberger78.pdf&quot;&gt;1978 paper&lt;/a&gt;), a very useful tool which can convert call option prices into an implied PDF.&lt;/p&gt;

&lt;h2 id=&quot;a-detour-into-volatility-space&quot;&gt;A detour into volatility space&lt;/h2&gt;

&lt;p&gt;Unfortunately, we cannot immediately apply the Breeden-Litzenberger formula to call prices for the same reason that butterfly spreads can’t be made arbitrarily narrow: strike prices come in discrete increments. To numerically calculate the second derivative of the call price function with respect to strike, we need our call prices to be near-continuous with respect to the strike, requiring interpolation (possibly some signal smoothing first).&lt;/p&gt;

&lt;p&gt;At this juncture, two roads diverge in a yellow wood. The obvious thing to do is to interpolate in price-space, i.e join the dots in the below plot:&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/options/call_prices.png&quot; style=&quot;width:80%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;This seems straightforward, but there is a catch. It is dangerously easy for interpolation to result in arbitrage opportunities (which are not realistic). For example, any straight-line interpolation, such as setting the price of the \$105-strike option to be exactly between the prices of the \$100- and \$110-strike options, leads to a free butterfly spread. In fact, with the Breeden-Litzenberger formula in mind, any interpolation &lt;em&gt;must&lt;/em&gt; always be convex (positive second derivative with respect to strike) since the PDF must be positive. If you are a market maker, mispricing the options to allow for arbitrage opportunities would be a cardinal sin, because you would make a guaranteed loss while your counterparty makes a risk-free profit.&lt;/p&gt;

&lt;p&gt;It is essential, therefore, to be cautious when interpolating. One reason for the instability of interpolation in “price space” is that the price can be thought of as the &lt;em&gt;dependent variable&lt;/em&gt; (i.e output) of an option pricing model which has several inputs:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;The price of the underlying and the strike (further OTM is less valuable)&lt;/li&gt;
  &lt;li&gt;Time to expiration (more time is more valuable)&lt;/li&gt;
  &lt;li&gt;Volatility (more volatile is more valuable)&lt;/li&gt;
  &lt;li&gt;Interest rates (higher rates means calls are more valuable)&lt;/li&gt;
  &lt;li&gt;Dividends (higher dividends means calls are less valuable)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;(The last two are much less important than the first three). The argument goes that it makes more sense to interpolate on the &lt;em&gt;inputs&lt;/em&gt; to this function versus the &lt;em&gt;output&lt;/em&gt; (price) because inputs should be “smoother” than outputs. The moneyness of the option and the time to expiration are pre-determined when entering a contract, so the only meaningful free input parameter which we might consider interpolating is the volatility.&lt;/p&gt;

&lt;h3 id=&quot;black-scholes-and-implied-volatility&quot;&gt;Black-Scholes and implied volatility&lt;/h3&gt;

&lt;p&gt;I can’t avoid bringing up Black-Scholes, but this is certainly not the place to give a detailed introduction to the theory.&lt;/p&gt;

&lt;p&gt;The Black-Scholes model &lt;em&gt;values&lt;/em&gt; an option – it does not &lt;em&gt;price&lt;/em&gt; an option any more than a discounted cash flow analysis &lt;em&gt;prices&lt;/em&gt; a stock – by making several assumptions: the volatility of the underlying is constant, the returns of the underlying are normally distributed, efficient markets, etc. As anyone who has witnessed Nassim Taleb’s Twitter vitriol will know, these assumptions are often far from the truth.&lt;/p&gt;

&lt;p&gt;Instead of proceeding with this definition, for the purposes of this post, let’s forget everything we know about Black-Scholes and redefine it as follows:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;There is a function called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Black-Scholes&lt;/code&gt; which converts &lt;strong&gt;implied volatilities (IVs)&lt;/strong&gt; – to be defined – into option prices.&lt;/li&gt;
  &lt;li&gt;There is a function called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Black-Scholes Inverse&lt;/code&gt; which converts option prices into IVs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That’s it. Nothing about pricing or valuing options. No assumptions about the distribution of asset returns. Just a mathematical &lt;strong&gt;identity&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The reason we can get away with this bold claim is that there is an embedded circularity. “Implied volatility” is &lt;em&gt;defined&lt;/em&gt; to be the volatility which results in the current option price when input to standard Black-Scholes. It might seem like we have just done some Lewis Carroll-esque wordplay, but for reasons we shall soon see, this circularity can be much more workable than the alternative of trying to define IV along the lines of the “expected future volatility”.&lt;/p&gt;

&lt;p&gt;In summary, price and IV can be interchanged by applying the appropriate function. This allows us to make the key insight: rather than interpolating in price space, we can convert the prices to IVs using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Black-Scholes Inverse&lt;/code&gt;, interpolate in IV space, then convert the interpolated IVs back to prices using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Black-Scholes&lt;/code&gt;.&lt;/p&gt;

&lt;h3 id=&quot;interpolating-in-iv-space&quot;&gt;Interpolating in IV space&lt;/h3&gt;

&lt;p&gt;Using the same SPY dataset as in &lt;a href=&quot;/2020/10/01/option-implied-pdfs/&quot;&gt;Part 1&lt;/a&gt; (collected when $S = \$332$ with about 3 weeks to expiry), the first step is to use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Black-Scholes Inverse&lt;/code&gt; to bring us from price space into IV space.&lt;/p&gt;

&lt;p&gt;There is no closed-form inverse to the Black-Scholes formula, so &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Black-Scholes Inverse&lt;/code&gt; is a numerical method – I chose to use Newton’s method, because it is easy to code up, but be aware that even the topic of computing IVs has been deeply researched. Excluding some pathological deep ITM/OTM contracts, the result of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Black-Scholes Inverse&lt;/code&gt; is shown below:&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/options/calls_to_iv.png&quot; style=&quot;width:50%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;There are some problems around the \$365-strike contracts, but after applying a Gaussian filter and clipping out additional deep ITM/OTM contracts, we have something workable:&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/options/iv_clean.png&quot; style=&quot;width:60%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;We can then apply the same cubic spline that we used with the butterfly probabilities to create a smooth function – a cross-section of the &lt;strong&gt;volatility surface&lt;/strong&gt;.&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/options/SPY_smile.png&quot; style=&quot;width:80%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;With a moment’s reflection, this plot is rather unintuitive. Rather than having a constant IV, we observe the characteristic &lt;strong&gt;volatility smile&lt;/strong&gt;. This does not make much sense if we think of IV as the “expected wiggliness” of returns in the underling between now and expiry – the future volatility should be independent of the strike – and is in clear contradiction to the Black-Scholes assumption that volatility is constant.&lt;/p&gt;

&lt;p&gt;Instead, using the “circular” definition that IV is the input volatility to Black-Scholes that results in the observed call price, we can rationalise the volatility smile as follows: buyers are willing to pay a premium (relative to the Black-Scholes “fair value”) for out-of-the-money options. A classic explanation for this is that asset returns typically have fat tails, meaning that OTM options are more valuable than Black-Scholes would suggest.&lt;/p&gt;

&lt;h3 id=&quot;constructing-the-pdf-of-underling-prices&quot;&gt;Constructing the PDF of underling prices&lt;/h3&gt;

&lt;p&gt;Having interpolated in volatility space, we can convert back to price space using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Black-Scholes&lt;/code&gt;, then take the second partial derivative of the call function with respect to strike (numerically) to find the PDF, as per the Breeden-Litzenberger formula:&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/options/volspace_pdf.png&quot; style=&quot;width:80%;&quot; /&gt;
&lt;/center&gt;

&lt;h2 id=&quot;comparing-pdfs&quot;&gt;Comparing PDFs&lt;/h2&gt;

&lt;p&gt;We can now compare the butterfly PDF that we generated in the previous post with the Breeden-Litzenberger PDF. Firstly, as seen above, the Breeden-Litzenberger method gives us a larger range of strikes (\$300-\$370 vs \$320-\$360 for butterfly PDFs), which makes sense because constructing each butterfly requires three price quotes.&lt;/p&gt;

&lt;p&gt;Secondly, even after restricting the range of strikes to be the same as for the butterfly PDF, note that the Breeden-Litzenberger PDF is better-behaved, lacking the kinks at $355 &amp;lt; K &amp;lt; 360$ (though as the previous plot shows, it does start to wobble as we move away from at-the-money):&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/options/butterfly_vs_breeden.png&quot; style=&quot;width:80%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;The last thing we ought to do is compare the two roads that diverged in the yellow wood: interpolation in IV space vs interpolation in price space – recall that these are two methods of generating a continuous call function, after which Breeden-Litzenberger can be applied.&lt;/p&gt;

&lt;p&gt;Using the same Gaussian filter and cubic spline, we can smooth and interpolate the call prices directly. The resulting PDFs is compared with the IV-space interpolation PDF below:&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/options/price_vol_comparison.png&quot; style=&quot;width:80%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;The IV-space approach seems to give much better results in the left tail, but slightly worse results in the right tail. Overall, there does not appear to be strong evidence that interpolating in IV-space is superior to interpolating in price-space, at least for this particular example, but I do think that interpolating in IV-space is logically more defensible.&lt;/p&gt;

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

&lt;p&gt;In conclusion, this pair of posts has given an introductory overview of how we might construct price PDFs by looking at option prices.&lt;/p&gt;

&lt;p&gt;There is much more that could be said about this subject. For example, we have completely ignored half of the available data – the prices of put options. A common technique is to use put-call parity to generate more data points for fair prices of call options, improving our estimate of the volatility surface. Furthermore, this post has taken a non-parametric approach, i.e we have not assumed that the volatility smile has any shape. In practice, parametric models are popular because they lead to smooth and analytically tractable surfaces.&lt;/p&gt;

&lt;p&gt;I realise that I have not spent a great deal of time explaining &lt;em&gt;why&lt;/em&gt; we might want the option-implied PDF. The obvious answer is that if you know the option-implied PDF, you can understand the market’s probabilistic expectations of the future and make trades when your view differs. What is not obvious is how one might generate probabilistic views in the first place. This is a topic that I will be revisiting in a subsequent post – this series was written solely to lay the groundwork for an upcoming piece, in which I am hoping to approach fundamental valuation from a probabilistic perspective.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Option-implied probability distributions, part 1</title>
   <link href="https://reasonabledeviations.com/2020/10/01/option-implied-pdfs/"/>
   <updated>2020-10-01T00:00:00+00:00</updated>
   <id>https://reasonabledeviations.com/2020/10/01/option-implied-pdfs</id>
   <content type="html">&lt;p&gt;The key idea in &lt;em&gt;Expectations Investing&lt;/em&gt;, a well-known book by Alfred Rappaport and Michael Mauboussin, is that to profitably invest in stocks one needs to find situations in which your view is variant to consensus expectations – a difficult task when you don’t know what consensus expectations are. There are several ways to figure out what the Street is thinking, the most common of which is to read sell-side research reports and get on the phone with analysts. However, earlier this year I wrote a &lt;a href=&quot;/2020/03/25/coronavirus-equity-expectations/&quot;&gt;post&lt;/a&gt; investigating how the current &lt;em&gt;price&lt;/em&gt; of an asset can be used to infer implied expectations of future performance. In Part 1 of a two-part series, we look to extend this concept by examining what the pricing of option butterfly spreads can tell us about the implied expectations of the &lt;em&gt;probability distribution&lt;/em&gt; of returns. &lt;a href=&quot;/2020/10/10/option-implied-pdfs-2/&quot;&gt;Part 2&lt;/a&gt; will consider a more advanced approach, closer to what is used by industry practitioners.&lt;/p&gt;

&lt;p&gt;In writing this post, I have had to make a difficult decision regarding the level of background knowledge to assume; there is no escaping the fact that the subject of derivatives is highly mathematical. Therefore, in each section, I aim to present the intuition first and the mathematics second so the reader may decide how much of the mathematics they follow.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The supporting code for this post can be found in this &lt;a href=&quot;https://github.com/robertmartin8/pValuation/blob/master/ProbabilisticValuation/OptionImpliedPDF.ipynb&quot;&gt;Jupyter notebook&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2 id=&quot;option-prices-are-related-to-the-distribution-of-underlying-prices&quot;&gt;Option prices are related to the distribution of underlying prices&lt;/h2&gt;

&lt;p&gt;A European call option gives you the right, but not the obligation, to buy an underlying asset (whose price at time &lt;em&gt;t&lt;/em&gt; is denoted by $S_t$) at a particular date in the future (the &lt;strong&gt;expiry date&lt;/strong&gt;, &lt;em&gt;T&lt;/em&gt;), at a particular price (the &lt;strong&gt;strike price&lt;/strong&gt;, &lt;em&gt;K&lt;/em&gt;). If stock XYZ is currently trading at \$100 a share, one might think that an option to purchase it at \$120 next month is worthless. If the option were expiring today, that would be true – you wouldn’t want to buy something for \$120 when the current price is \$100. The option expiring next month, however, is &lt;em&gt;not&lt;/em&gt; worthless because it’s possible that in the next month, the stock price could rise to \$130, in which case at expiry we could &lt;em&gt;exercise&lt;/em&gt; the option and buy the asset at \$120 before selling it at \$130 for a neat \$10 profit.&lt;/p&gt;

&lt;p&gt;We have established that this option is not worthless, which means that it has some value. Intuitively, its value is equal to the (present value of the) expected payoff at expiry. The payoff at expiry is the difference between the stock price and the strike price (\$130 – \$120 in the example above), or zero if the stock price is less than the strike price since you would not exercise the option. Hence, to value a call option &lt;em&gt;C&lt;/em&gt;, we might write:&lt;/p&gt;

\[C(K, \tau) = e^{-r\tau} \mathbb{E}(\max[S_T-K, 0]),\]

&lt;p&gt;where $\mathbb{E}$ is the expectation operator, $\tau \equiv T - t$ is the time period between now ($t$) and expiry ($T$) and &lt;em&gt;r&lt;/em&gt; is the risk-free rate. $S_T$, the price at expiration, is a random variable – if we knew the future stock price with certainty life would be easy. The goal of this post is to find the probability density function (PDF) that describes $S_T$, which we will denote as $f(S_T)$; loosely speaking, this would tell us the probability that the stock trades near a certain price on the expiration date. With this in mind, to construct an expression for the expected payoff, we essentially need to sum the “probability that the stock price is $S_T$ at expiry” multiplied by the “payoff if the stock price is $S_T$ at expiry” for all possible $S_T$. This is done by integration since prices are assumed to be continuous:&lt;/p&gt;

\[C(K, \tau) = e^{-r\tau} \int_0^\infty \max[S_T-K, 0] f(S_T) dS_T.\]

&lt;p&gt;We shall revisit this expression later on. For now, the key point is that there is a mathematical relationship between call option prices and the PDF of future returns.&lt;/p&gt;

&lt;h2 id=&quot;a-first-approximation-option-butterflies&quot;&gt;A first approximation: option butterflies&lt;/h2&gt;

&lt;p&gt;As we established previously, the probability that the underlying stock will close at a particular price next month is somehow baked into the prices of options expiring next month. Intuitively, the price of  calls should be positively related to the probability of an upside move – people would only want to buy an out-of-the-money (OTM) call, e.g. a call with strike \$120 when the underlying is trading at \$100, if there is some chance that their option will be in-the-money at expiration.&lt;/p&gt;

&lt;p&gt;However, we cannot simply say that the probability of a certain price move is proportional to the price of call options with that strike because the price of a call embeds the expected profit from all expiry prices above the strike. In other words, the fair price of the &lt;em&gt;K&lt;/em&gt;-strike option is related to the entire portion of the probability distribution above &lt;em&gt;K&lt;/em&gt;, as the integral above demonstrates.&lt;/p&gt;

&lt;p&gt;To proceed, we need to consider some combination of options that only profits if the underlying trades in a small range around a given strike at expiry. The price of such a structure would indeed be proportional to the probability of the associated price move. One such structure is the &lt;em&gt;long butterfly&lt;/em&gt;, which consists of two short calls at a given strike combined with a long call at a higher strike and a long call at a lower strike, “bracketing” the strike of the short calls. The payoff diagram explains this better than I can:&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/options/long_butterfly.png&quot; style=&quot;width:100%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;(Note that these payoff diagrams are shown on a &lt;em&gt;gross&lt;/em&gt; basis – they have not been shifted down to account for the price of the structure).&lt;/p&gt;

&lt;p&gt;The maximum payoff of a butterfly spread is equal to half the width of the spread (highest strike minus lowest strike). We shall denote the maximum payoff as $\Delta K$ – this payoff is realised when the stock price is equal to the central strike price at expiry.&lt;/p&gt;

&lt;p&gt;Now, an important question. What is the fair price &lt;em&gt;X&lt;/em&gt; of this butterfly spread?&lt;/p&gt;

&lt;p&gt;Consider a biased coin which pays $\Delta K$ if the coin shows heads, which happens with probability &lt;em&gt;p&lt;/em&gt;, and zero if the coin shows tails, which happens with probability $1-p$. The expected value of the coin toss is then $p\Delta K$. A rational individual who is neither risk-averse nor risk-seeking would pay exactly $p \Delta K$ to throw this coin.&lt;/p&gt;

&lt;p&gt;We can apply the same reasoning to the butterfly, making the approximation that the payoff of the structure is either the maximum payoff $\Delta K$ or zero (i.e ignoring the sloped portion of the payoff function). In this case, requiring that the price of the structure is equal to the expected value of its payoffs, there is a simple relation between the maximum payoff of the structure $\Delta K$, the probability &lt;em&gt;p&lt;/em&gt; that this maximum is received (equivalent to the probability that the price ends up at the central strike), and the price &lt;em&gt;X&lt;/em&gt; of the structure.&lt;/p&gt;

\[p \Delta K = X \implies p = \frac{X}{\Delta K}\]

&lt;p&gt;Let’s apply this to a real-life example. I downloaded some SPY option price data from my broker (about 3 weeks to expiration):&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/options/SPY_option_prices.png&quot; style=&quot;width:80%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;The probability that the SPY closes at \$340 on expiry (at the time of the snapshot, it was trading at \$332) will be approximated by considering the price of the 339/340/341 butterfly. For this structure, we have $\Delta K = 1$ and&lt;/p&gt;

\[X = 1.670 - 2 \times 1.365 + 1.100 = 0.04,\]

&lt;p&gt;meaning that it would cost 4¢ to open this position (assuming we could fill at mid-market). Based on the formula derived above, the probability that the SPY closes at \$340 on expiry is $p = 0.04/1 = 4\%$.&lt;/p&gt;

&lt;p&gt;In principle, we can now repeat this process for every possible butterfly to build out the entire distribution.&lt;/p&gt;

&lt;h2 id=&quot;a-kaleidoscope-of-butterflies&quot;&gt;A kaleidoscope of butterflies&lt;/h2&gt;

&lt;p&gt;Unfortunately, there are several added complexities when working with real data. Let’s repeat the above calculation to find the probability that the SPY closes at \$349. The price of the 348/349/350 butterfly is&lt;/p&gt;

\[X = 0.205 - 2 \times 0.165 + 0.125 = 0.\]

&lt;p&gt;Something is wrong here because we are receiving potential profits (with zero downside) for free! In an arbitrage-free market, this should not exist. Furthermore, the fact that the price of the butterfly is zero implies that there is no chance that the stock will trade at \$349 on expiry, which does not seem plausible, particularly because the same calculation tells us there is a 2% chance that the expiry price of the stock will be \$350.&lt;/p&gt;

&lt;p&gt;This problem arises because of the illiquidity of some of the options contracts. There are far more people trading the \$350-strike option (because it’s a nice round number) than there are for the \$349-strike. Hence, for the latter, we cannot be certain that the price we see on the screen is reflective of the true price at which an order would be executed.&lt;/p&gt;

&lt;p&gt;The plot below shows the butterfly-implied probabilities vary with the central strike price. Notice how noisy the data seems to be; there are even &lt;em&gt;negative&lt;/em&gt; probabilities!&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/options/SPY_raw_bfly_prob.png&quot; style=&quot;width:80%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;We need to clean this data up somehow. One approach would be to filter out the options with low volume, but that might result in us losing a lot of valuable data. Instead, I have decided to smooth the signal using a Gaussian filter. This works by convolving the noisy signal (in this case, butterfly-implied probabilities) with a Gaussian kernel – intuitively, each noisy data point is replaced with a weighted average of surrounding points (with weights determined by the Gaussian), resulting in a “blurring”. The result is clearly a major improvement:&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/options/SPY_smooth_bfly_prob.png&quot; style=&quot;width:80%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;We are not quite done yet, because all we have is a set of discrete points. We want a continuous PDF, so we need to interpolate between the points. To do so, we shall use a &lt;strong&gt;cubic spline&lt;/strong&gt;, which fits piecewise cubic functions to the discrete points. This results in a relatively smooth PDF (indeed, by changing a parameter, we can decide exactly how smooth we want it to be):&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/options/SPY_bfly_pdf.png&quot; style=&quot;width:80%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;We have now managed to meet the objective of this post. We are now able to answer questions like “what is the probability that the stock trades above \$350 at expiry” by integrating the PDF.&lt;/p&gt;

&lt;p&gt;The implied distribution of SPY prices has a characteristic log-normal shape, consistent with a bell-curve &lt;em&gt;return&lt;/em&gt; distribution. This does not have to be the case. There are certain situations in which the return distribution can be highly asymmetric. For example, on September 13th Gilead announced that they would acquire Immunomedics for \$88 per share in cash, resulting in IMMU stock jumping up to near \$88. Once this happens, the probability of a move to the upside is low (though it could happen if a bidding war begins), while a downside move is definitely on the table – acquisitions may fall through for a number of reasons, be it antitrust, shareholder disapproval, or force majeure.&lt;/p&gt;

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

&lt;p&gt;To sum up, the pricing of butterfly spreads can be a useful intuitive method for deriving the implied PDF of returns. That being said, the resulting distribution is not completely accurate, because it relies on the approximation that butterflies payoff in a binary manner. Furthermore, we have seen that dealing with real-life data requires several post-processing techniques to make the PDF “sensible”. In &lt;a href=&quot;/2020/10/10/option-implied-pdfs-2/&quot;&gt;Part 2&lt;/a&gt;, we will examine a more sophisticated industry-standard approach to finding the option-implied PDF. Stay tuned!&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Equity Investing Checklist</title>
   <link href="https://reasonabledeviations.com/2020/08/20/investing-checklist/"/>
   <updated>2020-08-20T00:00:00+00:00</updated>
   <id>https://reasonabledeviations.com/2020/08/20/investing-checklist</id>
   <content type="html">&lt;p&gt;This page represents a work-in-progress checklist for equity investing. It is continuously being refined based on my reading, as well as my actual experience in researching and investing. The hope is that this provides transparency and accountability (for myself), to better understand my cognitive biases and weak spots.&lt;/p&gt;

&lt;!--more--&gt;

&lt;p&gt;An important idea underlying this checklist is that time is finite, and there are thousands of public companies that we could analyse. Hence it’s ok to be trigger-happy with your vetos, and to stop researching a company if there’s a minor red flag. In the checklist below, I write “veto” whenever the point is a sufficient reason to stop researching the company.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;#investment-universe&quot;&gt;Investment universe&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#preliminary-research&quot;&gt;Preliminary research&lt;/a&gt;
    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#understanding-the-business&quot;&gt;Understanding the business&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#quick-look-at-financials&quot;&gt;Quick look at financials&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#meta-issues&quot;&gt;Meta issues&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#in-depth-research&quot;&gt;In-depth research&lt;/a&gt;
    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#resources&quot;&gt;Resources&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#key-questions&quot;&gt;Key questions&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#story&quot;&gt;Story&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#modelling-and-valuation&quot;&gt;Modelling and valuation&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#third-party-research&quot;&gt;Third-party research&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#pull-the-trigger&quot;&gt;Pull the trigger&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;investment-universe&quot;&gt;Investment universe&lt;/h2&gt;

&lt;p&gt;The first step is to narrow down the investment universe. This is a very vague process, and can be done in many different ways. The quality of these companies really doesn’t matter at this point; we are just trying to reduce our universe from ~5000 companies to ~100 companies. Some methods:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;My machine learning screen&lt;/li&gt;
  &lt;li&gt;Greenblatt/Lynch screeners&lt;/li&gt;
  &lt;li&gt;CapIQ screens&lt;/li&gt;
  &lt;li&gt;Random stock ideas&lt;/li&gt;
  &lt;li&gt;Sell-side aggregated lists&lt;/li&gt;
  &lt;li&gt;13F filings or insider buying&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;preliminary-research&quot;&gt;Preliminary research&lt;/h2&gt;

&lt;p&gt;In this section, we aim to understand whether a company is worth doing more research on. Throughout the process, we take brief notes of any questions we have about the company or any preliminary pros/cons, which will be revisited later if we continue researching the company.&lt;/p&gt;

&lt;h3 id=&quot;understanding-the-business&quot;&gt;Understanding the business&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;How does the company make money?
    &lt;ul&gt;
      &lt;li&gt;can just use Yahoo Finance, Atom Finance, or CapIQ.&lt;/li&gt;
      &lt;li&gt;company wikipedia page or website.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;What business is the company in?
    &lt;ul&gt;
      &lt;li&gt;Geography?&lt;/li&gt;
      &lt;li&gt;Do I understand the basics of the business?&lt;/li&gt;
      &lt;li&gt;What is the top-level trend in the business?&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Where does the company fit into my macro outlook? (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;veto&lt;/code&gt; if it clearly doesn’t)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;quick-look-at-financials&quot;&gt;Quick look at financials&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;What is the trend in the stock price?&lt;/li&gt;
  &lt;li&gt;Quick look at financial ratios, and how it compares with competitors
    &lt;ul&gt;
      &lt;li&gt;use Atom Finance or CapIQ for comps&lt;/li&gt;
      &lt;li&gt;note anything surprising (e.g. very low/high multiples)&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Quick look at the trends in revenue, margins, cash flows.&lt;/li&gt;
  &lt;li&gt;Quick look at the balance sheet. (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;veto&lt;/code&gt; if much too complicated for me to understand)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;meta-issues&quot;&gt;Meta issues&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;Do I find the company/sector vaguely interesting? (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;veto&lt;/code&gt; if I really have no interest in it, e.g. Oil and Gas, there is no point moving further)&lt;/li&gt;
  &lt;li&gt;What edge do I have trading this company?
    &lt;ul&gt;
      &lt;li&gt;is it small enough that major analysts don’t cover it? (a la Lynch)&lt;/li&gt;
      &lt;li&gt;do I have special sector knowledge?&lt;/li&gt;
      &lt;li&gt;not necessarily a veto factor because at this stage in my career there aren’t a huge number of companies I have an edge on.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;in-depth-research&quot;&gt;In-depth research&lt;/h2&gt;

&lt;p&gt;Once I have decided that it is worth investing the time to deeply research a company, it’s time to pull up the recent financial statements to try and properly understand the company.&lt;/p&gt;

&lt;h3 id=&quot;resources&quot;&gt;Resources&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;Read and make notes on the following sections from the 10K, with an emphasis on red/green flags and points that need to be revisited:
    &lt;ul&gt;
      &lt;li&gt;Introduction and company overview (we should have a rough idea of what will be in here)&lt;/li&gt;
      &lt;li&gt;Skim through the risk factors to see if there are any that aren’t boilerplate&lt;/li&gt;
      &lt;li&gt;Management Discussion &amp;amp; Analysis&lt;/li&gt;
      &lt;li&gt;Look over financial statements&lt;/li&gt;
      &lt;li&gt;Read the footnotes (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;veto&lt;/code&gt; if company has many accounting complexities, e.g. cross-holdings, weird leases, unusual capital structures; it is likely not worth the time given my current ability)&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Read through some recent earnings calls transcripts, in particular the Q&amp;amp;A section, to try and understand what the market is paying attention to.&lt;/li&gt;
  &lt;li&gt;Read through the proxy statement to understand the compensation model.&lt;/li&gt;
  &lt;li&gt;Read the latest news on the company and third party research
    &lt;ul&gt;
      &lt;li&gt;Very important to judge the third-party research on the thesis and not the name&lt;/li&gt;
      &lt;li&gt;Use the third-party research to inform data points rather than the overall story&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;key-questions&quot;&gt;Key questions&lt;/h3&gt;

&lt;p&gt;Some of the key questions to answer are as follows (based on Aswath Damodaran’s framework):&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;What are the key levers for the sector?&lt;/strong&gt; Do I have any deviant views on these?
    &lt;ul&gt;
      &lt;li&gt;How are supply and demand changing?&lt;/li&gt;
      &lt;li&gt;What are the tactical (short-term) and strategic (long-term) considerations?&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;What are the key levers for this company?&lt;/strong&gt; Do I have any deviant views on these?
    &lt;ul&gt;
      &lt;li&gt;What non-GAAP metrics are people paying attention to?&lt;/li&gt;
      &lt;li&gt;What questions are people asking in the Q&amp;amp;A section of the earnings calls?&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;How will the company’s investments drive sales growth or operating margin improvement? What is the cost of this growth in terms of required reinvestment?&lt;/li&gt;
  &lt;li&gt;What are the major risks to the company that may inhibit its sales growth or operating margin improvement?&lt;/li&gt;
  &lt;li&gt;What is management’s strategy, and how successful have they been at executing on their goal? How has the company evolved over time?&lt;/li&gt;
  &lt;li&gt;Are management incentives aligned? What is their compensation model? Have they been buying stock?&lt;/li&gt;
  &lt;li&gt;What non-operating assets does the company have?&lt;/li&gt;
  &lt;li&gt;Will the company remain a going concern? (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;veto&lt;/code&gt; if I am not confident that the company will remain solvent)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Regarding strategy and competition:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Revisit comps in more detail to understand competitive landscape&lt;/li&gt;
  &lt;li&gt;SWOT, Porter’s five forces.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;story&quot;&gt;Story&lt;/h3&gt;

&lt;p&gt;Having answered the questions above, it is time to summarise all of the information thus far into a story, that links the facts together. It is advisable to amass all the facts &lt;em&gt;then&lt;/em&gt; try to come up with a consistent story, rather than building a story on the go (prone to confirmation bias).&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;veto&lt;/code&gt; if I can’t come up with a simple story that explains the facts, or if the story is not attractive.&lt;/p&gt;

&lt;h2 id=&quot;modelling-and-valuation&quot;&gt;Modelling and valuation&lt;/h2&gt;

&lt;p&gt;Valuation is quite a deep area, so the depth you go depends on how much time you are willing to dedicate to it. The bare minimum is do a relative valuation.&lt;/p&gt;

&lt;h3 id=&quot;relative-valuation&quot;&gt;Relative valuation&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;Using CapIQ, Bloomberg, or a similar tool, construct a list of comparable companies. There is an important tradeoff between having good quality (i.e very similar) comps and having enough of a sample for the mean/median to be meaningful.
    &lt;ul&gt;
      &lt;li&gt;important to keep an eye on variables that are not captured by the valuation multiples, for example, leverage and cash flow&lt;/li&gt;
      &lt;li&gt;if you notice any outliers among the compset, investigate why, but consider removing them.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;We can then compute the average value for each metric (e.g. EV/EBITDA, P/E) across multiple values.&lt;/li&gt;
  &lt;li&gt;If our company has multiples much lower than the median, it is important to critically think about why this is the case. Some common reasons for a low P/E are: lower growth than comps, more levered, different risk exposures (geography).&lt;/li&gt;
  &lt;li&gt;If there is a very good reason that the company is trading at a big discount to comps, and hence you don’t feel the comps are a fair sample, then you can just use multiples from the company’s own history along with your own forecasts of the denominators to estimate the value.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;dcf-valuation&quot;&gt;DCF valuation&lt;/h3&gt;

&lt;p&gt;We should already have most of the inputs ready to go, based on the prior section. The key job here is to tie the numbers to the story and to come up with some idea of the intrinsic value of the company.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Use one of Damodaran’s many &lt;a href=&quot;http://pages.stern.nyu.edu/~adamodar/&quot;&gt;spreadsheets&lt;/a&gt;. In most cases, we should use his &lt;a href=&quot;http://www.stern.nyu.edu/~adamodar/pc/model.xls&quot;&gt;model selector&lt;/a&gt; and the relevant Ginzu spreadsheet.&lt;/li&gt;
  &lt;li&gt;Conduct a sensitivity analysis&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If we are having trouble with the inputs, we may need to do some work regarding market-implied variables, or inferring DCF inputs from comparable companies. Useful questions to consider:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;How do my assumptions differ from consensus?&lt;/li&gt;
  &lt;li&gt;Is the company trading at an &lt;em&gt;unjust&lt;/em&gt; discount to peers?&lt;/li&gt;
  &lt;li&gt;What is the market-implied forecasting period (cf Expectations Investing)? Do I think this period of cash flow growth is sustainable?&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;margin-of-safety-and-catalysts&quot;&gt;Margin of safety and catalysts&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;Using either of the above methods (or a combination of the two), we should have a range of valuations. A useful valuation tool is the &lt;strong&gt;football field&lt;/strong&gt; chart, which plots the valuation range from a number of sources, and compares it to the current share price.&lt;/li&gt;
  &lt;li&gt;It is a positive sign if there is a high degree of agreement between the different signals – if not, we should analyse why the market is treating one metric very differently.&lt;/li&gt;
  &lt;li&gt;It is a very positive sign if all of the metrics are significantly above the current share price – this is a &lt;strong&gt;margin of safety&lt;/strong&gt;, and serves to give us some leeway to account for &lt;strong&gt;model risk&lt;/strong&gt;. Nevertheless, it is important to be realistic and really think about what we know that the market doesn’t.&lt;/li&gt;
  &lt;li&gt;Lastly, it is critical to think about &lt;strong&gt;catalysts&lt;/strong&gt;. If we are arguing that a stock is completely undervalued, what will make the market re-value it?
    &lt;ul&gt;
      &lt;li&gt;the most common catalyst is probably an earnings report, since many people pay attention to these, but for small-cap stocks even these might not be enough to cause a significant re-valuation.&lt;/li&gt;
      &lt;li&gt;when dealing with shorts, regardless of how good your valuation is, a catalyst is a necessity. Many legendary investors (Tiger Cubs) got killed in the dot-com bubble because they were shorting incredibly overvalued companies that just went on to become even more overvalued. “The market can stay irrational longer than you can stay solvent”.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;pull-the-trigger&quot;&gt;Pull the trigger&lt;/h2&gt;

&lt;p&gt;At this point, if we decide that a company is worth investing in, we prepare a dated memo according to the following template:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Brief introduction to the company (industry, current share price, current multiples)&lt;/li&gt;
  &lt;li&gt;Investment thesis and key levers.&lt;/li&gt;
  &lt;li&gt;How my view of the levers differs from consensus&lt;/li&gt;
  &lt;li&gt;Risk factors (identify risks using a &lt;a href=&quot;https://en.wikipedia.org/wiki/Pre-mortem&quot;&gt;pre-mortem analysis&lt;/a&gt;)&lt;/li&gt;
  &lt;li&gt;How the company fits into my portfolio (in terms of diversification / sector risk)&lt;/li&gt;
  &lt;li&gt;Price target and stop loss.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is really for accountability and self-improvement, so that 6 months from now I can understand whether I was:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Right for the right reasons (yay!)&lt;/li&gt;
  &lt;li&gt;Right for the wrong reasons, i.e lucky.&lt;/li&gt;
  &lt;li&gt;Wrong but on the right lines, e.g. identified the key lever but called it the wrong way.&lt;/li&gt;
  &lt;li&gt;Completely wrong, or black-swanned.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;after-the-trade&quot;&gt;After the trade&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;For posterity, it is good to note down the date and price of your fill (although this is tracked by your broker).&lt;/li&gt;
  &lt;li&gt;It is inevitable that we will continue to learn new things about the stock once we own it – many investors say that this is the best time to learn about your company because you now have skin in the game. It is very possible that you may come across new information that invalidates your thesis, or affects your entry/exit conditions. This is fine, as long as you note it down very clearly. Hence 6 months from now, you will be able to retrospectively diagnose whether you were constructing a narrative to justify your beliefs, or the new information truly did affect your thesis.&lt;/li&gt;
  &lt;li&gt;When we eventually close the position, again, we should write down the date and the fill. This is important so that in future we can analyse what happened to all the stocks that we sold – did we sell too early?&lt;/li&gt;
&lt;/ul&gt;

</content>
 </entry>
 
 <entry>
   <title>A critical look at Greenblatt's Magic Formula</title>
   <link href="https://reasonabledeviations.com/2020/06/08/greenblatt-magic-formula/"/>
   <updated>2020-06-08T00:00:00+00:00</updated>
   <id>https://reasonabledeviations.com/2020/06/08/greenblatt-magic-formula</id>
   <content type="html">&lt;p&gt;As the saying goes, when something sounds too good to be true, it probably is – all the more so when it comes to investing. In this short post, we look at the Magic Formula of Joel Greenblatt, as described in &lt;em&gt;The Little Book That Still Beats the Market&lt;/em&gt;, critically examining the strategy and attempting to quantify its alpha.&lt;/p&gt;

&lt;!--more--&gt;
&lt;p&gt;What is unusual about this Magic Formula is that the person behind it is highly respectable. Joel Greenblatt is a value investing expert with an impressive performance track record; Gotham Capital’s persistent outperformance of the S&amp;amp;P500 speaks for itself. His book &lt;em&gt;You Can Be a Stock Market Genius&lt;/em&gt;, despite the awful title, reignited my interest in discretionary stock picking and the chapter on spinoffs has led to some profitable picks. Consequently, this post is not seeking to debunk the Magic Formula, but rather qualify the claims made both by Greenblatt and the many investors who have since adopted it.&lt;/p&gt;

&lt;p&gt;The supporting code is &lt;a href=&quot;https://github.com/robertmartin8/RandomWalks/tree/master/GreenblattMagicFormula&quot;&gt;here&lt;/a&gt;, but it may not be much use on its own as it is intended to be run within the Quantopian research/algorithm environment.&lt;/p&gt;

&lt;h2 id=&quot;how-the-magic-formula-works&quot;&gt;How the Magic Formula works&lt;/h2&gt;

&lt;p&gt;The “Magic Formula” is a simple procedure to find good companies to invest in. It encompasses the philosophy that the ideal company needs to be high quality, and it needs to be cheap. That’s all there is to it.&lt;/p&gt;

&lt;p&gt;There are many different ways these factors can be quantified, leading to several variations of the Magic Formula. Nevertheless, we will look at the canonical approach discussed in &lt;em&gt;The Little Book that Still Beats the Markets&lt;/em&gt;. For the quality factor, Greenblatt uses the &lt;strong&gt;Return on Capital (ROC)&lt;/strong&gt;, which he calculates as the operating earnings (EBIT) divided by the total value of the fixed assets (property, plant, and equipment) and net working capital. By defining capital this way, instead of the more conventional sum of debt and equity, Greenblatt isolates the assets that are used to generate operating income. Hence, the ROC quantifies how efficient the company is at using its capital to generating earnings.&lt;/p&gt;

&lt;p&gt;Cheapness is more straightforward. Greenblatt uses the ratio of operating earnings to the enterprise value (EBIT/EV) but you could instead use the &lt;strong&gt;earnings yield&lt;/strong&gt; (inverse of the P/E ratio) or free cash flow yield. We should formulate the cheapness metric formulated as a yield, such that a higher quantity is better –  this is useful when we are combining the cheapness rank with the ROC rank since both rankings will be in the same direction.&lt;/p&gt;

&lt;p&gt;With this in mind, the overall procedure as suggested in &lt;em&gt;The Little Book&lt;/em&gt; is as follows:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Construct the universe of stocks: we require market capitalisations of more than \$100m and remove stocks in the Financials or Utilities sectors (since they have slightly different accounting).&lt;/li&gt;
  &lt;li&gt;Calculate the ROC and the EBIT/EV&lt;/li&gt;
  &lt;li&gt;Rank each company in the stock universe by ROC&lt;/li&gt;
  &lt;li&gt;Rank each company in the stock universe by EBIT/EV&lt;/li&gt;
  &lt;li&gt;Add the two ranks to get a combined score&lt;/li&gt;
  &lt;li&gt;Each month, buy 2-3 positions from the top 20-30 companies&lt;/li&gt;
  &lt;li&gt;Hold each position for one year (timing the sales/purchases for tax benefits)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;em&gt;The Little Book&lt;/em&gt; has a table showing the annual performance of the Magic Formula approach: from 1988 to 2004, Greenblatt claims that the Magic Formula returned an annualised 33% (compared to 14% for the S&amp;amp;P500). This is a phenomenal return – compare it with the scatterplot below showing the performance and track record of many famous investors:&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/greenblatt/investor_scatter.png&quot; style=&quot;width:80%;&quot; /&gt;
&lt;figcaption&gt;Graph by Frederik Vanhaverbeke&lt;/figcaption&gt;
&lt;/center&gt;

&lt;p&gt;I certainly don’t suggest that Greenblatt has fabricated the results but it is difficult to believe that his simple procedure has consistently outperformed all these other superstar investors by such a large margin. In fairness, we do not have access to any other information about the strategy; those returns might be realistic (but less impressive) if they were achieved by taking twice as much risk.&lt;/p&gt;

&lt;h2 id=&quot;preliminary-analysis-of-the-ranking&quot;&gt;Preliminary analysis of the ranking&lt;/h2&gt;

&lt;p&gt;Before running a full backtest, it is useful to start by analysing the information content of whatever signal we aim to capture. In the case of the Magic Formula, the signal is the combined factor score. Quantopian’s research environment (along with the excellent Alphalens library) is the ideal tool for the job – it’s incredibly well-suited to cross-sectional equity factors like this and is easy to pick up for someone with a bit of python experience. Below is a brief snippet of the core ranking logic:&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;c1&quot;&gt;# Filter sector and volume
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sector&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;RBICSFocus&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;l1_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;latest&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;min_mcap&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;100e6&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;sector_mask&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;sector&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Finance&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sector&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Utilities&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;mask&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;Fundamentals&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mkt_val_public&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;latest&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;min_mcap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sector_mask&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Quality
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ebit&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Fundamentals&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ebit_oper_ltm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;latest&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;ev&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Fundamentals&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;entrpr_val_qf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;latest&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;earnings_yield&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ebit&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ev&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;ey_rank&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;earnings_yield&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rank&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mask&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mask&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Cheapnesses
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;net_fixed_assets&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Fundamentals&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ppe_net&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;latest&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;working_capital&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Fundamentals&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;wkcap_qf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;latest&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;roc&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ebit&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;net_fixed_assets&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;working_capital&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;roc_rank&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;roc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rank&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mask&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mask&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Compute the score
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;combined_score&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ey_rank&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ey_rank&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;max&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;roc_rank&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;roc_rank&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;max&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;We can then feed this into a boilerplate function which computes the forward returns for each stock and creates a report on the predictive power of the signal. The fastest way to get an initial indication of the signal’s potential is to look at the quantile plot, which shows how stocks with the high ranking scores perform compared to stocks with lower ranking scores.&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/greenblatt/quantile_plot.png&quot; style=&quot;width:100%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;These quantiles are quite good; we see an overall monotonic increase, suggesting that high ranking scores are indeed associated with higher returns.&lt;/p&gt;

&lt;p&gt;After the quantile plot, the next thing to look at is the &lt;strong&gt;information coefficient (IC)&lt;/strong&gt;, also known as the Spearman’s rank correlation coefficient, which measures the degree to which the ranking score is correlated with future returns. With a one-year time horizon, the Magic Formula score had a decent mean IC of 0.08. But rather than just looking at a point estimate of the value, it should be remembered that the predictive value of a signal may not remain constant with time. This is demonstrated in the plot below:&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/greenblatt/rolling_ic.png&quot; style=&quot;width:100%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;The Magic Formula score’s predictive power over a time period from 2004-2015 varied quite a lot, even becoming negative in 2009-2010. However, especially in more recent years, the Magic Formula has a robust IC.&lt;/p&gt;

&lt;p&gt;As a brief interjection, you may wonder why we are only testing using data up until 2015. The reason is that in quantitative research it is vital to leave a few years “untouched” to act as the final validation step before you deploy a model live.&lt;/p&gt;

&lt;p&gt;Overall, the results of this preliminary analysis are rather encouraging. With good quantiles and a reasonably high IC, the Magic Formula ranking does seem to have some predictive value. However, we shouldn’t get too excited; not all predictive signals are monetisable since the signal may not have sufficient magnitude to be profitable after transaction costs. The only way to find out is to run a proper backtest.&lt;/p&gt;

&lt;h2 id=&quot;backtesting-the-magic-formula&quot;&gt;Backtesting the Magic Formula&lt;/h2&gt;

&lt;p&gt;For this backtest, we will follow Greenblatt’s procedure as closely as possible. Each month, we will pick the top two stocks according to the ranking (provided we don’t already own them) and allocate 1/24 of the total capital to each. After the first year, there will thus be 24 equally-weighted stocks in the portfolio. Each subsequent month will require us to liquidate the oldest two stocks to make room for two new entrants.&lt;/p&gt;

&lt;p&gt;The only part of the Magic Formula we are not capturing is the tax optimisation – selling losers before the end of the tax year and winners at the start of a new one. For a fair comparison, we won’t consider taxes on the benchmark portfolio either. Quantopian’s default backtest includes transaction costs in the form of a 5 basis point (0.05%) slippage incurred every time a trade is made. The algorithm will have 100% turnover each year by design so the slippage should not be a major factor but it is good to incorporate it anyway.&lt;/p&gt;

&lt;p&gt;Between July 2003 and December 2015, &lt;strong&gt;the Magic Formula strategy returned an annualised 11.4% (Sharpe ratio 0.60), versus 8.7% for the S&amp;amp;P500 (Sharpe ratio 0.54)&lt;/strong&gt;. This is clearly an outperformance of the benchmark (3% alpha) but by nowhere near as much as the &lt;em&gt;Little Book&lt;/em&gt; claims.&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/greenblatt/v0_equity.png&quot; style=&quot;width:100%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;We can gain a more nuanced insight by considering different subsets of the backtesting period. Prior to mid-2007, the strategy was performing very well, achieving an annualised return of about 26% compared to about 18% for the benchmark – this is in line with Greenblatt’s claims. These returns are not just a result of taking more risk – notice the steadily growing &lt;strong&gt;specific return&lt;/strong&gt; (the component of performance independent of the market’s movement, shown in red).&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/greenblatt/v0_pre2007.png&quot; style=&quot;width:100%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;However, 2007-2010 tells a different story. The strategy had a drawdown of 57% (compared to 55% for the SPY), and the &lt;em&gt;cumulative&lt;/em&gt; specific return went negative, meaning that despite the strong performance in the prior years, a “pure” (beta-hedged) version of the strategy would have lost money.&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/greenblatt/greenblatt_v0_07to10.png&quot; style=&quot;width:100%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;Post-2010, the strategy shared in the considerable market recovery, but its 12% annualised return was a slight underperformance of the benchmark’s 13%.&lt;/p&gt;

&lt;p&gt;Over the backtest period, the Magic Formula was almost always more volatile than the market (which is to be expected since it holds only 24 stocks) and additionally had steeper drawdowns.&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/greenblatt/rolling_vol.png&quot; style=&quot;width:100%;&quot; /&gt;
&lt;/center&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/greenblatt/underwater.png&quot; style=&quot;width:100%;&quot; /&gt;
&lt;/center&gt;

&lt;h2 id=&quot;improving-the-magic-formula&quot;&gt;Improving the Magic Formula&lt;/h2&gt;

&lt;p&gt;Overall, the Magic Formula did indeed outperform the S&amp;amp;P500 between 2004 and 2015 but not by a large margin. For those of you who may be interested in building on top of the Magic Formula for your own investing, we now discuss some potential areas for improvement.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Composite factors&lt;/strong&gt;: rather than using a single quantity to measure cheapness or quality, it might be better to aggregate different metrics, for example, earnings yield or FCF yield for cheapness. Jim O’Shaughnessy’s research in &lt;em&gt;What Works on Wall Street&lt;/em&gt; suggests that a composite value factor is much more robust than single factors so we might have similar luck with quality and cheapness. As for the exact aggregation procedure, we could simply take the mean ranking across multiple different factors to start with but there is a lot of room here for sophistication – alpha combination can be seen as a portfolio optimisation problem so there is a large box of tools out there.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Trend&lt;/strong&gt;: one danger with buying cheap assets is that there is nothing to stop them from getting cheaper. This could be mitigated using a momentum filter, only buying a highly ranked stock when we can see that it has positive momentum. Wesley Gray has done some interesting work in this area.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Long/short&lt;/strong&gt;: if we believe that the Magic Formula score can identify both outperforming and underperforming assets, we can use a long/short basket strategy to hedge out market movement. The performance of this backtest should give a better indication of the quality of the ranking methodology compared to a long-only version. To make this more advanced, we can also consider hedging out various style exposures.&lt;/p&gt;

&lt;p&gt;Before you boldly go forth testing these modifications, a word of warning. In the context of algorithmic trading, it is crucial to limit the number of backtests you run. What might seem like the “scientific” approach – running a backtest one at a time for each proposed modification to isolate its effects on performance – is poor practice because it can easily lead to overfitting and hence reduce the out-of-sample validity. To minimise this risk, a better approach is to sit down and carefully think about all the modifications you would like to make and ensure that you have a strong economic hypothesis behind each. For example, rather than carelessly adding a new fundamental factor (Quantopian has hundreds) you should argue why the factor should be predictive. If you later find out in a backtest that the signal is not predictive, or is predictive &lt;em&gt;in the other direction&lt;/em&gt;, you should probably bin it.&lt;/p&gt;

&lt;p&gt;I read an interesting discussion on the Quantopian forums where someone was trying to use the debt/equity ratio as a quality factor, with the hypothesis that a higher debt/equity ratio is associated with a lower quality company. The backtest looked great so it attracted a fair bit of attention but in a shocking twist it was revealed that there was a sign error in the code; in fact, a &lt;em&gt;high&lt;/em&gt; debt/equity ratio was predictive of &lt;em&gt;positive&lt;/em&gt; future returns. In this case, there is indeed a believable economic hypothesis to explain the observed effect – leverage amplifies returns (incidentally, this was a key point in Joel Greenblatt’s thesis on Host Marriott in &lt;em&gt;You Can Be a Stock Market Genius&lt;/em&gt;) – but generally, you should be very careful about creating &lt;em&gt;ex-post&lt;/em&gt; justifications for observed phenomena.&lt;/p&gt;

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

&lt;p&gt;Based on our backtest, the surprising conclusion is that the Magic Formula is indeed a simple procedure that beats the markets on a risk-adjusted basis, with an annual alpha of 3% between 2003 and 2015. However, there are a couple of major caveats.&lt;/p&gt;

&lt;p&gt;Firstly, the magnitude of outperformance is nowhere near what Greenblatt describes in &lt;em&gt;The Little Book&lt;/em&gt; (33% vs 15%). This is likely due to the shift towards systematic equity ETFs over the past two decades, which arbitrage away inefficiencies like this. Furthermore, I hate to repeat the statement that “past performance is not indicative of future returns” but underlying this hackneyed phrase is the deeper concept of regime change. The performance of the Magic Formula in the post-crisis bull market was nowhere near as good as the performance in the 2004-2007 period; it is very possible that the 2008 GFC and subsequent central bank action represented a fundamental sea change, reducing the efficacy of certain value-oriented strategies. Hence, allocating capital to the Magic Formula strategy is implicitly making a bet that over your investment horizon, the market will be in a regime that rewards cheap high-quality companies.&lt;/p&gt;

&lt;p&gt;Secondly, following the Magic Formula would still incur a significant &lt;strong&gt;psychological risk&lt;/strong&gt; – the tendency for investors to let their emotions result in bad decision-making, for example, selling at the bottom of a crash or buying at the top of a bubble. It is very easy to look at the high annual return on a backtest and wish that you had used the strategy sooner, but would you really have been able to sit through a 56% drawdown like in 2008 without pulling your money out? Sure, in this case the market also experienced a great drawdown, but how about situations in which the market is going up but the strategy is losing money – in 2012, the Magic Formula strategy lost 5% while the SPY appreciated by more than 10%.&lt;/p&gt;

&lt;p&gt;Although this post represents neither a confirmation nor dramatic refutation of the Magic Formula strategy, I hope that it at least provides a framework for investigating cross-sectional equity strategies and highlights some of the important pitfalls in backtesting.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>The Big Shorts: what is the smart money betting against?</title>
   <link href="https://reasonabledeviations.com/2020/05/27/fca-short-investigation/"/>
   <updated>2020-05-27T00:00:00+00:00</updated>
   <id>https://reasonabledeviations.com/2020/05/27/fca-short-investigation</id>
   <content type="html">&lt;p&gt;Hedge funds in the UK are legally obligated to disclose to the Financial Conduct Authority (FCA) whenever their net short position in a particular listed company reaches 0.5% of the issued equity capital. In this post, I investigate a publicly available dataset containing information about these large institutional short positions in UK equities and attempt to understand the value that this data contains.
&lt;!--more--&gt;&lt;/p&gt;

&lt;p&gt;This is a true data science project, in that the main difficulties I had were not so much related to the analysis (there is no machine learning involved), but rather the cleaning and preprocessing of data. Usually when I write posts like this, I explain the code as I go along, but today I’m experimenting with a new format aimed at readers who are more interested in the actual outcomes of the analysis rather than the process. To that end, I have put all of the code (with brief explanations) in an accompanying &lt;a href=&quot;https://github.com/robertmartin8/RandomWalks/blob/master/ShortSellingAnalysis/ShortSellingAnalysis.ipynb&quot;&gt;Jupyter notebook&lt;/a&gt;; this post will focus on the motivation and results of the investigation, rather than the methods.&lt;/p&gt;

&lt;h2 id=&quot;short-selling&quot;&gt;Short selling&lt;/h2&gt;

&lt;p&gt;A short sale is a relatively simple transaction in which you borrow shares of a company, then immediately sell them. If the share price drops, you can buy them back at a cheaper price, return them to the lender, and pocket the difference. Rather than buying low and selling high, shorting is equivalent to selling high and buying low. This simplicity notwithstanding, shorting is often associated with more experienced investors. One reason could be that short selling has a theoretically unlimited downside – if you shorted Tesla at \$300 a share and covered at \$800, you would be out of pocket by \$500, whereas if you bought at \$300 and it went to zero, you would only be out of pocket by the \$300 you put in.&lt;/p&gt;

&lt;p&gt;Short selling is often demonised in the media, construed to be one of the many evil things that hedge funds get up to at the cost of society. However, there are many good reasons why you may want to bet against a company; the famous short-seller Jim Chanos aggressively shorted Enron in the lead up to its collapse in 2001 (and more recently, Luckin Coffee prior to its accounting scandal). Furthermore, it is not possible for short selling in and of itself to cause a company’s equity value to collapse because of the uptick rule (a stock can only be shorted after it ticks up). I am of the belief that short selling is an important market mechanism which may help to counteract the “irrational exuberance” that often plagues markets; it is fundamentally no better or worse morally than buying a stock. In both cases, capital is allocated based on your views of future performance.&lt;/p&gt;

&lt;h2 id=&quot;the-significance-of-the-fca-dataset&quot;&gt;The significance of the FCA dataset&lt;/h2&gt;

&lt;p&gt;As stated in the introduction, the Financial Conduct Authority in the UK mandates that investors disclose short positions exceeding 0.5% of a given public company’s float. These disclosures make for a fascinating dataset because hedge funds tend to be notoriously secretive about their positions since disclosing positions (long or short) may allow competitors to cotton-on to the fund’s strategy and accelerate alpha decay. In the case of short selling, there is an added dimension: disclosure leaves a fund susceptible to targeted short squeezes, in which other market participants start to buy up shares, driving up the price and sometimes forcing short-sellers to cover their positions at a loss (further driving up the price).&lt;/p&gt;

&lt;p&gt;Nevertheless, because of the FCA, we can get a rare glimpse into the holdings of the smartest players in the industry. Furthermore, the FCA additionally requires that once companies are above the 0.5% mark, they must disclose any changes in their position of more than 0.1%. Hence we not only know whether an institution has a large position but also how they are modifying their position in response to external factors.&lt;/p&gt;

&lt;p&gt;The dataset, which updates every day, can be downloaded from the &lt;a href=&quot;https://www.fca.org.uk/markets/short selling/notification-and-disclosure-net-short-positions&quot;&gt;FCA website&lt;/a&gt;. This is what the top of the dataset looks like:&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/short_selling/short_dataset_overview.png&quot; style=&quot;width:100%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;With this in mind, the main hypothesis that this post will be investigating is as follows: companies with a lot of institutional short interest (i.e companies for which many smart players are shorting the stock) should be more likely to experience a decline in the share price.&lt;/p&gt;

&lt;h2 id=&quot;data-cleaning-and-exploration&quot;&gt;Data cleaning and exploration&lt;/h2&gt;

&lt;p&gt;One problem with the FCA dataset is that it gives the company names and International Securities Identification Number (ISIN) rather than symbols like AAPL or TSLA. ISINs are “better” in the sense that they uniquely identify securities (symbols are not unique across different exchanges), but the difficulty is that most of the free data providers only let you query historical data by symbol. I had to write a web scraper to convert the ISINs to tickers, which was a real pain, but it allowed me to download historical prices for most of the present securities.&lt;/p&gt;

&lt;p&gt;Having done the dirty work, I was able to start exploring various aspects of the data. I started by looking for the most active investment, which turned out to be Marshall Wace’s position in Sainsbury’s (at one point, the net short position was 2% of the float, more than £100 million). The plot below shows the net percentage short position (green) against the rescaled price (blue).&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/short_selling/sbry_short.png&quot; style=&quot;width:100%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;The flat regions for the short position are expected because below 0.5% Marshall Wace does not have to disclose anything. This plot is rather interesting because the big spikes in the short position do not seem to be associated with decreases in the stock price (a statement that will be refined later on).&lt;/p&gt;

&lt;p&gt;Next, I was interested to find out which investors were most active in short selling:&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/short_selling/active_investors.png&quot; style=&quot;width:60%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;To get a rough indicator for the most shorted stocks, I took the mean short position in a given stock across each investor, then summed these over the time frame. By this measure, the most-shorted stock was Ocado, the UK grocery delivery company.&lt;/p&gt;

&lt;p&gt;Next, to start exploring the hypothesis, I plotted the 1-day forward returns against the short position (naively, without grouping by investor or ticker). If the hypothesis were true, we would expect a general downward-sloping pattern (larger short position means lower returns).&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/short_selling/naive_scatter.png&quot; style=&quot;width:80%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;Eyeballing it, there does not seem to be any meaningful relationship. The Spearman rank correlation coefficient (also known as the &lt;strong&gt;information coefficient&lt;/strong&gt;) can be used to quantify this – I found that there were only statistically significant correlations across the longer time horizons. Perhaps more interestingly, the sign of the correlation was positive ($r \approx 0.015$). This means that short positions are correlated with future price appreciation, the opposite of what was hypothesised.&lt;/p&gt;

&lt;h2 id=&quot;intelligent-short-interest-as-a-trading-signal&quot;&gt;Intelligent short interest as a trading signal&lt;/h2&gt;

&lt;p&gt;The above analysis is very crude because it does not perform any grouping. To improve it, I aggregated the net short position in each stock across all investors for each given date, which I will henceforth refer to as the “intelligent short interest” since it is the short interest from large investors who we hypothesise to be knowledgeable.&lt;/p&gt;

&lt;p&gt;We can then use the wonderful &lt;a href=&quot;https://github.com/quantopian/alphalens&quot;&gt;Alphalens&lt;/a&gt; library, built by Quantopian, to properly evaluate the predictive capability of this feature. One particularly useful tool is the quantile analysis, which evaluates the performance of different feature quantiles (e.g. high/median/low values of the short interest). For example, if the intelligent short interest were indeed predictive of negative returns, we would expect the 20% of assets with the highest intelligent short interest to do much worse than the 20% of assets with the lowest intelligent short interest. Using 5 quantiles, we can look at the information coefficients (read: Spearman correlation coefficients) to understand the predictive value of the feature:&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/short_selling/information_analysis.png&quot; style=&quot;width:60%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;This tells us a slightly different story to the initial analysis. There are indeed statistically significant negative correlations between the intelligent short interest  and the future return, although the signal isn’t particularly strong. An especially damning plot is the mean forward return for each quantile:&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/short_selling/quantile_plot.png&quot; style=&quot;width:80%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;Colourful though the plot may be, it’s pretty terrible from a signal perspective. It is telling us that regardless of which quintile we are in (low vs high short interest), forward returns are positive and there is no clear pattern. By contrast, below I have attached a quantile plot for another trading signal I have been researching, which shows excellent discriminatory power between the extreme quantiles:&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/short_selling/quantile_good.png&quot; style=&quot;width:80%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;I wasn’t ready to give up just yet – sometimes, a bit of feature engineering can be used to bring life to a seemingly useless signal. Rather than looking at the absolute intelligent short interest, I thought it might be interesting to consider the &lt;em&gt;change&lt;/em&gt; in the intelligent short interest, with the hypothesis that an increase in the intelligent short interest is predictive of negative future returns. In fact, this signal was even worse than the absolute intelligent short interest:&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/short_selling/delta_quantile.png&quot; style=&quot;width:80%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;In summary, the results of this section are mixed at best. Based on the statistical significance of the information coefficient (even after applying a Bonferroni correction to account for multiple comparisons), we would conclude that the hypothesis is correct – formally, that there &lt;em&gt;is&lt;/em&gt; sufficient evidence at the 1% level to reject the null hypothesis that there is no correlation between the intelligent short interest and the future returns. However, statistical jargon aside, the predictive power is rather weak and the quantile plots are a clear indication that the intelligent short interest is not going to be a useful trading signal.&lt;/p&gt;

&lt;h2 id=&quot;the-smartest-smart-money&quot;&gt;The smartest smart money&lt;/h2&gt;

&lt;p&gt;This section is a slight detour from the hypothesis about the predictive power of the dataset. I noticed that in principle we had enough data to evaluate the performance of different investors, using the &lt;strong&gt;time-weighted rate of return (TWRR)&lt;/strong&gt;. The result of this calculation is tabulated; here is a segment of it:&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/short_selling/perf_evaluation.png&quot; style=&quot;width:80%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;This is interpreted as follows: AHL Partners was involved in shorting ASOS (ASC.L) over a period of 448 days in total, during which time its overall TWRR on the position was -96%. Of course, this is only based on the time during which AHL’s position was more than 0.5% of ASOS’s float, so this TWRR will not be perfectly accurate.&lt;/p&gt;

&lt;p&gt;The performance data was further cleaned to exclude total involvement periods of less than a year since I am more interested in identifying which managers are consistently making positive returns with their large short positions. A histogram of the annualised short returns is shown below:&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/short_selling/perf_hist.png&quot; style=&quot;width:60%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;The distribution itself is quite interesting, displaying long tails (longer on the left) and a sharp peak – note that returns can be more than -100% because we are dealing with the TWRR. The summary statistics paint a richer picture:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;&lt;strong&gt;Statistic&lt;/strong&gt;&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;&lt;strong&gt;Value&lt;/strong&gt;&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;Mean&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;-0.8%&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;Median&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;-5.0%&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;Skew&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;-0.02&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;Kurtosis&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;85.9&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;The high kurtosis means that the distribution has much fatter tails than a normal distribution, suggesting that the “outliers” are quite important, while the negative skew implies that the left tail is longer. However, the thing that jumped out at me was the huge difference between the median and the mean. The median short investment returns an annualised -5%, while the mean returns about 0%. This suggests that there is a small group of investments massively pulling up the average.&lt;/p&gt;

&lt;p&gt;We can go one step further – rather than just evaluating the performance of &lt;em&gt;investments&lt;/em&gt;, we can look at the performance of &lt;em&gt;investors&lt;/em&gt;. To do this, I used a simple aggregation procedure, taking the investor’s mean return across all positions. This is certainly not the best way of doing things because it assumes that the shorts are all of equal value. The dataset actually contains enough information for us to calculate the value of each investor’s shorts, and come up with a &lt;em&gt;dollar&lt;/em&gt; performance. Nevertheless, proceeding with the simplified analysis, the histogram of each investor’s average performance is shown below:&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/short_selling/investor_hist.png&quot; style=&quot;width:60%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;This provides evidence that it is not just a small group of investments pulling up the average, it is a small group of &lt;em&gt;investors&lt;/em&gt;. The top 10 investors, their average period of involvement, and their annualised return on the large short positions are as follows:&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/short_selling/top_performers.png&quot; style=&quot;width:100%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;Out of courtesy I will not name and shame the bottom 10, but they can be found in the Jupyter notebook if you’re curious. I was surprised to see many big names, but in retrospect, because this analysis does not take into account dollar returns, the playing field is tilted in favour of the smaller players.&lt;/p&gt;

&lt;p&gt;Lastly, we can try to get some sense of the different investment styles (and their relative successes) by plotting both the average annualised TWRRs and the average involvement period. The size of the points is proportional to the number of different companies being shorted (a good proxy for the assets under management of the investor), while darker colours correspond to higher frequencies.&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/short_selling/investor_perfs.png&quot; style=&quot;width:100%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;The plot can be used to comment that, for example, although D1 Capital Partners and Arrowgrass Capital Partners both have a similar annualised return of about 65%, D1’s investment style involves much shorter involvement periods with a given company. Unsurprisingly, it is also seen that the larger players also trade with higher frequency.&lt;/p&gt;

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

&lt;p&gt;In conclusion, the short disclosure dataset is almost certainly &lt;em&gt;not&lt;/em&gt; a basis for a simple copycat strategy. Although the net intelligent short interest has a statistically significant negative correlation with future returns (over time horizons of more than a month), the predictive power is weak, highlighted by the quantile plot. This may seem strange – the hypothesis was grounded on the reasonable assumption that the bets of hedge funds, who we consider to be the “smart money”, would be pretty good predictors of future returns.&lt;/p&gt;

&lt;p&gt;However, we must remember that this dataset only represents half of the story. Hedge funds (the clue is in the name) often hedge their long positions with shorts. These shorts may not be expected to have positive returns when the overall market is going up, as long as they outperform when the market is going down. In particular, a common strategy for large players like Marshall Wace is the long/short basket strategy, in which you go long a large group of companies and short another group, based on some ranking methodology. In this case, you almost expect most of your shorts (and longs) to do badly – it is the law of large numbers that results in a net profit.&lt;/p&gt;

&lt;p&gt;Another possible explanation for the weak predictive effect is short squeezes, in which an initial small increase in prices can result in a positive-feedback loop of liquidation as investors cover their positions.&lt;/p&gt;

&lt;p&gt;Despite the mixed results, this kind of analysis may provide a useful datapoint for discretionary equity portfolio managers because it shines light onto the landscape of marginal buyers/sellers. For example, if you see that a certain fund holds a large short position in a small company, you know that at some point in future there might be significant demand for the shares as the fund seeks to close its short position.&lt;/p&gt;

&lt;p&gt;We have also drawn some other interesting insights from the dataset, for example, the fact that a small group of investors seem to be pulling up the average. This could be an area for further investigation – rather than blindly copying from the person sitting next to you, it might make sense to first identify who the smartest person in the classroom and copy from them.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Statistical arbitrage in closed-end funds</title>
   <link href="https://reasonabledeviations.com/2020/05/10/stat-arb-cefs/"/>
   <updated>2020-05-10T00:00:00+00:00</updated>
   <id>https://reasonabledeviations.com/2020/05/10/stat-arb-cefs</id>
   <content type="html">&lt;p&gt;Sometimes, it is cheaper to buy a basket of assets than it is to buy the assets in the basket. In this post, we discuss closed-end funds and why they often trade at a discount to their net asset value. Furthermore, we explore whether this could be the basis for an algorithmic trading strategy.
&lt;!--more--&gt;&lt;/p&gt;

&lt;h2 id=&quot;closed-end-funds-vs-etfs&quot;&gt;Closed-End Funds vs ETFs&lt;/h2&gt;

&lt;p&gt;Most of us are probably familiar with exchange-traded funds (ETFs) – baskets of securities where the basket itself is tradable. The ETF industry has grown rapidly over the past few decades because ETFs provide a low-cost way to get exposure to a certain market, sector, or strategy.&lt;/p&gt;

&lt;p&gt;In this post, we will instead be discussing &lt;strong&gt;closed-end funds (CEFs)&lt;/strong&gt;. On the surface, these may seem similar to ETFs: CEFs are also baskets of securities, and they can also be listed and traded on an exchange. A pedant might point out that by definition, a CEF is an ETF since it is a fund that trades on an exchange – this is unfortunately not correct, as there is a true conceptual distinction.&lt;/p&gt;

&lt;p&gt;We often like to think of ETFs as upscaled equivalents of a personal trading portfolio. One gives money to a manager, who in return issues a share of the ETF to represent the ownership and invests that money in a basket of assets. This mental model is, in fact, a much better description of CEFs. ETFs are a little bit more complicated. For both ETFs and CEFs, an important point is that the share price is not “naturally” guaranteed to be equal to the value of all of the securities that the fund owns, i.e the &lt;strong&gt;net asset value (NAV)&lt;/strong&gt;. This is because the share price of the fund is determined by supply and demand – if everyone in the world suddenly wants to buy the shares of a fund, the share price can shoot up independently of the value of the underlying assets. However, ETFs have an interesting mechanism for ensuring that these deviations between the price and NAV are very short-lived.&lt;/p&gt;

&lt;p&gt;ETF fund managers are seldom the people who buy and sell the securities. They instead turn to &lt;strong&gt;authorised participants (APs)&lt;/strong&gt;, typically investment banks, who transact in the underlying securities. The fund manager then issues shares of the ETF to the AP, in return for the securities that were bought. This process is known as &lt;strong&gt;creation&lt;/strong&gt; because shares of the ETF are being issued. The AP is then free to hold these shares, or more often, trade them on a stock exchange. However, the AP’s role goes far beyond the initial purchase of the securities to set up the ETF. They play a critical role in ensuring that the ETF share price never strays too far from the NAV. Any momentary supply-demand imbalances can be arbitraged away – for example, if there is suddenly less demand for the ETF, and it trades at a discount to the NAV, the AP can buy ETF shares on the open market and &lt;strong&gt;redeem&lt;/strong&gt; them from the ETF fund manager. This locks in an arbitrage profit and drives the ETF price back towards the NAV.&lt;/p&gt;

&lt;p&gt;This is why ETFs are &lt;em&gt;open-end&lt;/em&gt; funds: the fund manager can issue/redeem however many shares they want. Conversely, CEFs issue a fixed number of shares when they first IPO, and there is no subsequent creation or redemption. As a result, we might expect there to be a larger spread between the CEF price and the NAV (compared with an ETF’s price and its NAV).&lt;/p&gt;

&lt;h2 id=&quot;nav-arbitrage-and-the-closed-end-fund-puzzle&quot;&gt;NAV Arbitrage and the Closed-End Fund Puzzle&lt;/h2&gt;

&lt;p&gt;Although CEFs don’t have the same creation and redemption mechanism as ETFs, there is still a fundamental link between the NAV and the price.&lt;/p&gt;

&lt;p&gt;Let’s take a very simple case where we have a “basket” of two stocks, say AAPL and FB. As of the time of writing, AAPL has a price of \$289/share and FB has a price of \$202/share. The fair price of this basket is then $\$289 + \$202 = \$491$. This is intuitively obvious, but to drive home the point, we can ask what would happen if the basket &lt;em&gt;weren’t&lt;/em&gt; priced at \$491. If the basket was instead trading at \$400, we would be correct in thinking that the basket is “cheap” – we’d certainly rather buy the basket than the two stocks individually. We can take this a step further and go long the basket and short the individual stock, which results in a risk-free profit once other market participants realise that the basket and the two stocks should be priced identically.&lt;/p&gt;

&lt;p&gt;If we are a majority shareholder in the fund, we don’t even have to rely on the rationality of other market participants; we can force a liquidation event, in which the fund manager sells all the securities, distributes the proceeds as dividends, then delists the shares. At such a time, the price of the shares must be equal to the NAV, less the cost/slippage from selling the securities.&lt;/p&gt;

&lt;p&gt;Thus, even without APs, the price of a CEF &lt;em&gt;should&lt;/em&gt; track its NAV. However, empirically, this is not the case. Below, we show the price vs NAV for two large funds, the Eaton Vance Global Diverse Equity Fund (EXG), and the Aberdeen Asia-Pacific Income Fund (FAX), using data from &lt;a href=&quot;https://www.cefconnect.com/&quot;&gt;CEFConnect&lt;/a&gt;:&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/cef/exg_fax_nav.png&quot; style=&quot;width:100%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;For both of these funds, the share price trades at a persistent discount to the NAV – in the case of FAX, this discount is almost 20%. The existence of this spread (typically a discount) between the price and the NAV is known as the &lt;strong&gt;Closed-End Fund Puzzle&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;There are several responses to the closed-end fund puzzle, though I would refer interested readers to &lt;a href=&quot;https://scholar.harvard.edu/files/shleifer/files/investorsentiment.pdf&quot;&gt;Lee, Shleifer and Thaler (1991)&lt;/a&gt; for a detailed investigation:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Cost of transacting – we already touched on this. In a liquidation event, we will not receive the full NAV because of the transaction costs, which may be rather significant if we are dealing with assets that are hard to sell. We can verify that this is indeed a contributing factor by observing that there seems to be a broad negative correlation between asset liquidity and the spread. Equity CEFs typically have a smaller discount compared with bond CEFs, because equities are easier to sell.&lt;/li&gt;
  &lt;li&gt;Difficulty of arbitrage – in practice, it is hard to lock in a risk-free arbitrage because it may be very difficult to transact in the underlying (which is sometimes why the CEF was created in the first place). Furthermore, the fund managers are free to enter and exit positions as they desire, so an arbitrageur would have to constantly make changes to adequately replicate the portfolio.&lt;/li&gt;
  &lt;li&gt;CEFs have a much lower fraction of institutional ownership than the underlying – it is possible that the pricing of CEFs is less rational as a result, with price movement driven by liquidity and sentiment rather than value.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The rest of this post will investigate the hypothesis that the spread between the CEF price and its NAV is mean-reverting, or more intuitively, that larger discounts are predictive of a future price increase and larger premiums are predictive of a future price decrease.&lt;/p&gt;

&lt;h2 id=&quot;statistical-arbitrage&quot;&gt;Statistical arbitrage&lt;/h2&gt;

&lt;p&gt;Statistical arbitrage (stat arb) is a fancy term describing the process of buying assets that are statistically cheap and selling assets that are statistically expensive, hoping to lock in the difference. The key is that these arbitrage opportunities are not at all risk-free, they are only really arbitrage opportunities in the sense that if we make a sufficiently large number of such bets, we will have a positive expected value (the law of large numbers).&lt;/p&gt;

&lt;p&gt;The classic stat arb strategy is &lt;strong&gt;pairs trading&lt;/strong&gt;, which involves finding two assets that are highly correlated and trading the spread between them. The standard example is Coca-Cola and Pepsi. Rather than making a bet on the overall direction of either stock, we can eliminate idiosyncratic risk by trading the difference between the price of Coca-Cola shares and Pepsi shares. To ensure that our correlations aren’t spurious, it is desirable for there to exist some fundamental economic reason why the two assets are related. In the case of Coca-Cola and Pepsi, the argument goes that because they are essentially substitute goods, any widening of the spread should be temporary. So if Coca-Cola stock appreciates while Pepsi’s stays flat, the statistical arbitrageur may long Pepsi and short Coca-Cola.&lt;/p&gt;

&lt;p&gt;Stat arb is most effective when the spread is &lt;strong&gt;stationary&lt;/strong&gt;, a technical term which describes the fact that the data-generating process for the time series does not change (its statistical properties are constant in time). This is a logical requirement – if the statistical properties were changing, nothing stops the spread from widening forever. Stationary series, as a consequence of the definition, are almost always &lt;strong&gt;mean-reverting&lt;/strong&gt; – wider spreads are more likely to be followed by narrower spreads and vice versa.&lt;/p&gt;

&lt;p&gt;In many ways, the spread between the price and NAV is the ideal candidate for a stat arb strategy. There is an incredibly strong economic link between the NAV and the price; we even know that at liquidation they &lt;em&gt;must&lt;/em&gt; converge (less fees). The only wrinkle is that the NAV isn’t actually a tradable security. This doesn’t completely preclude the development of a trading strategy, but it wouldn’t be a proper pairs trade since we would only be acting on one of the legs. For example, if the CEF were trading at a 20% discount to NAV, a pairs trade would involve going long the CEF and “short the NAV”. In this case, even if both the NAV and the price of the CEF decrease, your position would be fine because the short side hedges you. But since you can’t trade the NAV, although you are still able to make bets on convergence (by going long the CEF when it is trading at a discount), such bets would not be pure bets on the spread.&lt;/p&gt;

&lt;p&gt;It may be possible to work-around this by computing the beta of the NAV with respect to a comparable asset/ETF then trading the correct amount of that comparable. For example, if we were dealing with a CEF that invests in high yield bonds, we could compute the beta of the NAV to a high yield bond ETF (e.g. HYG). Let’s say $\beta = 2$, in which case we could bet on the spread converging by going long \$10k in the CEF and short \$5k of HYG. But again, this is not a perfect bet on the spread.&lt;/p&gt;

&lt;p&gt;This important caveat notwithstanding, we shall now examine the stationarity of the price-NAV spread and investigate whether the spread is predictive of future returns.&lt;/p&gt;

&lt;h2 id=&quot;the-price-nav-spread&quot;&gt;The price-NAV spread&lt;/h2&gt;

&lt;p&gt;We will look at a single CEF, namely, the &lt;a href=&quot;https://www.cefconnect.com/fund/USA&quot;&gt;Liberty All-Star Equity Fund&lt;/a&gt;. This was chosen because it is large and liquid (and has a memorable ticker, USA). Using an appropriate colour scheme, we can plot the price and NAV:&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/cef/USA_price_nav.png&quot; style=&quot;width:100%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;From 2000 to 2009, the CEF alternates between trading at a discount/premium to NAV, before settling into a persistent discount from 2009 onwards. Below is a plot of the spread, i.e $\text{price}/\text{NAV} - 1$  (positive is premium, negative is discount).&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/cef/USA_spread.png&quot; style=&quot;width:100%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;Now, we can perform an Augmented Dickey-Fuller test to determine whether the spread is indeed stationary. It turns out that although there is not enough statistical evidence to reject the null hypothesis that the spread is non-stationary, the price and NAV are significantly &lt;strong&gt;cointegrated&lt;/strong&gt; (this intuitively means that some linear combination of the price and NAV &lt;em&gt;is&lt;/em&gt; stationary).&lt;/p&gt;

&lt;p&gt;Stationarity aside, let’s press on with a somewhat crude estimate of the predictive value of the spread. Rather than doing any kind of proper backtest, we will just compute the correlation between the current spread and the subsequent change in the spread. To be specific, we shall use the Spearman rank correlation coefficient, sometimes referred to in finance as the &lt;strong&gt;information coefficient (IC)&lt;/strong&gt;, which is more robust to nonlinearity than the Pearson product-moment correlation coefficient.&lt;/p&gt;

&lt;p&gt;We find that the correlation between the current spread and the change in the spread over the following week is $-0.06$ (statistically significant at the 5% level). It is comforting that there is a negative sign because it supports the hypothesis that the spread mean-reverts: premiums are correlated with decreasing premiums, discounts are correlated with narrowing discounts.&lt;/p&gt;

&lt;p&gt;However, as we discussed previously, the NAV isn’t tradable so we instead want to find out the correlation between the current spread and the price return of the CEF. The same Spearman rank calculation gives a correlation of $-0.07$. This is quite a decent IC – according to a paper by &lt;a href=&quot;https://www.jpmorgan.com/cm/BlobServer/Extending_the_Fundamental_Law_of_Investment_Management_.pdf?blobkey=id&amp;amp;blobwhere=1158630145176&amp;amp;blobheader=application%2Fpdf&amp;amp;blobheadername1=Cache-Control&amp;amp;blobheadervalue1=private&amp;amp;blobcol=urldata&amp;amp;blobtable=MungoBlobs&quot;&gt;JP Morgan Equity Research&lt;/a&gt;, investors can “achieve significant risk adjusted excess returns with information coefficients between 0.05 and 0.15” (the sign doesn’t matter here). We can visualise the relationship with a scatterplot:&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/cef/spread_ret_plot.png&quot; style=&quot;width:80%;&quot; /&gt;
&lt;/center&gt;

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

&lt;p&gt;There is clearly a lot more exploration that needs to be done – we have only looked at the spread for a single fund, and have chosen a simplistic information coefficient analysis rather than conducting a full backtest. Nevertheless, the results thus far have been quite encouraging. In addition to the reasonably high information coefficient, we have a clear economic hypothesis about why the discount to NAV may be a persistent predictive factor. In a recent MacroVoices podcast, Eric Peters shared a great insight (emphasis is mine):&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Our approach to anything that we ever do in markets, is to first ask the question: &lt;strong&gt;why do you get paid to do something?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Without this economic understanding, it’s much more likely that any pattern you’ve discovered is a statistical spectre that haunts the historical data but will disappear in the daylight of future data.&lt;/p&gt;

&lt;p&gt;To answer the question in the introduction, I &lt;em&gt;do&lt;/em&gt; think that this could form the basis of a trading strategy and hope that over the next few weeks I will have a chance to investigate further.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>A Tanker Trade</title>
   <link href="https://reasonabledeviations.com/2020/04/24/oil-storage/"/>
   <updated>2020-04-24T00:00:00+00:00</updated>
   <id>https://reasonabledeviations.com/2020/04/24/oil-storage</id>
   <content type="html">&lt;p&gt;April 2020 has been a volatile month for oil. Last week, the May WTI contract traded at a low of &lt;em&gt;minus&lt;/em&gt; \$40 a barrel. In a desperate search for storage space, people have been chartering oil tankers to use as floating storage units, leading to a price surge in shares of tanker companies like Nordic American Tanker (46%), Teekay (30%), and Scorpio Tankers (59%). In this post, we aim to build a framework for forecasting the revenue of DHT Holdings (NYSE:DHT), a tanker company.
&lt;!--more--&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The Excel models and accompanying python code are &lt;a href=&quot;https://github.com/robertmartin8/pValuation/tree/master/OilTankers&quot;&gt;here&lt;/a&gt;. Please use them responsibly!&lt;/em&gt;&lt;/p&gt;

&lt;h2 id=&quot;it-takes-two-to-contango&quot;&gt;It takes two to contango&lt;/h2&gt;

&lt;p&gt;I am certainly not an expert on energy markets, but over the past couple of weeks, I’ve seen a lot of misinformation regarding oil prices. Since this is relevant to the content of this post, I want to briefly clarify a few of misunderstandings (cognisant of the fact that I probably have a few of my own).&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;As a retail trader, it is very difficult for you to trade spot/physical oil. Instead, you might use oil futures, or more likely, an ETF like USO (which owns short-term futures). In usual times, USO does a decent job of tracking spot oil, so it’s a cheap way for you to get exposure to oil.&lt;/li&gt;
  &lt;li&gt;Because of the coronavirus, there has been a massive demand shock. The resulting surplus in oil has lead to a lot of the storage capacity being filled up, resulting in a much higher storage cost.&lt;/li&gt;
  &lt;li&gt;Futures contracts are thus more expensive the further out you go because they bake in the storage cost. This situation is known as &lt;strong&gt;contango&lt;/strong&gt;, or in this case &lt;strong&gt;super contango&lt;/strong&gt; owing to the magnitude of the effect.&lt;/li&gt;
  &lt;li&gt;NYMEX WTI futures are physically settled, so when it comes to expiry, owning a long contract obligates you to receive delivery of 1000 barrels of oil. When the storage tanks are full, the piece of paper that gives you the right (and obligation) to receive oil becomes a liability – you are willing to give it away, or even pay someone to take it off your hands. Hence “negative oil prices”.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All this leads to the surprising fact that if you buy oil at \$5 a barrel and it trades at \$20 a barrel two months later, you have not quadrupled your investment. You might have actually &lt;em&gt;lost&lt;/em&gt; money on this trade – it’s impossible to say with just the information I have given you since you need to factor in the cost of rolling your contract to the next month. When the futures curve is in super contango, this roll can be incredibly costly. We will later revisit this concept when we try to infer the storage cost from the futures curve.&lt;/p&gt;

&lt;h2 id=&quot;modelling-revenue-for-dht-holdings&quot;&gt;Modelling revenue for DHT Holdings&lt;/h2&gt;

&lt;p&gt;The main goal of this post is to build a framework for forecasting the revenue of a tanker company. Though I don’t intend for this post to be a stock pitch, our discussion will be much more tangible in the context of a particular listed company. I decided on DHT Holdings because it is a pure-play on tankers: DHT owns 27 VLCCs (Very Large Crude Carriers – yes, that is their technical name) and their business does nothing but rent these out. In 2019, they had \$535m in revenue, all of which was from shipping. Of their expenses, less than 4% were administrative; everything else was related directly to the tankers. This makes the revenues rather easy to forecast in principle. If you know how much the daily rate that DHT charges you to charter a tanker you can multiply this by 365 days then by the number of tankers to ballpark their annual revenue.&lt;/p&gt;

&lt;p&gt;The model essentially consists of an Excel spreadsheet containing estimates of the annual revenue contributions from each of the 27 tankers. The main complexity we will be accounting for in this simple model is that you can either time-charter a tanker, paying a fixed price to rent it for a longer period of time (6 months to a few years), or you can do a spot charter, renting the boat for a short period on short notice. As always, we turn to DHT’s most recent &lt;a href=&quot;https://www.sec.gov/Archives/edgar/data/1331284/000114036120006806/form20f.htm&quot;&gt;annual report&lt;/a&gt; (in particular, the notes to the financial statements therein), which provide a wealth of information regarding how DHT’s vessels were used in 2019.&lt;/p&gt;

&lt;h3 id=&quot;time-charters&quot;&gt;Time-charters&lt;/h3&gt;

&lt;p&gt;As of the end of 2019, some of DHT’s ships had already been chartered for 2020. Unfortunately for DHT (and luckily for the people renting the boats), these rates are fixed so DHT cannot raise the prices even though demand is high. The annual report tells us that the 2020 revenues from the time-charters will be \$47.4m. I divided this by the four boats that will be chartered for all of 2020 (the &lt;em&gt;DHT Lotus&lt;/em&gt; will only be chartered until Q1), then divided by 365 to estimate the daily rate for these four ships. The result comes to about \$32,000 a day.&lt;/p&gt;

&lt;p&gt;On April 1 2020, DHT &lt;a href=&quot;https://www.dhtankers.com/dht-holdings-inc-announces-time-charters-for-six-of-its-vlccs/&quot;&gt;announced&lt;/a&gt; time charters for another 6 VLCCs. These time charters are all for 12 months with a daily rate of \$67,300. As we can see, this is more than double the daily rate of the existing charters, though this premium is not entirely due to supply and demand – you would expect longer contracts to have a cheaper daily rate because it reduces risk for DHT.&lt;/p&gt;

&lt;h3 id=&quot;spot-charters&quot;&gt;Spot charters&lt;/h3&gt;

&lt;p&gt;Sadly, once we have dealt with the time charters, things become a lot more difficult. This is because the spot charter rate depends on supply and demand – if everybody wants to charter a ship, DHT can raise the daily rates. We will model the situation as follows. Because of the WTI supply glut, there will be a certain number of &lt;strong&gt;high-demand days&lt;/strong&gt; for which people will be willing to pay the &lt;strong&gt;high-demand daily rate&lt;/strong&gt;. For the rest of the year, we use the &lt;strong&gt;low-demand daily rate&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;We may further assume that the low-demand daily rate will be similar to what it was last year. We are told that in 2019 there was \$478m in revenue from spot chartering (between 22 ships), i.e an average daily rate of \$60,000. Hence the two key variables are the number of high-demand days and the high-demand daily spot rate. This is where the estimates start getting pretty subjective, but we will later do a sensitivity analysis that somewhat mitigates this.&lt;/p&gt;

&lt;p&gt;I’ve been reading about people trying to hire tankers for storage since at least mid-March, so we know that the number of high-demand days is going to be greater than 30. A quick look at the futures curve shows that the storage cost is going to be very high for the month of May and possibly even June. Let’s err on the side of caution and say that there are 60 days of high demand.&lt;/p&gt;

&lt;p&gt;Now for the hardest part – how do we quantify what “high demand” means in terms of the spot rate? Well we, know that in 2019 the average spot rate was about \$60,000 compared to \$30,000 for the time charter. The most recent time-charter had a rate of \$67,300, so assuming 2019’s time:spot ratio holds in 2020, an initial estimate of the spot rate is about \$130k. However, this was on April 1st, about 3 weeks before the extent of the supply constraints came to a head with negative oil. A number of different sources (&lt;a href=&quot;https://seekingalpha.com/news/3557384-vlcc-charter-rates-skyrocket-past-200k-per-day&quot;&gt;not particularly credible&lt;/a&gt;) estimate a spot rate of over \$200k a day.&lt;/p&gt;

&lt;p&gt;With these assumptions, we can build a simple table adding up the revenue from each VLCC. As a baseline, I am using CapIQ’s consensus revenue estimate of \$646m (updated as of 21st April), which is 20% higher than 2019’s revenue.&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/oil_tankers/dht_rev_surprise.png&quot; style=&quot;width:60%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;It’s nice to see that our forecasted revenue is in the same ballpark as the CapIQ consensus estimate (if it were 10x higher or lower, it’d be more likely that we were doing something wrong).&lt;/p&gt;

&lt;h2 id=&quot;the-futures-curve-and-the-market-implied-cost-of-carry&quot;&gt;The futures curve and the market-implied cost of carry&lt;/h2&gt;

&lt;p&gt;In the initial analysis, we gave a very haphazard estimate of the high-demand spot charter rate. Let’s see if we can do a little bit better by examining the futures curve for oil:&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/oil_tankers/futures_curve.png&quot; style=&quot;width:80%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;The curve is said to be “in contango” because it is sloping upwards – this is associated with the &lt;strong&gt;cost of carry&lt;/strong&gt; (the cost of storing the underlying), because you’d be willing to pay a little more (or in these times, a &lt;em&gt;lot&lt;/em&gt; more) to avoid having to hold the physical oil.&lt;/p&gt;

&lt;p&gt;Now for some maths. Let’s say the cost of carry is $s$ per year (expressed as a percentage). So if $s=0.10$ and the oil costs \$20 a barrel, the storage cost is $2 a year. Hence by a simple no-arbitrage argument, the price &lt;em&gt;F&lt;/em&gt; of a futures contract expiring 1 year from now relative to the spot price $S_0$ is:&lt;/p&gt;

\[F = S_0 (1+s)\]

&lt;p&gt;This is called a no-arbitrage argument because if it &lt;em&gt;weren’t&lt;/em&gt; true, you could make a risk-free arbitrage profit. Note that I am ignoring the interest rate because it is so low (money printer go brrrr). With the above formula, we see that the implied storage cost (in dollars) between any two months is the price difference of their futures contracts. For example, as of 24/4/20, the June contract is trading at \$16.48 and the July contract is trading at \$21.80. Hence, a reasonable estimate for the storage cost in June is $\$21.80 - \$16.48 = \$5.32$. I was also interested in calculating the annualised cost of storage for a given month, so derived the formula below, but it’s not relevant to our discussion.&lt;/p&gt;

\[\begin{align*} 
F_1 &amp;amp;= S_0 e^{sT_1} \\
F_2 &amp;amp;= S_0 e^{sT_2} \\
\implies \frac{F_2}{F_1} &amp;amp;= e^{s(T_2 - T_1)} \\
\end{align*}\]

\[\implies s = \frac{1}{T_2- T_1} \ln \left(\frac{F_2}{F_1} \right)\]

&lt;p&gt;Below is a plot that shows the implied cost of carry for different months, overlayed on the futures curve.&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/oil_tankers/futures_cost_of_carry.png&quot; style=&quot;width:100%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;Using the June cost of \$5.32 a barrel, we can put an upper bound on the high-demand spot charter rate. It can be calculated, using the density of oil and the deadweight tonnage, that a VLCC can carry about 2 million barrels of oil. Thus, a cost of carry of \$5.32 per barrel for a month is equivalent to a daily rate of $\$5.32 \times 2 \times 10^6 ~/~ 30 = \$355,000$. This is only an upper bound because in reality there will be a nontrivial cost of freight, so it is not fair to allocate all of the \$5.32 to the cost of the VLCC. In any case, it is nice to see that our initial estimate of \$200k a day lies between our supposed lower and higher bounds. In fact, \$200k seems like quite a conservative estimate for the high-demand rate, because it is the June storage cost. If we calculate the &lt;em&gt;May&lt;/em&gt; storage cost based on the settlement price of -\$40, we get the ludicrous spot charter rate of \$3.8m &lt;em&gt;a day&lt;/em&gt;. I’m not saying that this is anywhere close to the true May storage cost, but it’s just to illustrate that \$200k is very much on the low end.&lt;/p&gt;

&lt;p&gt;The astute reader will note that we could just use each month’s market-implied spot charter rates directly, rather than our unsophisticated “high-demand low-demand” model. However, I think that the current procedure of reverting to the 2019 cost for the low-demand spot rate is more robust and has significantly less model risk, so we’ll stick with that.&lt;/p&gt;

&lt;h2 id=&quot;sensitivity-analysis&quot;&gt;Sensitivity analysis&lt;/h2&gt;

&lt;p&gt;Before we conclude, let’s conduct a sensitivity analysis. This is a critically important part of any model because it emphasises the intrinsic uncertainty of trying to forecast the future. Concretely, we make Excel recompute the quantity of interest (in this case, FY20 revenue) under a range of different values of our two key inputs: the high-demand spot rate and the number of high-demand days.&lt;/p&gt;

&lt;p&gt;Because one of the objectives of this exercise is to understand the downside, I have centred the high-demand rates around the initial lower estimate of \$200k. For the high-demand days, we use a range of values from 45 days to 75 days, centred around 60.&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/oil_tankers/sensitivity.png&quot; style=&quot;width:100%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;I’ve applied special formatting (dark red with white text) to represent the revenue forecasts that are below the CapIQ estimate. You’ll notice that these are in the top-left as expected, corresponding to a lower spot rate and a shorter period of high demand. However, under a broad range of other assumptions, the model is forecasting positive earnings surprises relative to the CapIQ estimate.&lt;/p&gt;

&lt;h2 id=&quot;encore&quot;&gt;Encore&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;This section was added a few days after the original post, building on the feedback I received from Reddit&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;I posted my initial analysis on Reddit and it generated some nice discussion. Someone pointed out that you can actually find spot charter rates online; in particular, there is a Twitter user that posts nothing but charter details:&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/oil_tankers/tankers_twitter.png&quot; style=&quot;width:80%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;This is very useful because it removes a lot of the subjectivity in our estimation of spot charter rates. To that end, I wrote a python script that does the following:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Downloads and parses the tweets using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tweepy&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;Extract the ship name, daily charter rate, the number of days chartered, and start date&lt;/li&gt;
  &lt;li&gt;Combine this with the charter data from their annual report and press releases (which I had to input manually)&lt;/li&gt;
  &lt;li&gt;Build a pandas dataframe with 365 columns (one for each day) and 27 columns (one for each ship)&lt;/li&gt;
  &lt;li&gt;Fill in the data for the days we know&lt;/li&gt;
  &lt;li&gt;Output to excel&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A section of the spreadsheet is shown below:&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/oil_tankers/v2_spreadsheet.png&quot; style=&quot;width:100%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;All of the zeros correspond to days for which we don’t have any explicit information. We can then fill these in with whatever estimation model we see fit. Rather than using the two-stage model from before, I’m going to do something much simpler and replace the zeros with the mean charter rate of non-missing entries:&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/oil_tankers/v2_revenue_forecast.png&quot; style=&quot;width:80%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;This results in a forecasted 2020 revenue of \$652m, very close to CapIQ’s estimate. I think the true revenue will lie somewhere between this value and the more optimistic forecast from the first model.&lt;/p&gt;

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

&lt;p&gt;In this post, we have built a very simple model for DHT Holdings’ 2020 revenue by combining their fixed time charter rates with our estimates of the spot charter rate, in light of the recent supply glut. Under fairly conservative estimates, I initially forecasted a 10% revenue beat on the upside. However, with more refined data based on actual spot charter rates this year, the CapIQ consensus estimate seems fair.&lt;/p&gt;

&lt;p&gt;There are many variables that I have conveniently chosen to ignore. For example:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Freight, which we mentioned but didn’t quantify, may be quite expensive. Rail freight costs about \$5 a barrel, which would constitute the lion’s share of the \$5.23 June cost of carry.&lt;/li&gt;
  &lt;li&gt;It takes a couple of weeks to get a VLCC from the Suez to the Texan coast. There may be complications with space at the ports (which may not have enough space to park a huge number of VLCCs).&lt;/li&gt;
  &lt;li&gt;There may be secular headwinds, such as the new 2020 International Maritime Organisation regulations regarding fuel sulphur content, which may lower the low-demand spot rate.&lt;/li&gt;
  &lt;li&gt;We have deliberately tried to minimise the number of explicit assumptions regarding the direction of the oil market, because it is not an area that I have any edge in.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However, I think that the Excel model nevertheless provides a reasonable starting point for people to input their own estimates, perhaps factoring in the nuances. Besides, the sensitivity analysis is a nice tool to show you how varying your inputs affects the model output, and in this particular case, shows a reasonable margin of safety. I want to emphasise that this post is not a stock pitch; at best, it is a &lt;em&gt;component&lt;/em&gt; of a stock pitch. Ultimately, I suggest you put in the time to come up with your own estimates of the different inputs, and in so doing, tell your own story about the company.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Understanding the market's expectations of COVID-19</title>
   <link href="https://reasonabledeviations.com/2020/03/25/coronavirus-equity-expectations/"/>
   <updated>2020-03-25T00:00:00+00:00</updated>
   <id>https://reasonabledeviations.com/2020/03/25/coronavirus-equity-expectations</id>
   <content type="html">&lt;p&gt;One of the reasons why I find markets fascinating is that they are capable of integrating huge amounts of information, misinformation, hope, fear and uncertainty into a single number – the price of an asset. In this post, we work backwards, quantitatively examining what the current price of an asset can tell us about its future prospects.&lt;/p&gt;

&lt;!--more--&gt;

&lt;h2 id=&quot;the-importance-of-expectations&quot;&gt;The importance of expectations&lt;/h2&gt;

&lt;p&gt;For an investment to be successful, it is not enough that you make a correct prediction. You must make a correct prediction that hasn’t already being priced in by the market. Amateur investors quickly realise that buying Google stock because “everyone uses Google!” is a bad idea – the market realised the same thing long ago and has therefore rewarded Google with a share price that is 25 times its annual earnings.&lt;/p&gt;

&lt;p&gt;The mechanism for this is as follows: assuming an &lt;em&gt;efficient&lt;/em&gt; market (we will comment on this assumption later), if market participants expect a company’s earnings to go up next year, they will buy the stock in advance. So once all market participants have formulated and traded on their views, the market price of the asset encodes future expectations for earnings (or to be precise, cash flows).&lt;/p&gt;

&lt;p&gt;To make a successful investment, it really helps to have some idea of what these expectations are so that you can understand whether or not your perspectives deviate from consensus. Only then will you be able to profit – if your deviant perspective is correct.&lt;/p&gt;

&lt;h2 id=&quot;price-implied-expectations&quot;&gt;Price-implied expectations&lt;/h2&gt;

&lt;p&gt;Unfortunately, although we have just argued that the current price incorporates expectations, the reverse procedure of figuring out the expectations implied by a price is not trivial. Imagine I have a basket of fruit containing some apples and bananas (quantities &lt;em&gt;A&lt;/em&gt; and &lt;em&gt;B&lt;/em&gt; respectively). I tell you that there are 5 pieces of fruit in the basket. How many apples do I have?&lt;/p&gt;

&lt;p&gt;Obviously, there isn’t a (unique) answer to this question because I haven’t given you enough information. There are two unknowns (&lt;em&gt;A&lt;/em&gt; and &lt;em&gt;B&lt;/em&gt;), but only one equation relating them, $A + B = 5$.&lt;/p&gt;

&lt;p&gt;This silly analogy goes quite far in explaining the difficulty of working out the price-implied expectations. There are many variables that determine a stock price, e.g. future sales growth, profit margins, reinvestment rates, interest rates – and for each of these variables, market expectations matter. In the same way that knowing the total quantity of fruit was insufficient to calculate the number of apples, the price is insufficient to calculate the expectation for a particular variable, like the future sales growth.&lt;/p&gt;

&lt;p&gt;However, although in the fruit example we weren’t able to calculate a &lt;em&gt;unique&lt;/em&gt; value of &lt;em&gt;A&lt;/em&gt; or &lt;em&gt;B&lt;/em&gt;, we were able to restrict the possibilities to those values of &lt;em&gt;A&lt;/em&gt; and &lt;em&gt;B&lt;/em&gt; that satisfy $A+ B = 5$. This relationship can be visualised as a straight line in “fruit space”:&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/pvaluation/fruit_contour.png&quot; style=&quot;width:50%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;We will revisit this idea in due course. However, we must first understand how assets are priced (how the total number of fruits in the basket is related to the numbers of apples and bananas).&lt;/p&gt;

&lt;h2 id=&quot;discounted-cash-flows-dcfs&quot;&gt;Discounted Cash Flows (DCFs)&lt;/h2&gt;

&lt;p&gt;Would you rather have \$1000 today or \$1000 a year from now? The correct answer is to take the \$1000 today because you can invest it and earn some return on it. Following on from that, we can calculate how much a guaranteed \$1000 &lt;em&gt;n&lt;/em&gt; years from now should be worth to us today, in other words, the &lt;em&gt;present value&lt;/em&gt; of the \$1000:&lt;/p&gt;

\[PV = \frac{1000}{(1+i)^n}\]

&lt;p&gt;Here, &lt;em&gt;i&lt;/em&gt; is the “discount rate”, equal to the rate of return you could get on an asset of similar risk. In the above example, we would use the risk-free rate since I said that the \$1000 is guaranteed to be paid.&lt;/p&gt;

&lt;p&gt;This simple formula is the heart of all intrinsic valuation methodologies, but it rapidly becomes more complicated when we deal with assets whose future cash flows are uncertain. In particular, there are many idiosyncrasies when it comes to valuing equities (i.e shares in companies).&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Corporate earnings are very different to cash flows.&lt;/li&gt;
  &lt;li&gt;If we own an equity stake in a company, how much cash do we actually receive?&lt;/li&gt;
  &lt;li&gt;Earnings and cash flows are anything but static – management can deploy capital effectively to improve future growth or conversely can make investments that are value-destructive.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the next section, we discuss how some of these issues are resolved, to value the S&amp;amp;P500.&lt;/p&gt;

&lt;h2 id=&quot;valuing-the-sp500&quot;&gt;Valuing the S&amp;amp;P500&lt;/h2&gt;

&lt;p&gt;The S&amp;amp;P500 index consists of the largest 500 companies (measured by market capitalisation) in the US. When people are talking about how “the markets” are performing, more often than not they are referring to the S&amp;amp;P.&lt;/p&gt;

&lt;p&gt;The framework I am using to value the S&amp;amp;P500 was built by Aswath Damodaran, the “Dean of Valuation”. A brief overview is given below, but I would encourage you to read his &lt;a href=&quot;https://aswathdamodaran.blogspot.com/2020/02/a-viral-market-meltdown-fear-or.html&quot;&gt;blog post&lt;/a&gt; (or watch the accompanying &lt;a href=&quot;https://www.youtube.com/watch?v=1vJdCpVxO7s&quot;&gt;YouTube video&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Remember, at its core, a DCF only needs two things: future cash flows and a discount rate. To estimate the future cash flows of the S&amp;amp;P, we estimate the earnings growth in the next five years, as well as what fraction of earnings is returned to shareholders in the form of dividends and buybacks. The discount rate depends on the equity risk premium and the risk-free rate of return. At the end of year 5, the index has some &lt;em&gt;terminal value&lt;/em&gt; that is calculated by assuming that the S&amp;amp;P grows in line with the economy in perpetuity.&lt;/p&gt;

&lt;p&gt;The cash flow in year $n$ (for $n \leq 5$) can be written in terms of the base-year earnings $E_0$, the growth rates $g_n$, and the cash return fractions $f_n$:&lt;/p&gt;

\[CF_n = f_n \cdot E_0(1+g_0)(1+g_1)...(1+g_{n-1})\]

&lt;p&gt;The initial cash return fraction $f_0$ can be set to whatever number we want, but we will ensure that by year 5 they tend towards a stable long-term quantity.&lt;/p&gt;

&lt;p&gt;The terminal value in year 5 depends on the perpetual growth rate $\tilde{g}$:&lt;/p&gt;

\[TV = \frac{CF_5 (1 + \tilde{g})}{(i - \tilde{g})}\]

&lt;p&gt;Hence the present value of the S&amp;amp;P index is:&lt;/p&gt;

\[PV = \sum_{n=1}^5 \frac{CF_n}{(1+i)^n} + \frac{TV}{(1+i)^5}\]

&lt;p&gt;The base-year earnings is a known quantity, and it is possible to find the consensus estimates of near-term growth rates $g_n$ online. Thus, plugging these numbers into the formula should give a value near the current price of the S&amp;amp;P500.&lt;/p&gt;

&lt;h2 id=&quot;the-effect-of-covid-19&quot;&gt;The effect of COVID-19&lt;/h2&gt;

&lt;p&gt;To model how COVID-19 affects the value of the index, we can think about how it changes the following variables:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Fiscal year (FY) 2020 earnings growth – this will definitely take a hit as consumers spend less.&lt;/li&gt;
  &lt;li&gt;Earnings growth in FY2021-2024 – this depends on the fraction of lost earnings that we think will be recouped in the subsequent years.&lt;/li&gt;
  &lt;li&gt;The fraction of cash returned – companies may become more conservative and retain more cash, lowering the cash return fraction.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Damodaran argues that any effects on the discount rate are minimal because the decrease in the risk-free rate is offset by the increase in equity risk premiums. Also, as we will later verify, the fraction of cash returned is nowhere near as important as the first two variables (because regardless of the initial fraction, by the end of year 5 it converges to a stable value). Thus, we can make the approximation that price depends on two variables: the drop in FY20 earnings and the fraction of earnings recouped.&lt;/p&gt;

&lt;p&gt;Here we have a minor problem: the model requires us to specify the earnings &lt;em&gt;growth&lt;/em&gt; in FY21-24 – while this is indeed related to the fraction of earnings recouped, converting between the two is not trivial. It is intuitively clear that if you think a high fraction of earnings will be recouped, subsequent growth will have to be greater to make up for the lost earnings. But to quantify this, you essentially have to project out the cash flows under the pre-coronavirus consensus growth estimates ($g_0’ = 5.52\%$ in FY20 and $g’ = 3.36\%$ in FY21-24), with $1-f$ of the FY20 earnings lost permanently. The resulting formula is as follows, where $g_0$ (no dash) denotes our estimate of the FY20 earnings growth (negative since we are expecting a drop):&lt;/p&gt;

\[\hat{g} = \left[\left(1+f \cdot \frac{g_0' - g_0}{1 + g_0}\right)\cdot (1+g')^4\right]^{1/4} -1\]

&lt;p&gt;EDIT: thanks to Ryan Kho for pointing out that I had transcribed the formula incorrectly. The formula is not at all intuitive, so I have uploaded a &lt;a href=&quot;https://github.com/robertmartin8/RandomWalks/blob/master/valuations/EarningsRecoupment.xlsx&quot;&gt;spreadsheet&lt;/a&gt; to demonstrate where it comes from.&lt;/p&gt;

&lt;p&gt;With this out of the way, we are now in a situation very similar to that of the fruit basket. We have two variables $f$ and $g_0$, that combine to form the price. Given the price alone, we do not have enough information to find either of $f$ and $g_0$, but we can certainly plot their relationship visually.&lt;/p&gt;

&lt;h2 id=&quot;python-implementation&quot;&gt;Python implementation&lt;/h2&gt;

&lt;p&gt;If you just want to see the results, feel free to skip over this section. In any case,
there’s nothing particularly fancy going on – it’s just a python translation of the maths from above:&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;c1&quot;&gt;# Function to value the S&amp;amp;P given drop and growth.
&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;valuation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;drop&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;growth&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;base_earnings&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;163&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;f0&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.855&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# base cash return
&lt;/span&gt;    &lt;span class=&quot;n&quot;&gt;rf&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.0134&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# risk free rate
&lt;/span&gt;    &lt;span class=&quot;n&quot;&gt;erp&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.0525&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# equity risk premium
&lt;/span&gt;    &lt;span class=&quot;n&quot;&gt;lt_growth&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.0134&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# perpetual growth
&lt;/span&gt;    &lt;span class=&quot;n&quot;&gt;lt_roe&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.15&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# perpetual return on equity
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# Stable cash return fraction
&lt;/span&gt;    &lt;span class=&quot;n&quot;&gt;lt_f&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;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lt_growth&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lt_roe&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# Calculate discount factor 1/(1+i)
&lt;/span&gt;    &lt;span class=&quot;n&quot;&gt;discount_factor&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;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rf&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;erp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; 
    
    &lt;span class=&quot;n&quot;&gt;pv&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;earnings&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;base_earnings&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;n&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;p&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;mi&quot;&gt;6&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;n&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;n&quot;&gt;earnings&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;1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;drop&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;n&quot;&gt;earnings&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;1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;growth&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;ret_frac&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;f0&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;n&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;lt_f&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;f0&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;5&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;cash_flow&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;earnings&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ret_frac&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;pv&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cash_flow&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;discount_factor&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;**&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;n&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;tv&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;earnings&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;1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lt_growth&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;lt_f&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;rf&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;erp&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lt_growth&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;pv_terminal&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tv&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;discount_factor&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;**&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pv&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pv_terminal&lt;/span&gt;
    
&lt;span class=&quot;c1&quot;&gt;# Function to convert a recoupment fraction to growth rate
&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;recoup_to_growth&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;drop&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;g0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;0.0552&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;g&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;0.0336&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# confusingly, I have relabelled g0' as g0.
&lt;/span&gt;    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f&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;g0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;drop&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;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;drop&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;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;g&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;4&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;mf&quot;&gt;0.25&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;c1&quot;&gt;# Function to value the S&amp;amp;P given drop and recoupment fraction.
&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;valuation_recoup&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;drop&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;frac&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;kwargs&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;valuation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;drop&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;recoup_to_growth&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;frac&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;drop&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;kwargs&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;To generate the data used in the plots:&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;nn&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;n&quot;&gt;n_res&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;c1&quot;&gt;# resolution
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;recoup&lt;/span&gt; &lt;span class=&quot;o&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;linspace&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;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;n_res&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;drop&lt;/span&gt; &lt;span class=&quot;o&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;linspace&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;mf&quot;&gt;0.5&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;n&quot;&gt;n_res&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;Y&lt;/span&gt; &lt;span class=&quot;o&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;meshgrid&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;recoup&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;drop&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;Z&lt;/span&gt; &lt;span class=&quot;o&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;zeros&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;n_res&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;n_res&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;i&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;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;enumerate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;recoup&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;j&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;enumerate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;drop&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;Z&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;j&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;valuation_recoup&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;y&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;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The plots themselves were just made using matplotlib and the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mplot3d&lt;/code&gt; toolkit.&lt;/p&gt;

&lt;h2 id=&quot;results&quot;&gt;Results&lt;/h2&gt;

&lt;p&gt;Since we have two independent variables, we need three dimensions to visualise how they affect the price:&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/pvaluation/spy_earnings_contour3d.png&quot; style=&quot;width:80%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;While this visualisation is pretty cool, it’s not that useful. We can sacrifice some of the visual appeal for utility by projecting it down onto two dimensions (i.e a contour plot):&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/pvaluation/spy_earnings_fraction_contours.png&quot; style=&quot;width:100%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;This chart might look daunting, but I want to stress that it’s the same concept as the original straight-line we had for apples and bananas, except with more lines so that we can see the relationship for different prices (“total number of fruits in the basket”).&lt;/p&gt;

&lt;p&gt;There are several ways you can use this chart. Most obviously, you can use it to convert your estimates of the FY20 earnings drop and the fraction recouped into a price. For example, if you think that these values are -40% and 60% respectively, reading off the graph shows that the corresponding value of the S&amp;amp;P is \$2550.&lt;/p&gt;

&lt;p&gt;However, the point of this post is to see what the current price is implying. As of 16/4/20, the S&amp;amp;P index is trading at about \$2783. This number is pretty close to \$2750, so let’s consider that contour for now. Specifically, the contour describes all possible combinations of earnings-drop and fraction-recouped that result in a value of \$2750. If, for example, you thought that earnings would drop -33% (as Goldman Sachs recently suggested), then the price-implied recoupment fraction is something like 75%. If you think this value of 75% is optimistic (and that Goldman’s estimate is accurate), the logical trade would be to short the S&amp;amp;P index.&lt;/p&gt;

&lt;p&gt;You might be disappointed by the fact that there are only two levers you can play with (I personally think “the fewer the better”), but it turns out that these really are the most important variables. What about the cash return fraction? In the above plots, I have set it at 85%, the 10y average (much lower than the 92% returned in 2019). Let’s say you strongly disagree with this, in one of two ways:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;You think COVID-19 is going to be so terrifying that companies will reduce the return fraction to 2009-2010 levels of about 70%.&lt;/li&gt;
  &lt;li&gt;You think the Fed’s bazooka will encourage companies to revert to their 2019 cash return fraction of 92%.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The plot below shows how much these differing assumptions (red and green respectively) affect the price contours (hint: very little).&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/pvaluation/valuation_cash_returns.png&quot; style=&quot;width:100%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;For example, taking a look at the new contours next to the original \$2750 curve, we see that these two major changes to the return fraction have only moved the price by about \$30 either way.&lt;/p&gt;

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

&lt;p&gt;One of the underlying assumptions in this post is that markets are broadly efficient. Since the original work of Fama and French on the efficient market hypothesis (EMH) and the subsequent popularisation by books like &lt;em&gt;A Random Walk Down Wall Street&lt;/em&gt;, there has been an increasing rift between the traditional academics, who happily apply the EMH to many problems and claim grand results, and the practitioners, who vehemently reject the EMH. But as Andrew Lo puts it in his book, &lt;em&gt;Adaptive Markets&lt;/em&gt;, the truth is somewhere in between. The efficient market hypothesis is incorrect, but it is a very good first approximation. When market prices move, your first instinct should be that the price movement is justified by a change in expectations of future cash flows.&lt;/p&gt;

&lt;p&gt;All this isn’t to say that markets can’t be wrong. But in fact, that is somewhat the point of this post. Between 19 Feb and 22 Mar, the S&amp;amp;P500 index dropped by 33%. Was this an overreaction? Or was it justified by the expectations of greatly diminished future earnings? With the above plot, you can quantitatively understand the “fair” value of the index given a certain combination of fundamentals. This post is clearly not offering a “magic formula” that tells you if something is overvalued or undervalued – rather, it is a first step in building the toolkit that allows you to even approach such questions.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Rebuilding PyPortfolioOpt: an open source adventure</title>
   <link href="https://reasonabledeviations.com/2020/03/19/rebuilding-pyportfolioopt/"/>
   <updated>2020-03-19T00:00:00+00:00</updated>
   <id>https://reasonabledeviations.com/2020/03/19/rebuilding-pyportfolioopt</id>
   <content type="html">&lt;p&gt;A few weeks ago, a user raised an issue on the GitHub repository for &lt;a href=&quot;https://github.com/robertmartin8/PyPortfolioOpt&quot;&gt;PyPortfolioOpt&lt;/a&gt;, my open-source portfolio optimisation software library. In this nontechnical post, I discuss why a seemingly innocuous error resulted in a ground-up rebuild of a large chunk of PyPortfolioOpt, and share some reflections on open-source in general. &lt;br /&gt;
&lt;!--more--&gt;&lt;/p&gt;

&lt;p&gt;The actual bug that was reported is not particularly important, but it is quite easy to explain. PyPortfolioOpt offers a piece of functionality to construct a portfolio with minimum volatility (i.e minimal risk) – aptly called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;min_volatility&lt;/code&gt;. The problem was that &lt;em&gt;it wasn’t working&lt;/em&gt;. One of my users, going by the name edgetrader, had provided a sample dataset and some code, which he claimed was producing incorrect output. I hate to say it, but issues like these are often caused by the user not formatting the data correctly or using the wrong value for a parameter. However, edgetrader’s code looked fine, and running it on my machine did nothing but confirm the issue – &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;min_volatility&lt;/code&gt; was failing to produce the minimum volatility portfolio (for a painfully simple dataset too!).&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/rebuilding_os/pypfopt_bugreport.png&quot; style=&quot;width:80%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;I had a very good guess of where the error was coming from (later confirmed by another GitHub user, gatorliu). It was as bad as it could possibly be. To use an automotive analogy, rather than being a minor issue with an indicator light or windscreen wiper, the problem here was coming from deep within the engine. In PyPortfolioOpt, this corresponds to the &lt;em&gt;backend optimiser&lt;/em&gt; – the part of the package that does the mathematical legwork of actually crunching the numbers to optimise a portfolio.&lt;/p&gt;

&lt;h2 id=&quot;what-is-a-backend&quot;&gt;What is a backend?&lt;/h2&gt;

&lt;p&gt;The terms “frontend” and “backend” are often thrown around within the software engineering space, so let’s clarify exactly what we mean. In this context, I think of PyPortfolioOpt as being split into two broad chunks: the “frontend” API (application-programmer interface), i.e how users interact with the software, and the “backend”, which is the part that &lt;em&gt;actually&lt;/em&gt; solves the optimisation problem. In the car analogy, which we will continuously revisit, we might say that the frontend API is the steering wheel, gear stick, pedals, etc. (whatever the user is interacting with), while the backend is what goes on “under the hood” (engine, transmission, electronics).&lt;/p&gt;

&lt;p&gt;Now, as mentioned before, the “engine” of PyPortfolioOpt is the optimiser. Generally, optimisation is a very difficult mathematical task; many PhD-holders are paid a lot of money to build software routines for optimising different functions under different scenarios. For an open-source project (especially one not directly related to numerical computing), the norm is to pick from a handful of open-source optimisers, which, to be absolutely clear, have also been developed by extremely talented people. The problem is that it is rather difficult to decide on which one. So I put it to you – you have three choices:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;A general-purpose optimiser that can optimise for all different kinds of user objectives and constraints, with a very simple syntax.&lt;/li&gt;
  &lt;li&gt;An optimiser that is highly specialised for a certain type of optimisation problem (which includes financial portfolio optimisation), but is not very flexible to user constraints, and requires strong mathematical competence to interact with.&lt;/li&gt;
  &lt;li&gt;An optimiser that builds on top of choice #2, which provides a much nicer interface.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;What would you do? Bear in mind that it’s only really feasible to choose one and that it will take a large amount of development effort so the cost of swapping is very high. One further point is that although in theory, we like to treat the frontend API and backend as completely separate, your choice of backend will inevitably impact what the frontend looks like.&lt;/p&gt;

&lt;h2 id=&quot;technical-debt&quot;&gt;Technical debt&lt;/h2&gt;

&lt;p&gt;In March 2018, when I first started building PyPortfolioOpt, the choice seemed like a no-brainer. I chose the first option, which corresponds to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;scipy&lt;/code&gt;. A flexible optimiser that can work with all sorts of user objectives? Sounds fantastic! However, even then I knew that I was making a tradeoff; as I said, optimisation is a tough problem, so while option #1 in principle works with many different kinds of optimisation problems, it is actually less likely to produce the true optimal solution (it might, for example, get stuck in a “good” but suboptimal solution).&lt;/p&gt;

&lt;p&gt;Contrast this with #2, which is a package called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cvxopt&lt;/code&gt;. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cvxopt&lt;/code&gt; is an efficient solver for &lt;em&gt;convex&lt;/em&gt; optimisation problems (the definition of ‘convex’ doesn’t matter here; all you need to know is that it is a narrow subset of all possible problems). Luckily, portfolio optimisation &lt;em&gt;is&lt;/em&gt; a convex problem, at least with standard objectives and constraints, so it should produce much better solutions. With that in mind, why did I choose &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;scipy&lt;/code&gt;? Here are a couple of reasons:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;The syntax for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cvxopt&lt;/code&gt; was much harder to understand and required a greater level of mathematical sophistication (in March 2018 I hadn’t started university so I was somewhat constrained in that regard). Here’s a quick example to illustrate the difference between syntaxes – note how it is quite clear what everything is doing in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;scipy&lt;/code&gt;, whereas for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cvxopt&lt;/code&gt; you have to be quite familiar with the library and the mathematical formulation of the problem:&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;c1&quot;&gt;# Option 1: scipy
&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;scipy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;optimize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;minimize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
     &lt;span class=&quot;n&quot;&gt;fun&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;volatility_objective&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
     &lt;span class=&quot;n&quot;&gt;args&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;cov_matrix&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,)&lt;/span&gt;
     &lt;span class=&quot;n&quot;&gt;x0&lt;/span&gt;&lt;span class=&quot;o&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;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;n&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;n&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
     &lt;span class=&quot;n&quot;&gt;bounds&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;1&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;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
     &lt;span class=&quot;n&quot;&gt;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;s&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;eq&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;fun&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;lambda&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;w&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;nb&quot;&gt;sum&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;w&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;p&quot;&gt;)&lt;/span&gt;
 &lt;span class=&quot;n&quot;&gt;weight&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;x&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;

 &lt;span class=&quot;c1&quot;&gt;# Option 2: cvxopt
&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;G&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cvx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;matrix&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;n&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
 &lt;span class=&quot;n&quot;&gt;h&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cvx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;matrix&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;n&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;p&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;n&quot;&gt;A&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cvx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;matrix&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;1.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;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
 &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cvx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;matrix&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;1.0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
 &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cvx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;qp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mu&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cov_matrix&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;ret&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;G&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;h&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;A&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'x'&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;/li&gt;
  &lt;li&gt;I didn’t think the performance different would matter very much.&lt;/li&gt;
  &lt;li&gt;I was lazy.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I think you’d agree that the first two reasons seem fair; the third merits more of a discussion.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Technical debt&lt;/strong&gt; is a very important concept in software development, be it open-source or in business. It is the idea that by choosing “quick and easy” solutions that seem to meet your needs now, you incur a debt that will eventually need to be repaid once your product reaches a certain stage of growth. While it has negative connotations, it is not always a bad thing. In the same way that large companies might borrow money to invest in a new project, incurring technical debt can be a great way to get your software up and running. This is a fundamental tenet of the Lean Startup methodology, according to which the most common reason why startups (and open-source projects) fail is that they spend a lot of time architecting a perfect solution to a problem that nobody has. To that end, it is critical to get a “minimum viable product” out the door as soon as possible so you can start receiving user feedback and iterating. Perhaps this is my way of rationalising a lazy choice, but given my constraints, I think it was probably the right decision at the time.&lt;/p&gt;

&lt;p&gt;I released the first version of PyPortfolioOpt in May 2018 and received a strong positive response from the community, with surprising organic growth. Before long, it had a couple of hundred GitHub stars and surpassed most of the existing portfolio optimisation libraries. I believe this success was almost entirely due to the strong emphasis I placed on having an intuitive frontend API design and very detailed documentation, making it very easy for people to get up and running with the library. I did implement some semi-novel mathematics, for example, adding certain constraint functions to produce more diversified portfolios and creating new risk models, but this wouldn’t have mattered if the API were difficult to use.&lt;/p&gt;

&lt;p&gt;One thing I did not anticipate was an influx of &lt;em&gt;professional&lt;/em&gt; users, who were applying PyPortfolioOpt to real portfolio allocation problems. I had assumed that most professionals would use advanced in-house optimisation suites, but it turns out that there are many discretionary fund managers who were looking for a way to start systematising their allocation process and PyPortfolioOpt happened to come in at the right level of abstraction. For example, a typical workflow in PyPortfolioOpt is:&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;S&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;risk_models&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sample_covariance&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;rets&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;expected_returns&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mean_historical_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;ef&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;EfficientFrontier&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rets&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;S&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;weight_bounds&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;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;weights&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ef&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;min_volatility&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;This requires a rough knowledge of what is going on (which most investment professionals have) but abstracts away all of the mathematics.&lt;/p&gt;

&lt;p&gt;However, from the more quantitatively-inclined users (for example on the &lt;a href=&quot;https://www.reddit.com/r/algotrading/&quot;&gt;algotrading&lt;/a&gt; subreddit), I receive repeated (constructive) criticism on one area in particular – the use of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;scipy&lt;/code&gt; optimiser on the backend. Many people shared their negative experiences with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;scipy&lt;/code&gt; and suggested I migrate to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cvxopt&lt;/code&gt;. I took note of this feedback, but remained unconvinced that there was a significant performance difference, and decided to focus on building new features rather than working on the optimisation backend.&lt;/p&gt;

&lt;h2 id=&quot;the-tipping-point&quot;&gt;The tipping point&lt;/h2&gt;

&lt;p&gt;The issue that edgetrader raised was ultimately a strong impetus to completely re-evaluate my decision regarding the choice of the backend optimiser. Here I was, confronted with an astoundingly simple example (there were no weird constraints or objectives) for which the existing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;scipy&lt;/code&gt; backend was failing miserably. I could no longer credibly make the case that the worse performance was outweighed by the cleaner syntax, particularly with the thought that my library was being used to allocate &lt;em&gt;real&lt;/em&gt; money.&lt;/p&gt;

&lt;p&gt;I decided that it was time to “grow up”, and move on from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;scipy&lt;/code&gt;. But where would I go? I still disliked the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cvxopt&lt;/code&gt; syntax and didn’t feel it was suitably extensible to provide my users with enough options. Additionally, although I was willing to make a few breaking changes to the frontend API (i.e changes that aren’t backwards compatible), I wanted to minimise this when possible, particularly for the basic user.&lt;/p&gt;

&lt;p&gt;You may notice that after initially describing the three different options for a backend optimiser, I associated option #1 with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;scipy&lt;/code&gt; and option #2 with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cvxopt&lt;/code&gt;. What about the last option?&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;An optimiser that builds on top of choice #2 [&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cvxopt&lt;/code&gt;], which provides a much nicer interface.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I didn’t elaborate on it further because in March 2018 I didn’t think such a thing existed. However, I recently came across a library that got me truly excited – a viable alternative to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cvxopt&lt;/code&gt; that provided a fantastic interface which I could weave into PyPortfolioOpt. The library is called &lt;a href=&quot;https://www.cvxpy.org/&quot;&gt;cvxpy&lt;/a&gt;, a modelling language for convex optimisation problems courtesy of the clever folks at Stanford. There are two aspects of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cvxpy&lt;/code&gt; that instantly sold me. Firstly, the syntax is wonderfully expressive:&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;w&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Variable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;risk&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;quad_form&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;w&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cov_matrix&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;prob&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Problem&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;objective&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Minimize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;risk&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
                  &lt;span class=&quot;n&quot;&gt;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;n&quot;&gt;cp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sum&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;w&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;n&quot;&gt;w&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;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;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Secondly, while &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cvxpy&lt;/code&gt; is restricted to convex optimisation problems, it has an incredible type-checker that can actually explicitly verify if a problem is convex and &lt;strong&gt;noisily fail&lt;/strong&gt; if not. “Noisy failure” sounds terrible, but in fact, it is a gift for a developer – the reason being that if you &lt;em&gt;know&lt;/em&gt; there is an error, you can programmatically fix it or make suggestions to the user. By contrast, the bug that edgetrader raised was a &lt;strong&gt;silent failure&lt;/strong&gt;. The only way we found the bug was by manually checking the output.&lt;/p&gt;

&lt;h2 id=&quot;the-actual-migration&quot;&gt;The actual migration&lt;/h2&gt;

&lt;p&gt;Having decided to make the switch from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;scipy&lt;/code&gt; to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cvxpy&lt;/code&gt;, it was time to rearchitect the project. People unfamiliar with the industry jargon sometimes find “software architecture” to be a curious phrase. In this context, it just means thinking at a high level about how all of the parts will interact with each other before actually coding it up – in the same way that architects design buildings before actually laying down the bricks.&lt;/p&gt;

&lt;p&gt;I mentioned that I initially wanted to keep the changes entirely backwards compatible, so it would just be swapping out the engine while keeping the cockpit identical. However, when thinking about the architecture and reading &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cvxpy&lt;/code&gt;’s documentation, I realised that this would be incredibly limiting. One of my major goals with PyPortfolioOpt from day one was to have a highly &lt;strong&gt;modular&lt;/strong&gt; design – that is, all the optimisation objectives are kept in one place, risk models in another, optimisers again elsewhere. This makes the codebase much simpler to understand, test, and build on, but more importantly, it allows for advanced users to swap in certain parts of the pipeline according to their own expertise. For example, if you are an investor who has a great model for predicting the expected returns of stocks, you should be able to plug that into the PyPortfolioOpt pipeline with all the other steps taken care of. The thoughtful design of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cvxpy&lt;/code&gt; allowed PyPortfolioOpt to achieve the truly modular structure I always wanted. See if you can spot the lego bricks in the following example:&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;ef&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;EfficientFrontier&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;n&quot;&gt;cov_matrix&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;weight_bounds&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;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;ef&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;add_objective&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;transaction_cost&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;ef&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;add_constraint&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;lambda&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;weights&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;weights&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;=&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.05&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;weights&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ef&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;min_volatility&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;Ultimately I felt that this modularity was well worth making a breaking change to PyPortfolioOpt. A major consideration was that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cvxpy&lt;/code&gt; also provides a much better platform for future development effort. I suppose this was the foresight that I was lacking when I initially decided to use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;scipy&lt;/code&gt; on the backend.&lt;/p&gt;

&lt;p&gt;All told, the actual migration went rather well. It’s always quite stressful to make breaking changes to the core part of a library because you have to have a very good idea of the interdependence between different parts of a project – even though I am the sole author, this is quite a task! However, two things in particular were an absolute blessing, and I would strongly advise other open-source developers to take note.&lt;/p&gt;

&lt;p&gt;Firstly, documentation. The documentation is the “User Manual” which should detail how everything works. It may sound silly, but whenever a user asks me a question about PyPortfolioOpt, I normally don’t know the answer off the top of my head and end up relying heavily on the docs. “Self-documenting” is a popular myth; even in-line comments are not a suitable replacement for proper dedicated (separate) documentation. No user should have to dig through your source code to figure out how to use something.&lt;/p&gt;

&lt;p&gt;Secondly, at the risk of saying “eat your vegetables”, having a comprehensive suite of automated tests made things so much easier. I wrote tests for PyPortfolioOpt mostly because it was “suggested best practice”, but they are truly an incredible resource when it comes to software development. Since each piece of functionality is independently tested, I no longer have to maintain an exact mental map of all of the details of the package. If I make a change in one place that happens to affect another function, you can be sure the tests will greet me with an angry red cross. An especially useful practice has been building new tests after every bug fix – for example, in PyPortfolioOpt v1.0.0 the tests &lt;em&gt;include&lt;/em&gt; edgetrader’s bug, so we can both be sure it is well and truly resolved.&lt;/p&gt;

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

&lt;p&gt;One thing that I want readers to take from this post is that software design, as with many things in life, is a game of tradeoffs.&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/rebuilding_os/software_tradeoffs.png&quot; style=&quot;width:80%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;You have to make a multitude of decisions (some small, some critical) about the frontend API, the set of features, backend design, etc. Doing this correctly results in a package that people actually enjoy using, leading to organic growth by referrals. Otherwise, you end up putting a lot of effort into something that nobody will use.&lt;/p&gt;

&lt;p&gt;I realise that this post has made a lot of use of the ‘perpendicular pronoun’, whereas the truth is that I am indebted to the many contributors who have lightened my load by providing a steady flow of feedback, second opinions, feature suggestions, and code reviews. A special shoutout to &lt;a href=&quot;https://github.com/schneiderfelipe&quot;&gt;Felipe Schneider&lt;/a&gt;, who often responds to issues much faster than I and has been indispensable when it comes to bug/feature triage and architecture suggestions.&lt;/p&gt;

&lt;p&gt;PyPortfolioOpt is a living thing now, and we are well beyond the days when I can make whatever changes I like. PyPortfolioOpt is being used by real people to allocate real money, and to that end, I have a responsibility to develop responsibly, with a thought to both future progress as well as backwards compatibility. To my current users, I ask for your forgiveness. I can personally attest to the hassle of having to change your code because another library changed its interface. Please know that the change was made with performance and long-term usability in mind.&lt;/p&gt;

&lt;p&gt;Having released version 1.0.0, I won’t be making any breaking changes for the foreseeable future. It is my hope that the recent bout of “creative destruction” has laid the groundwork for PyPortfolioOpt to continue a steady and sustainable growth trajectory, and maintain its position as the “go-to” python portfolio optimisation library.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Black-Litterman allocation in algorithmic trading</title>
   <link href="https://reasonabledeviations.com/2020/01/04/black-litterman-algotrading/"/>
   <updated>2020-01-04T00:00:00+00:00</updated>
   <id>https://reasonabledeviations.com/2020/01/04/black-litterman-algotrading</id>
   <content type="html">&lt;p&gt;In December 2019, I released a major update to &lt;a href=&quot;https://github.com/robertmartin8/PyPortfolioOpt&quot;&gt;PyPortfolioOpt&lt;/a&gt;, my python portfolio optimisation package. The most significant addition was an implementation of the Black-Litterman (BL) method. Although BL optimisation is commonly used as part of a pipeline to optimise a multiasset/equity portfolio, in this post I argue that BL is particularly well suited to the problem of optimally weighting signals in an algorithmic trading context.
&lt;!--more--&gt;&lt;/p&gt;

&lt;h2 id=&quot;overview-of-the-black-litterman-model&quot;&gt;Overview of the Black-Litterman model&lt;/h2&gt;

&lt;p&gt;The “standard” way of doing portfolio optimisation is to start with a group of assets from which you’d like to make a portfolio, construct a vector of expected returns (usually by taking the mean historical returns of a group), then feed this vector along with the covariance matrix into a mean-variance optimiser (MVO). This sounds great in theory, except that the resulting portfolios tend to perform shockingly badly in practice, worse than if you were to just equally-weight the assets. As it happens, the major cause of problems is the use of mean historical returns to estimate future returns (see &lt;a href=&quot;/2018/09/27/lessons-portfolio-opt/&quot;&gt;this post&lt;/a&gt; for more). The Black-Litterman model attempts to improve on this by using a clever Bayesian scheme to construct the expected returns vector.&lt;/p&gt;

&lt;p&gt;BL allocation revolves around the concept of a “view”. A view is just a forecast you have of the future returns of an asset. It is different to the expected returns vector required by Efficient frontier optimisation in two key ways:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;You don’t need to specify views on all the assets. This is much more realistic, since you may only have an opinion on a handful of assets, while still wanting to construct a diverse portfolio.&lt;/li&gt;
  &lt;li&gt;BL lets you input &lt;strong&gt;view confidences&lt;/strong&gt;, which translate predictably into the resulting allocation. For example, for a positive view, a higher confidence (&lt;em&gt;ceteris parabus&lt;/em&gt;) will result in a greater percentage allocation to that stock (if this sounds trivially obvious, note that the same isn’t always true in efficient frontier optimisation due to instability).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Formally, BL is a Bayesian model which essentially provides a way of combining your views with some &lt;strong&gt;prior&lt;/strong&gt; estimate of returns (“prior” has a particular meaning in the context of Bayesian statistics). Part of the insight of Black and Litterman’s 1991 paper was that the current market-capitalisations of stocks can be used to construct a reasonable prior estimate of returns. For example, if company A has double the market cap of company B (assuming they have the same volatility), it can be argued that company A has double the expected return of company B. Maybe this isn’t a great estimate, but it doesn’t have to be – it is just a prior, which we will later update with our sophisticated views. The Black-Litterman expected returns are then a Bayesian weighted average of the prior and your views, with a weighting based on your confidence. That is all I want to say about the theory behind BL – the interested reader should refer to &lt;a href=&quot;https://pyportfolioopt.readthedocs.io/en/latest/BlackLitterman.html&quot;&gt;PyPortfolioOpt’s documentation&lt;/a&gt; and the links therein (for the more adventurous, I have presented the mathematical derivation &lt;a href=&quot;/assets/images/../notes/BlackLitterman.pdf&quot;&gt;here&lt;/a&gt;). This post will be kept relatively non-technical, or at least not explicitly mathematical, though it is clearly best supplemented with an understanding of the mathematics.&lt;/p&gt;

&lt;h2 id=&quot;black-litterman-in-algorithmic-trading&quot;&gt;Black-Litterman in algorithmic trading&lt;/h2&gt;

&lt;p&gt;Although BL is typically used to optimise multiasset or equity portfolios, during a quantitative research internship this past summer I became quite interested in the applications of Black-Litterman allocation to algo-trading. Having done a little bit more research, I found that BL provides a very natural way of optimally weighting &lt;em&gt;trading signals&lt;/em&gt;. A signal, in the most general sense, is a trigger for some marketplace action, usually based on the output of some model. For example, we might have multiple time series that we have found to be predictive of future price movements, or a machine learning model that predicts the performance of all the stocks in a universe.&lt;/p&gt;

&lt;p&gt;There is a subtle difference between these two examples. In the time series case, it is possible that we have multiple signals on &lt;em&gt;the same asset&lt;/em&gt;. It could be the case that we have found many factors that all seem to be predictive (to different degrees) of the price of the GBPUSD exchange rate – some technical factors (e.g. a moving average indicator, an oscillator) as well as some “fundamental” ones (e.g. yield curves, consumer spending). By contrast, in the machine learning recommender case we have a single model that makes forecasts on many assets.&lt;/p&gt;

&lt;p&gt;What is common across both of these scenarios is that we need a way of combining the individual signals into a resulting portfolio. As far as I can see, there are two obvious configurations in which we can use Black-Litterman to attack this problem, each one more suited to the respective scenario.&lt;/p&gt;

&lt;h3 id=&quot;method-1-treat-the-signals-as-assets&quot;&gt;Method 1: treat the signals as assets&lt;/h3&gt;

&lt;p&gt;The first method, which is more suitable for signals based on time series, is quite simple – we  model each signal as an asset with a certain expected return and volatility, then optimise a portfolio of these assets. In fact, this approach can be done without the Black-Litterman model, using standard mean-variance optimisation. We would just have to construct a covariance matrix of signal returns as well as an estimate for expected returns (most likely based on historical returns), before optimising some objective function. The covariance matrix is there to ensure that we consider how signals move together, cognisant that we don’t want to excessively allocate to signals that are moving the same way. That said, we have already seen that a pitfall of MVO is that it is highly sensitive to return estimates because it assumes 100% confidence. Black-Litterman doesn’t.&lt;/p&gt;

&lt;p&gt;A fair criticism of BL is that it is usually difficult to quantify the confidence in views – formally, BL requires the confidence to be specified as the variance of expected returns (this is different to the volatility of returns). There are some solutions to this, for example, &lt;a href=&quot;https://faculty.fuqua.duke.edu/~charvey/Teaching/BA453_2006/Idzorek_onBL.pdf&quot;&gt;Idzorek (2003)&lt;/a&gt; provides a method for mapping percentage confidence estimates to the required form of the BL input, though this is still rather unwieldy in practice. However, this is much less of a problem when it comes to algorithmic trading because we normally collect a wealth of data during backtesting regarding various performance metrics. In many cases, we are explicitly able to provide a confidence estimate, leading to a rigorous input for the BL model.&lt;/p&gt;

&lt;p&gt;One seemingly thorny issue is what we should use for a prior. The prior is meant to be the estimate of returns for each asset in the absence of any particular information. I would argue that the most appropriate prior for a time series signal is zero; in fact, this is perfectly acceptable since the BL formula still works with no prior. Some might say that this defeats the whole point of BL, but I would counter that in this case BL still provides a logical way of combining views that have different confidence levels.&lt;/p&gt;

&lt;h3 id=&quot;method-2-construct-a-portfolio-on-signal-recommendations&quot;&gt;Method 2: construct a portfolio on signal recommendations&lt;/h3&gt;

&lt;p&gt;Method 1 is best suited to constructing portfolios of signals where each signal pertains to a small basket of assets. For the second scenario we mentioned, in which we have a model that looks through an asset universe and identifies a subset that may outperform, the application of Black-Litterman is a lot more straightforward. We use the model’s estimate of returns along with its confidence (mapped to a variance) as inputs to the Black-Litterman formula and construct a portfolio.&lt;/p&gt;

&lt;p&gt;For this method, there is more choice for the prior. You can either use the relevant market prior for that universe (e.g. use the S&amp;amp;P500 if your universe is S&amp;amp;P500 equities) or go with no prior. The interesting thing about this is that BL is a fantastic way to mix the predictions of different models. There is nothing stopping you from combining multiple views (each with their own confidences) on the same asset – model A might think AAPL will return 20%, while model B thinks it will only return 2%. Who should you trust? A naive meta-model might just take the mean, but the BL machinery allows us to take a Bayesian weighted average, which incorporates your estimates of confidence.&lt;/p&gt;

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

&lt;p&gt;This post has shown how Black-Litterman allocation finds a natural application in algorithmic trading, since backtests give us information on both expected returns and the confidence in those estimates. One major downside with BL (which is also a downside for all kinds of mean-variance optimisation) is that it is naturally a &lt;strong&gt;single-period optimiser&lt;/strong&gt;, meaning that it doesn’t automatically adjust to new information. You will need to decide on some period over which views will be aggregated and return vectors constructed, then use BL to make an allocation. At the end of this period, you will have to repeat the process (including your new data) and rebalance positions. This introduces another degree of freedom into your resulting algo-trading pipeline, which is never a good thing. However, making the switch from single-period optimisation to &lt;strong&gt;multi-period optimisation&lt;/strong&gt; is a massive step up and requires significantly more theoretical machinery.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>An asymmetric bet on interest rates</title>
   <link href="https://reasonabledeviations.com/2019/12/05/asymmetric-bet-interest-rates/"/>
   <updated>2019-12-05T00:00:00+00:00</updated>
   <id>https://reasonabledeviations.com/2019/12/05/asymmetric-bet-interest-rates</id>
   <content type="html">&lt;p&gt;In a classic scene of &lt;em&gt;No Country For Old Men&lt;/em&gt;, Javier Bardem’s character ominously asks a shopkeeper: “what’s the most you ever lost on a coin toss?”. The shopkeeper says that he doesn’t know – this is probably quite a reasonable response given that for a fair coin, one has little reason to make a bet since your expected value (EV) is zero. Yet retail investors seem to make coin-toss bets all the time: they conclude that based on their analysis, a certain stock is a buy (i.e it has positive expected value). But they often forget to account for &lt;em&gt;model risk&lt;/em&gt; – the risk that their analysis itself is faulty.&lt;/p&gt;

&lt;p&gt;There is plenty of research to show that in actual fact, the buy/sell decisions of (retail) investors may as well be coin flips. Steve Cohen, the legendary hedge-fund manager, points out that his best portfolio managers get the direction of stock movement right only 56% of the time. However, the key to their success is the &lt;strong&gt;asymmetric payoff&lt;/strong&gt;. When they are right, they ride the stock up to significant gains, but when they are wrong they are quick to cut losses. Let’s assume that whenever they are right, the stock goes up 30%, but if the stock is going down they will cut their losses at 5%. The EV is then calculated as follows:&lt;/p&gt;

\[\text{EV} = 0.56 \times 30\% + (1-0.56) \times (-5\%) \approx 17\%\]

&lt;p&gt;Hence despite the “low accuracy”, the expected return on a single trade is an admirable 17%. We can use EV calculations to understand the profitability of all kinds of trading setups – some funds choose to make bets that have a less than 5% chance of being right, but will return 100x if they are. Other players, particularly market makers and option sellers, choose to make fractions of cents on every trade, but often have significant tail risk. In this post, I want to present an idea I had at the start of summer for an asymmetric bet on US interest rates.&lt;/p&gt;

&lt;h2 id=&quot;outline-of-the-pitch&quot;&gt;Outline of the pitch&lt;/h2&gt;

&lt;p&gt;It was July 2019: the height of the trade war, Brexit uncertainties alive as ever, people wondering if the decade-long bull market was going to come to an end (having forgotten about December 2018) – all eyes were on the Federal Reserve and it seemed that every day financial news sources had a different analysis of what would happen to interest rates. I was reading the &lt;em&gt;Financial Times&lt;/em&gt; when a statement popped up that really excited me:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;the market is pricing in an 100% probability of a rate cut of 25bps or more&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This statement provides an interesting opportunity to make a positive EV bet because of the asymmetry it presents. When the market is so confident that interest rates will be cut, you are getting excellent odds to make a bet on the opposite outcome (i.e rates held constant or increasing), even if you think that’s quite unlikely.&lt;/p&gt;

&lt;p&gt;Concretely, my proposal was to make a hawkish bet, i.e betting that interest rates will either stay the same or go up. Because the market thinks that there is an “100% chance” of a cut, if the rates are indeed cut, then I wouldn’t pay much for being wrong. However, on the off chance I’m right, my payoff could be large.&lt;/p&gt;

&lt;p&gt;In the rest of the post, we will discuss how to actually bet on interest rates, how the market-implied probability of a rate cut is calculated, and a post-mortem of my pitch.&lt;/p&gt;

&lt;h2 id=&quot;the-federal-reserve-and-interest-rates&quot;&gt;The Federal Reserve and interest rates&lt;/h2&gt;

&lt;p&gt;Banks in the US are legally required to keep some cash in the Federal Reserve.  Amounts in excess of this minimum reserve can be lent as an unsecured loan to other market participants; the interest rate on these loans is the &lt;strong&gt;Federal funds rate&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;These loans are negotiated by the borrowers and lenders (who are usually financial institutions). Why, then, do people always talk about the Fed hiking/cutting rates? The answer is that although the Federal Open Market Committee (FOMC) doesn’t set interest rates, it does set a “target rate”. This is more than just words, because the Fed can conduct &lt;strong&gt;open-market actions&lt;/strong&gt; to help move the overnight rate towards its target – this typically consists of either buying or selling government bonds. For example, when the Fed buys government bonds, there is more money in the system so people will pay less to borrow money (i.e interest rates go down).&lt;/p&gt;

&lt;h2 id=&quot;fed-funds-futures&quot;&gt;Fed funds futures&lt;/h2&gt;

&lt;p&gt;A forward contract is an agreement between a buyer and a seller to transact in a particular quantity of some item, at a specified date and price in the future. For example, a soybean farmer might want to lock-in a sale price for their soy beans before the harvest and might thus agree to sell 5,000 bushels exactly 6 months from now (May 2020), at a price of 8.85 per bushel. Come May 2020, the farmer delivers the soybeans to the buyer and is paid $\$8.85 \times 5000 = \$44,250$.&lt;/p&gt;

&lt;p&gt;A futures contract is essentially the same, except that the contracts are standardised, allowing them to be traded on an exchange. A good way of thinking about this is that there are pieces of paper going round saying “by holding this paper, I agree that on the 1st of May 2020, I will deliver 5000 of bushels of soybeans for the price of $8.85 per bushel” – these pieces of paper can then be bought and sold. Note that you don’t actually have to physically deliver the underlying asset (in most cases) – the usual practice is to settle in cash, for example by paying the deliveree the current market price of the asset.&lt;/p&gt;

&lt;p&gt;There is a lot more that could be said about futures, both on the theory side (no-arbitrage pricing) to the practicalities (margin, roll, and covering positions), but the key takeaway is that a futures contract is a way of speculating on the price of some underlying asset. This is a useful way of thinking about it, particularly in the context of underlying assets that are not physical commodities and hence cannot be delivered.&lt;/p&gt;

&lt;p&gt;In this vein, a Federal funds futures contract is a piece of paper saying that you will be paid an amount of money that depends on the Federal funds rate at a certain date in future. For example, let’s say that our simple conversion scheme is to take the Federal funds rate as a percentage, multiply it by \$100, and pay out that amount e.g. on the day that the contract expires, if the Fed funds rate is 2.1%, you will be paid \$210. How much would you pay for this piece of paper today? Clearly that depends on what you think is going to happen to interest rates. This is really the heart of a Fed funds futures contract – it’s a piece of paper whose value depends on the Federal funds rate at some date in the future.&lt;/p&gt;

&lt;p&gt;There is one minor annoyance, however. For historical reasons, futures brokers prefer standardised scales, so the Fed funds futures trade with the “IMM price”, i.e the price index of the contract is given by:&lt;/p&gt;

\[\text{Futures price} = 100 - \text{interest rate}\]

&lt;p&gt;Thus a value of 97.5 corresponds to a Fed funds rate of 2.5%. There is some additional nuance that I don’t particularly want to go into (for example, the interest rate used in the above calculation is actually the 30 day rolling mean of Fed funds rates), but any interested readers can have a look at the actual &lt;a href=&quot;https://www.cmegroup.com/trading/interest-rates/stir/30-day-federal-fund_contract_specifications.html&quot;&gt;contract specification&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This notwithstanding, the essential point is that Fed funds futures give us a direct way to speculate on US interest rates. In the above example, if we think the Fed is going to cut rates from 2.5% to 2.25%, we would go long a Fed funds future if it were trading below 97.75, because after the rate cut, the contract would settle at 97.75 (more than what we paid for it). Likewise if we have a hawkish view, we would short the Fed funds future.&lt;/p&gt;

&lt;h2 id=&quot;the-market-implied-probability-of-a-rate-cut&quot;&gt;The market-implied probability of a rate cut&lt;/h2&gt;

&lt;p&gt;Clearly, if the current interest rates are 2.5% and the Fed funds futures are priced at 98 (corresponding to a 2% rate), we know that the market thinks a rate cut will happen. But how was financial news quantifying the market-implied &lt;em&gt;probability&lt;/em&gt; of a rate cut?&lt;/p&gt;

&lt;p&gt;As it happens, the CME group runs a wonderful web app called the &lt;a href=&quot;https://www.cmegroup.com/trading/interest-rates/countdown-to-fomc.html&quot;&gt;&lt;em&gt;CME FedWatch Tool&lt;/em&gt;&lt;/a&gt;, which provides a whole host of analytics regarding FOMC meetings – including the probability of a certain rate movement. Their methodology is &lt;a href=&quot;https://www.cmegroup.com/education/demos-and-tutorials/fed-funds-futures-probability-tree-calculator.html&quot;&gt;detailed&lt;/a&gt; on the website, but a brief summary is as follows.&lt;/p&gt;

&lt;p&gt;The first thing to note is that Fed Funds futures contracts can be bought with expiries on calendar months up to about 3 years from now. That sentence was terribly worded, but I really can’t think of a better phrasing, so I’m just going to use a picture:&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/fedfunds/fed_funds_contracts.png&quot; style=&quot;width:80%;&quot; /&gt;
&lt;figcaption&gt;Data from the CME group&lt;/figcaption&gt;
&lt;/center&gt;

&lt;p&gt;This is incredibly useful because it allows us to isolate the effect of a given month’s FOMC meeting. For example, if there is a meeting on 31 July, we need only consider the contract expiring 1 July and the contract expiring 1 August to understand the market’s view of the meeting. Let’s go through a very simple example of a probability calculation to estimate the probability of a 25bps cut (using made-up data):&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;Fed funds future (exp 1 Jul) = 97.60
Fed funds future (exp 1 Aug) = 97.70

implied rate (1 Jul) = 100 - 97.60 = 2.40
implied rate (1 Aug) = 100 - 97.70 = 2.30

P(25bps cut) = (2.40 - 2.30) / 0.25 = 40%
P(no cut) = 1 - P(cut) = 60%
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The only mysterious step is possibly the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;P(cut)&lt;/code&gt; step, but this can be easily reasoned out. If there is a 25bps rate cut, the Fed-funds-implied rate will go from 2.40 to 2.15 by expiry. But the market has priced this at 2.30 instead. How far “along” is 2.30 between 2.40 and 2.15? Ten twenty-fifths of the distance. This must be the market-implied probability.&lt;/p&gt;

&lt;p&gt;Of course, there is added complexity in that the above analysis has only dealt with a very narrow question: what is the probability of a 25bps cut vs no cut. In reality, the Fed can leave rates the same, or cut/raise them by any multiple of 25bps. The full calculation is essentially the same as the above, just with a bit of mathematical trickery to adjust for the different branches.&lt;/p&gt;

&lt;p&gt;Now that we understand how these probabilities are calculated, we can look at the data. This chart shows all of the market-implied probabilities leading up to the 31/7/2019 FOMC meeting. For context, I was pitching this idea at the start of July 2019 and the Fed’s target rate was 225-250bps:&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/fedfunds/cme_fed_prob.png&quot; style=&quot;width:100%;&quot; /&gt;
&lt;figcaption&gt;Chart from the CME FedWatch Tool&lt;/figcaption&gt;
&lt;/center&gt;

&lt;p&gt;We can see that by mid-June, the probability that rates would be held at 225-250bps went to zero (the nosedive of the purple line). This means that Fed funds futures were priced such that there was an 100% chance of a rate cut.&lt;/p&gt;

&lt;h2 id=&quot;my-estimate-of-the-probability-of-a-rate-cut&quot;&gt;My estimate of the probability of a rate cut&lt;/h2&gt;

&lt;p&gt;Having seen what the market expects, we can now begin to think about some reasons that the market could be wrong. I think there are three plausible reasons:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;In July 2019, the US economy was chugging along perfectly well. In June, &lt;a href=&quot;https://www.cnbc.com/2019/07/05/jobs-report-june-2019.html&quot;&gt;nonfarm payrolls&lt;/a&gt; rose 224,000 (well above market expectations); the nonfarm payroll numbers are one of the many key job statistics that people keep an eye on because they give a great look into the health of the US economy.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Related to the above, a major component of the Fed’s mandate is to keep inflation at around 2%. When the economy is “heating up” too much (with high inflation), the Fed may want to raise rates to cool things down. Likewise, if the economy is cooling down, the Fed may want to lower the target rate to encourage people to take money out of their bank accounts and spend! Between January and June 2019, US inflation had steadily climbed from around 1.5% to 2.0%, in absence of any open-market action from the Fed. Thus it could be argued that there is no reason for them to cut rates in the July FOMC meeting.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;This is a “softer point”, but one thing I have always found interesting is Jerome Powell’s levelheadedness in the face of Donald Trump, who has issued many scathing comments (over Twitter, no less) of the Fed for not being looser with monetary policy. It is quite possible that Powell would want to remain defiant (and assert the Fed’s independence) by not “giving in” to Trump’s demands.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/fedfunds/trump_tweet.png&quot; style=&quot;width:80%;&quot; /&gt;
&lt;figcaption&gt;Very classy.&lt;/figcaption&gt;
&lt;/center&gt;

&lt;p&gt;I haven’t gone into too much detail for the above points because &lt;em&gt;it doesn’t really matter&lt;/em&gt;. The fundamental argument in the post is that as long as there exists a nonzero probability of a rate-hold (or rate hike), we are able to make a positive EV bet. To elucidate this, let’s say that based on my analysis above I’m willing to say that there’s just a 2% chance of a rate cut.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;&lt;strong&gt;Interest rate&lt;/strong&gt;&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;&lt;strong&gt;Market-implied probability&lt;/strong&gt;&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;&lt;strong&gt;My estimated probability&lt;/strong&gt;&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;225-250 (hold)&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;0%&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;2%&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;200-225 (25bps cut)&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;97%&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;96%&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;175-200 (50bps cut)&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;3%&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;2%&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;Let’s assume that we are dealing with a contract that expires right after the FOMC meeting. Given the market-implied probabilities, we can estimate the price of the future’s contract let’s say the Fed funds futures contract (which expires right after the FOMC meeting) is trading at 97.883 – a number I calculated as below:&lt;/p&gt;

\[\text{market price} = 100 - (0.97 \times 212.5 \text{bps} + 0.03 \times 187.5 \text{bps}) = 97.883\]

&lt;p&gt;Using my estimated odds (different from the market’s!), the fair price of the Fed funds future (ignoring pedantry regarding the time-value of money) is:&lt;/p&gt;

\[\text{fair price} = 100 - (0.02 \times 237.5\text{bps} + 0.96 \times 212.5 \text{bps} + 0.02 \times 187.5 \text{bps}) = 97.875\]

&lt;p&gt;Hence my hawkish bet (shorting the futures contract) would result in a profit of about \$33 per contract (according to the contract spec, each contract settles for \$4167 times the price index). You might think that 0.08% profit sounds paltry, but it should be remembered that the beauty of futures is &lt;em&gt;leverage&lt;/em&gt;. For this particular contract, you would only have to put up about \$400 of maintenance margin, so the actual percentage profit would be about 8%. This isn’t bad at all, considering the time horizon of this bet is a few weeks.&lt;/p&gt;

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

&lt;p&gt;Unsurprisingly, the Fed decided to cut rates by 25bps, citing weak global growth and trade tensions. However, I don’t think this pitch was incorrect. The point is not that I thought rates would be held steady, but that I was willing to assign this outcome a higher probability than the market was.&lt;/p&gt;

&lt;p&gt;I ended up pitching this idea, together with three other students, to a panel of managing directors at J.P. Morgan as part of an industry insight event. They were sceptical initially, but were quite intrigued once I had made it sufficiently clear that this was really a play on market expectations rather than interest rates directly. On a side note, I knew that a successful pitch here could result in a front-office summer internship offer at JPM so perhaps there was another dimension to this asymmetric bet…&lt;/p&gt;

&lt;p&gt;If there’s anything you should take away from this post, it is this: I am certainly not claiming to know more than economists about what the Fed is going to do. What I &lt;em&gt;am&lt;/em&gt; saying is that whenever the market is so sure that something is going to happen (or never going to happen!), there could be a good opportunity for a highly asymmetric bet.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>How predictive is the historical volatility?</title>
   <link href="https://reasonabledeviations.com/2019/11/01/how-predictive-is-historical-volatility/"/>
   <updated>2019-11-01T00:00:00+00:00</updated>
   <id>https://reasonabledeviations.com/2019/11/01/how-predictive-is-historical-volatility</id>
   <content type="html">&lt;p&gt;One of the things that makes markets exciting (or frightening) is that prices move around a lot. It is important to be able to describe and predict the range of possible price movements over a given time horizon since some investors might desire assets whose prices don’t move up and down too much. We can quantify this by computing the &lt;strong&gt;volatility&lt;/strong&gt;, which is commonly defined to be the standard deviation of the asset’s (log) returns. This post examines how well we can predict future volatility and why that matters.&lt;!--more--&gt;&lt;/p&gt;

&lt;p&gt;Volatility is a rather important quantity because it is the default way for investors to quickly assess the amount of risk that a stock might add to the portfolio. It also shows up in many different parts of finance, from the traditional Capital Asset Pricing Model used in investment banking to option-pricing. One of the most tangible applications, however, is in portfolio optimisation, which attempts to constructs portfolios that meet certain investor objectives, for example, finding the that minimises the total volatility. The general procedure for doing this is to estimate the future returns and covariance of a group of stocks (normally basing these estimates on historical values) then running an optimisation procedure – I have written about this extensively in the &lt;a href=&quot;https://pyportfolioopt.readthedocs.io/en/latest/&quot;&gt;documentation&lt;/a&gt; for my portfolio optimisation software library.&lt;/p&gt;

&lt;p&gt;However, the key to this procedure is that we are assuming that the past is a reasonable predictor of the future. A large amount of research has shown that historical returns are a very poor predictor of future returns, but I haven’t seen many comments on whether historical volatility is a good predictor of future volatility. This post is a brief investigation into the matter.&lt;/p&gt;

&lt;h2 id=&quot;methodology-and-hypothesis&quot;&gt;Methodology and hypothesis&lt;/h2&gt;

&lt;p&gt;We are going to keep this post extremely simple and examine the predictability of the S&amp;amp;P500’s volatility. Firstly, we will collect adjusted closing prices for the S&amp;amp;P500 over a large timeframe. We will then form 1y, 3y, 5y, 10y, 20y rolling windows and compute the rolling volatility for each. These will then be correlated with the realised volatility.&lt;/p&gt;

&lt;p&gt;The reason for this is that I am quite interested to know how much data is required to make a better estimate of future volatility. My hypothesis is that there will be some intermediate optimum – too little data and you can’t make a good estimate, but too much data dilutes recent datapoints with ancient ones (which may be less relevant).&lt;/p&gt;

&lt;h2 id=&quot;preparing-data&quot;&gt;Preparing data&lt;/h2&gt;

&lt;p&gt;We start by using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pandas_datareader&lt;/code&gt; to acquire some free price data from Yahoo Finance. Remarkably, we can get daily OHLCV (open, high, low, close, volume) data all the way back to 1927. I am of the opinion that it is good practice to save whatever data you download to the disk, in case you need it later.&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;nn&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;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;pandas_datareader&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;web&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;sp500_df&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;web&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;DataReader&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;^GSPC&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;yahoo&quot;&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;p&quot;&gt;.&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;mi&quot;&gt;1950&lt;/span&gt;&lt;span class=&quot;p&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;mi&quot;&gt;1&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;p&quot;&gt;.&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;mi&quot;&gt;2019&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;12&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;19&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;sp500_df&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;to_csv&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;data/sp500_prices.csv&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;p&gt;We can then read the data back in and clean it up. In particular, for this exploration we only need the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Adj Close&lt;/code&gt; column, which contains the daily close prices adjusted for dividends and stock splits.&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;sp500_df&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;n&quot;&gt;read_csv&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;data/sp500_prices.csv&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;parse_dates&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;px&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sp500_df&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;set_index&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Date&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Adj Close&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;p&gt;It is straightforward to compute returns thanks to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pct_change()&lt;/code&gt; methods, which calculates the percentage change between dataframe rows.&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;rets&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;px&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pct_change&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dropna&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;rets&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;head&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-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Date
1950-01-04    0.011405
1950-01-05    0.004748
1950-01-06    0.002953
1950-01-09    0.005889
1950-01-10   -0.002927
Name: Adj Close, dtype: float64
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Because the price data excludes weekends and holidays, a trading year is actually only 252 days. To calculate the rolling volatility for a given window, we can just rely on the magic of pandas:&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;nn&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;n&quot;&gt;rolling_vol&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rets&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rolling&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;time_period&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;n_days&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;std&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;np&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sqrt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;252&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 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;np.sqrt(252)&lt;/code&gt; arises because volatilities are most commonly expressed on annual terms, whereas the method calculates a daily volatility. Combining all of this into a loop so that we can easily repeat the calculation for multiple time periods:&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;n_days&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;c1&quot;&gt;# trading days in a year
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;time_periods&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;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&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;c1&quot;&gt;# in years
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;stds&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;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;t&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;time_periods&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;stds&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rets&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rolling&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;t&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;n_days&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;std_df&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;n&quot;&gt;concat&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;stds&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;n&quot;&gt;std_df&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;sa&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;y&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;t&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;time_periods&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;std_df&lt;/span&gt; &lt;span class=&quot;o&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;sqrt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;252&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# annualise
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The result of this is a dataframe where each column represents a different window length.&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;Date		1y	        ...     20y			
2019-12-13	0.143483	...     0.188674
2019-12-16	0.142234	...     0.188642
2019-12-17	0.140569	...     0.188635
2019-12-18	0.140573	...     0.188622
2019-12-19	0.139656	...     0.188622
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Having now computed our historical volatility estimates, we need to calculate the realised volatilities (this is what we are trying to predict). This can be done with a nice pandas trick, using the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;shift()&lt;/code&gt; method to “look into the future”:&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;c1&quot;&gt;# Compute realised annual volatilities
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;realised_vol&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rets&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rolling&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;n_days&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;shift&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;n_days&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;np&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sqrt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;252&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;realised_vol&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;realised_vol&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rename&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;realised_vol&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;realised_vol&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;head&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-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Date
1950-01-04    0.148951
1950-01-05    0.148979
1950-01-06    0.149594
1950-01-09    0.150306
1950-01-10    0.150330
Name: realised_vol, dtype: float64
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Having collected and processed the data, we are now ready to conduct an analysis.&lt;/p&gt;

&lt;h2 id=&quot;analysis&quot;&gt;Analysis&lt;/h2&gt;

&lt;p&gt;Firstly, let’s plot all of the rolling volatilities that we calculated.&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/histvol/historical_vol_all_std.png&quot; style=&quot;width:100%;&quot; /&gt;
&lt;figcaption&gt;The historical volatility of the S&amp;amp;P500 using different rolling windows&lt;/figcaption&gt;
&lt;/center&gt;

&lt;p&gt;Clearly, the wider the rolling window, the more stable our estimate of volatility. This is reflected in the fact that the yellow line moves around much less than the blue line, which exhibits many spikes. On the subject of spikes, it is interesting to note that the spikes seem to correspond to market crises: 2008 MBS, 2002-03 DotCom, 1987 Black Monday, 1973-74 oil crisis, and the 1962 Kennedy Slide (along with many other smaller ones). I find it equally interesting that most of the volatility spikes are extremely short-lived, which suggests to me that markets don’t take too long to re-price assets following a major shift.&lt;/p&gt;

&lt;p&gt;But that is almost a tangent – the main point of this post is to understand how the historical volatility correlates with future realised volatility. The correlation coefficients are 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;n&quot;&gt;std_df&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;corrwith&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;realised_vol&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-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;1y     0.429631
3y     0.286037
5y     0.176046
10y    0.179328
20y    0.058464
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;These results are quite shocking. Firstly, I find the overall trend to be surprising, as I expected either the 3y or 5y to strike the right balance between recency and stability. It is quite surprising that using only the last year’s volatility to estimate future volatility has the most predictive power, but there you go. Secondly, the actual values are quite low. To put it differently, even for the 1y data, historical volatility only explains 18% ($0.4296^2$) of the variance in the realised volatility.&lt;/p&gt;

&lt;p&gt;However, the explained variance is often not as intuitive as you might expect. I believe that a more tangible number is the Mean Absolute Error (MAE), which tells us how far away our prediction is from the realised volatility, on average.&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;mae&lt;/span&gt; &lt;span class=&quot;o&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;nb&quot;&gt;abs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;std_df&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;subtract&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;realised_vol&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;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)).&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mean&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-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;1y     0.043003
3y     0.048608
5y     0.050167
10y    0.050909
20y    0.049677
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;For the 1y data, historical volatility had a MAE of about 4.3%, which is quite large! However, looking through the data I saw that it was a few far-off predictions that caused such awful results. This is corroborated by a histogram of the realised volatilities, for which we can observe a fat right tail:&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/histvol/realised_vol_hist.png&quot; style=&quot;width:100%;&quot; /&gt;
&lt;figcaption&gt;The distribution of realised volatilities for the SPY benchmark using data from 1950-2019&lt;/figcaption&gt;
&lt;/center&gt;

&lt;p&gt;All of these findings notwithstanding, it is reassuring to see that the errors between historical and realised volatilities are almost centred around zero:&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;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;std_df&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;1y&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;n&quot;&gt;realised_vol&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;hist&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bins&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;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/histvol/vol_errors_hist.png&quot; style=&quot;width:100%;&quot; /&gt;
&lt;figcaption&gt;The error of the 1y historical volatility predictions&lt;/figcaption&gt;
&lt;/center&gt;

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

&lt;p&gt;In conclusion, we see that historical volatility is far from a perfect forecaster of future volatility, explaining less than 20% of the variance in realised volatilities in the best case (out of our experiments). It is not entirely surprising that this is the case, given that volatility forecasting is a very heavily-researched topic, since a better estimate of the volatility gives you a competitive advantage when it comes to pricing options or trading volatility instruments like the VIX. There are numerous sophisticated econometric models for predicting future volatility, not least the GARCH family, so it is naive to assume that simply using the historical volatility would give good results.&lt;/p&gt;

&lt;p&gt;When it comes to the topic of portfolio optimisation, these findings should be a real wake-up call that you need to consider the uncertainties in both your return estimates and volatility/covariance estimates. It is true that the mean-variance machinery is not able to take this into account – all the more reason to swap to a confidence-aware Bayesian method such as &lt;a href=&quot;https://pyportfolioopt.readthedocs.io/en/latest/BlackLitterman.html&quot;&gt;Black-Litterman optimisation&lt;/a&gt;. However, even then, it is not straightforward to understand the potential effects of the skewed distribution of realised volatilities.&lt;/p&gt;

&lt;p&gt;It should be noted that not everyone agrees that the volatility is a number worth paying attention to in the first place. A famous critic is Warren Buffet, who rightly points out that a good company whose stock price suddenly drops due to temporary factors (e.g. a media scandal) could be a bargain purchase – in which case it is actually &lt;em&gt;less&lt;/em&gt; risky than it was before, although its volatility will be higher as a result of the drop. Perhaps this post lends some evidence to this point of view. Given the distribution of realised volatilities, it seems that the converse may also be true: even if everything looks calm and you predict low volatility, a right-tail event could make things go horribly wrong. As is always the case in finance, caveat emptor applies!&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The full Jupyter notebook can be found on my &lt;a href=&quot;https://github.com/robertmartin8/RandomWalks/blob/master/VolatilityBacktesting.ipynb&quot;&gt;GitHub&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>Implementing k-means clustering from scratch in C++</title>
   <link href="https://reasonabledeviations.com/2019/10/02/k-means-in-cpp/"/>
   <updated>2019-10-02T00:00:00+00:00</updated>
   <id>https://reasonabledeviations.com/2019/10/02/k-means-in-cpp</id>
   <content type="html">&lt;p&gt;I have a somewhat complicated history when it comes to C++. When I was 15 and teaching myself to code, I couldn’t decide between python and C++ and as a result tried to learn both at the same time. One of my first non-trivial projects was a C++ program to compute orbits – looking back on it now, I can see that what I was actually doing was a (horrifically inefficient) implementation of Euler’s method. I just couldn’t wrap my head around fixed-size arrays (not to mention pointers!). In any case, I soon realised that juggling C++ and python was untenable – not only was I new to the concepts (such as type systems and OOP), I was having to learn two sets of syntax in addition to two flavours of these concepts. I decided to commit to python and haven’t really looked back since.&lt;/p&gt;

&lt;p&gt;Now, almost 6 years later (tempus fugit!), having completed the first-year computer science course at Cambridge, I feel like I am in a much better place to have a proper go at C++. My motivation is helped by the fact that all of the second-year computational practicals for physics are done in C++, not to mention that C++ is incredibly useful in quantitative finance (which I am deeply interested in).&lt;/p&gt;

&lt;p&gt;To that end, I decided to jump straight in and implement a machine learning algorithm from scratch. I chose &lt;em&gt;k&lt;/em&gt;-means because of its personal significance to me: when I was first learning about ML, &lt;em&gt;k&lt;/em&gt;-means was one of the first algorithms that I fully grokked  and I spent quite a while experimenting with different modifications and implementations in python. Also, given that the main focus of this post is to learn C++, it makes sense to use an algorithm I understand relatively well.&lt;/p&gt;

&lt;p&gt;Please let me add the disclaimer that this is certainly not going to be an optimal solution – this post is very much a learning exercise for me and I’d be more than happy to receive constructive criticism. As always, all code for this project can be found on &lt;a href=&quot;https://github.com/robertmartin8/RandomWalks/blob/master/kmeans.cpp&quot;&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;contents&quot;&gt;Contents&lt;/h2&gt;
&lt;!-- TOC --&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;#what-is-k-means-clustering&quot;&gt;What is k-means clustering?&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#the-k-means-algorithm&quot;&gt;The k-means algorithm&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#implementation&quot;&gt;Implementation&lt;/a&gt;
    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#c-preambles&quot;&gt;C++ preambles&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#representing-a-datapoint&quot;&gt;Representing a datapoint&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#reading-in-data-from-a-file&quot;&gt;Reading in data from a file&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#pointers-an-old-enemy-revisited&quot;&gt;Pointers: an old enemy revisited&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#initialising-the-clusters&quot;&gt;Initialising the clusters&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#assigning-points-to-a-cluster&quot;&gt;Assigning points to a cluster&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#computing-new-centroids&quot;&gt;Computing new centroids&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#writing-to-a-file&quot;&gt;Writing to a file&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#testing&quot;&gt;Testing&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#conclusion&quot;&gt;Conclusion&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;!-- /TOC --&gt;

&lt;h2 id=&quot;what-is-k-means-clustering&quot;&gt;What is k-means clustering?&lt;/h2&gt;

&lt;p&gt;I have decided to give four brief explanations with increasing degrees of rigour. Nothing beyond the first explanation is really essential for the rest of this post, so feel free to stop whenever.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;em&gt;k&lt;/em&gt;-means clustering allows us to find groups of similar points within a dataset.&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;k&lt;/em&gt;-means clustering is the task of finding groups of points in a dataset such that the total variance within groups is minimised.&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;k&lt;/em&gt;-means clustering is the task of partitioning feature space into &lt;em&gt;k&lt;/em&gt; subsets to minimise the within-cluster sum-of-square deviations (WCSS), which is the sum of quare euclidean distances between each datapoint and the centroid.&lt;/li&gt;
  &lt;li&gt;Formally, &lt;em&gt;k&lt;/em&gt;-means clustering is the task of finding a partition $S = \{S_1, S_2, \ldots S_k\}$ where $S$ satisfies:&lt;/li&gt;
&lt;/ol&gt;

\[\arg \min \sum_{i=1}^k \sum_{x \in S_i} \lVert{x - \mu_i}\rVert^2\]

&lt;h2 id=&quot;the-k-means-algorithm&quot;&gt;The k-means algorithm&lt;/h2&gt;

&lt;p&gt;The &lt;em&gt;k&lt;/em&gt;-means clustering problem is actually incredibly difficult to solve. Let’s say we just have $N=120$ and $k=5$, i.e we have 120 datapoints which we want to group into 5 clusters. The number of possible partitions is more than the number of atoms in the universe ($5^{120} \approx 10^{83}$) – for each one, we then need to calculate the WCSS (read: variance) and choose the best partition.&lt;/p&gt;

&lt;p&gt;Clearly, any kind of brute force solutions is intractable (to be specific, the problem has exponential complexity). Hence, we need to turn to approximate solutions. The most famous approximate algorithm is &lt;strong&gt;Lloyd’s algorithm&lt;/strong&gt;, which is often confusingly called the “&lt;em&gt;k&lt;/em&gt;-means algorithm”. In this post I will silence my inner pedant and interchangeably use the terms &lt;em&gt;k&lt;/em&gt;-means algorithm and &lt;em&gt;k&lt;/em&gt;-means clustering, but it should be remembered that they are slightly distinct. With that aside, Lloyd’s algorithm is incredibly simple:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Initialise the clusters&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The algorithm needs to start somewhere, so we need to come up with a crude way of clustering points. To do this, we randomly select &lt;em&gt;k&lt;/em&gt; points which become ‘markers’, then assign each datapoint to its nearest marker point. The result of this is &lt;em&gt;k&lt;/em&gt; clusters. While this is a naive initialisation method, it does have some nice properties - more densely populated regions are more likely to contain centroids (which makes logical sense).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Compute the centroid of each cluster&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Technically Lloyd’s algorithm computes the centroid of each partition of 3D space via integration, but we use the reasonable approximation of computing the centre of mass of the points in a given partition. The rational behind this is that the centroid of a cluster ‘characterises’ the cluster in some sense.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Assign each point to the nearest centroid and redefine the cluster&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If a point currently in cluster 1 is actually closer to the centroid of cluster 2, surely it makes more sense for it to belong to cluster 2? This is exactly what we do, looping over all points and assigning them to clusters based on which centroid is the closest.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Repeat steps 2 and 3&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We then repeatedly recompute centroids and reassign points to the nearest centroid. There is actually a very neat proof that this converges: essentially, there is only a finite (though massive) number of possible partitions, and each &lt;em&gt;k&lt;/em&gt;-means update at least improves the WCSS. Hence the algorithm must converge.&lt;/p&gt;

&lt;h2 id=&quot;implementation&quot;&gt;Implementation&lt;/h2&gt;

&lt;p&gt;Our goal today is to implement a C++ version of the &lt;em&gt;k&lt;/em&gt;-means algorithm that successfully clusters a two-dimensional subset of the famous mall customers dataset (available &lt;a href=&quot;https://github.com/robertmartin8/udemyML/blob/master/06_clustering/Mall_Customers.csv&quot;&gt;here&lt;/a&gt;). It should be noted that the &lt;em&gt;k&lt;/em&gt;-means algorithm certainly works in more than two dimensions (the Euclidean distance metric easily generalises to higher dimensional space), but for the purposes of visualisation, this post will only implement &lt;em&gt;k&lt;/em&gt;-means to cluster 2D data. A plot of the raw data is shown below:&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/kmeans/kmeans-unclustered.png&quot; style=&quot;width:80%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;By eye, it seems that there are five different clusters. The question is whether our &lt;em&gt;k&lt;/em&gt;-means algorithm can successfully figure this out. We are actually going to cheat a little bit and tell the algorithm that there will be five clusters (i.e $k=5$). There are methods to avoid this, but they essentially involve testing different values of &lt;em&gt;k&lt;/em&gt; and finding the best fit, so they don’t add much value to this post.&lt;/p&gt;

&lt;h3 id=&quot;c-preambles&quot;&gt;C++ preambles&lt;/h3&gt;

&lt;p&gt;Firstly, we need to define our imports and namespace.&lt;/p&gt;

&lt;div class=&quot;language-csharp 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;err&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;include&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ctime&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;     &lt;span class=&quot;c1&quot;&gt;// for a random seed&lt;/span&gt;
&lt;span class=&quot;err&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;include&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fstream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;   &lt;span class=&quot;c1&quot;&gt;// for file-reading&lt;/span&gt;
&lt;span class=&quot;err&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;include&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;iostream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;// for file-reading&lt;/span&gt;
&lt;span class=&quot;err&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;include&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sstream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;   &lt;span class=&quot;c1&quot;&gt;// for file-reading&lt;/span&gt;
&lt;span class=&quot;err&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;include&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;vector&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;namespace&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;std&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;In general, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;using namespace std&lt;/code&gt; is not considered best practice (particularly in larger projects) because it can lead to ambiguity (for example, if I define a function or variable called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vector&lt;/code&gt;) and unexpected behaviour. However, the alternative is to have things like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;std::cout&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vector::vector&lt;/code&gt; everywhere – for an educational post, the loss in clarity is worse than the potential ambiguity.&lt;/p&gt;

&lt;h3 id=&quot;representing-a-datapoint&quot;&gt;Representing a datapoint&lt;/h3&gt;

&lt;p&gt;To represent a datapoint for this program, we will be using a C++ &lt;strong&gt;struct&lt;/strong&gt;. Structs caused me a great deal of confusion when I was learning about C++ because I couldn’t quite figure out how they differ from classes. As it happens, they are really quite similar – possibly the only relevant difference is that members of a struct are public by default. In any case, I would think of a struct as a way of defining a more complicated data type, though it is more than just a container for primitive datatypes because you can also define some functionality.&lt;/p&gt;

&lt;div class=&quot;language-cpp 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;struct&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Point&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;double&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;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;     &lt;span class=&quot;c1&quot;&gt;// coordinates&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cluster&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;     &lt;span class=&quot;c1&quot;&gt;// no default cluster&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;double&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;minDist&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;// default infinite dist to nearest cluster&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;Point&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;x&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;n&quot;&gt;y&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;n&quot;&gt;cluster&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;n&quot;&gt;minDist&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;__DBL_MAX__&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;Point&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;double&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;kt&quot;&gt;double&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y&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;x&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;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;cluster&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;n&quot;&gt;minDist&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;__DBL_MAX__&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;kt&quot;&gt;double&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;distance&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Point&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p&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;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;p&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;n&quot;&gt;x&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;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;p&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;n&quot;&gt;x&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;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;y&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y&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;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;y&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y&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;The first few lines are self-explanatory: we define the coordinates of a point, as well as the cluster it belongs to and the distance to that cluster. Annoyingly, you can’t directly set the default value in the struct (e.g. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;double x = 0&lt;/code&gt;) – you need to do this via &lt;strong&gt;initialisation lists&lt;/strong&gt;. Initially the point belongs to no cluster, so we arbitrarily set that to -1. Accordingly, we must set &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;minDist&lt;/code&gt; to infinity (or the next best thing, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;__DBL_MAX__&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;We also define a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;distance&lt;/code&gt; function, which computes the (square) euclidean distance between this point and another. Our &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Point&lt;/code&gt; struct can be used as follows:&lt;/p&gt;

&lt;div class=&quot;language-cpp 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;c1&quot;&gt;// Define new point at the origin&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;Point&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Point&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;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;cout&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p1&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;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;endl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;// print the x coordinate&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// Define another point and compute square distance&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;Point&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Point&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;3.0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;4.0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;cout&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;distance&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;p2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;endl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;// prints 25.0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If we wanted to represent a datapoint in &lt;em&gt;p&lt;/em&gt;-dimensions, we could replace the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;x&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;y&lt;/code&gt; members with a vector or array of doubles, with each entry corresponding to a coordinate in a given dimension. The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;distance&lt;/code&gt; function would similarly need to be modified to loop over the vectors/arrays and sum all of the squared differences.&lt;/p&gt;

&lt;h3 id=&quot;reading-in-data-from-a-file&quot;&gt;Reading in data from a file&lt;/h3&gt;

&lt;p&gt;Having decided how we are going to store datapoints within our C++ script, we must then read in the data from a CSV file. This is rather unexciting, but actually took me a long time to figure out. Essentially, we loop over all the lines in the CSV file and break them down based on the commas.&lt;/p&gt;

&lt;div class=&quot;language-cpp 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;vector&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Point&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;readcsv&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;vector&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Point&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;points&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;line&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;ifstream&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;s&quot;&gt;&quot;mall_data.csv&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;getline&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;line&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;stringstream&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lineStream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;line&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;double&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;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;getline&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;lineStream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;sc&quot;&gt;','&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;n&quot;&gt;stof&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;getline&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;lineStream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;sc&quot;&gt;'\n'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;y&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;stof&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

        &lt;span class=&quot;n&quot;&gt;points&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;push_back&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Point&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;y&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;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;points&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;Note that the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;readcsv&lt;/code&gt; function returns a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vector&lt;/code&gt; of points. I decided to use a vector instead of an array because vectors handle all of the memory management for you (though are slightly less performant) and are functionally quite similar to python lists.&lt;/p&gt;

&lt;h3 id=&quot;pointers-an-old-enemy-revisited&quot;&gt;Pointers: an old enemy revisited&lt;/h3&gt;

&lt;p&gt;Suppose your friend wants to visit your house. You have two options (the relevance of this thought experiment will be clear shortly.)&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Give them your postcode and let them find your house.&lt;/li&gt;
  &lt;li&gt;Hire a team of builders to replicate your house brick-for-brick right outside their front door.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;readcsv&lt;/code&gt; function returns a vector of points. One might assume that we can then just pass this to whatever &lt;em&gt;k&lt;/em&gt;-means function we define and be done with it.&lt;/p&gt;

&lt;div class=&quot;language-cpp 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;vector&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Point&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;points&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;readcsv&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;// read from file&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;kMeansClustering&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;points&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;          &lt;span class=&quot;c1&quot;&gt;// pass values to function&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;However, we must be aware that depending on the size of our dataset, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;points&lt;/code&gt; might take up quite a large chunk of memory, so we must handle it carefully to be efficient. The problem with the above code is that we are passing the &lt;em&gt;values&lt;/em&gt; of the points to the function, i.e we are making a copy of them. This is inefficient from a memory perspective. Luckily, C++ offers a way around this, called &lt;strong&gt;pass by reference&lt;/strong&gt;. Essentially, instead of giving the &lt;em&gt;value&lt;/em&gt; of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;points&lt;/code&gt; vector to the function, we pass the location (read: postcode) of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;points&lt;/code&gt; vector in memory.&lt;/p&gt;

&lt;div class=&quot;language-cpp 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;vector&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Point&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;points&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;readcsv&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt; 
&lt;span class=&quot;n&quot;&gt;kMeansClustering&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;points&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// pass address of points to function&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The prototype of our &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;kMeansClustering&lt;/code&gt; function is then as follows:&lt;/p&gt;

&lt;div class=&quot;language-cpp 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;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;kMeansClustering&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;vector&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Point&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;points&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;epochs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;k&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;Because we are now passing an address (and thus not technically a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vector&amp;lt;Point&amp;gt;&lt;/code&gt;), we must include an asterisk. Read the first argument as “a reference to a vector of Point objects”.&lt;/p&gt;

&lt;p&gt;I have also added two other arguments:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;epochs&lt;/code&gt; is the number of iterations over which we will do our main &lt;em&gt;k&lt;/em&gt;-means loop&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;k&lt;/code&gt; is the number of clusters.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;initialising-the-clusters&quot;&gt;Initialising the clusters&lt;/h3&gt;

&lt;p&gt;We first need to assign each point to a cluster. The easiest way of doing this is to randomly pick 5 “marker” points and give them labels 1-5 (or actually 0-4 since our arrays index from 0).&lt;/p&gt;

&lt;p&gt;The code for this is quite simple. We will use another vector of points to store the centroids (markers), where the index of the centroid is its label. We then select a random point from the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;points&lt;/code&gt; vector we made earlier (from reading in the csv) and set that as a centroid.&lt;/p&gt;

&lt;div class=&quot;language-cpp 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;vector&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Point&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;centroids&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;srand&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;time&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;c1&quot;&gt;// need to set the random seed&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&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;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;k&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;i&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;centroids&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;push_back&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;points&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;at&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rand&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;n&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;One brief C++ note: because &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;points&lt;/code&gt; is actually a pointer rather than a vector, in order to access an item at a certain index we can’t do &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;points[i]&lt;/code&gt; – we have to first ‘dereference’ it by doing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(*points)[i]&lt;/code&gt;. This is quite ugly, so fortunately we have the syntactic shortcut of: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;points-&amp;gt;at[i]&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Once the centroids have been initialised, we can begin the &lt;em&gt;k&lt;/em&gt;-means algorithm iterations. We now turn to the “meat” of &lt;em&gt;k&lt;/em&gt;-means: assigning points to a cluster and computing new centroids.&lt;/p&gt;

&lt;h3 id=&quot;assigning-points-to-a-cluster&quot;&gt;Assigning points to a cluster&lt;/h3&gt;

&lt;p&gt;The logic here is quite simple. We loop through every datapoint and assign it to its nearest centroid. Because there are &lt;em&gt;k&lt;/em&gt; centroids, the result is a partition of the datapoints into &lt;em&gt;k&lt;/em&gt; clusters.&lt;/p&gt;

&lt;p&gt;In terms of the actual code, I had to spend some time thinking about the best way to represent that a point belonged to a certain cluster. In my python implementation (now many years old), I used a dictionary with cluster IDs as keys and a list of points as values. However, for this program I decided to use a quicker solution: I gave each point a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cluster&lt;/code&gt; attribute which can hold an integer ID. We then set this ID to the index of the cluster that is closest to the point.&lt;/p&gt;

&lt;div class=&quot;language-cpp 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;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;vector&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Point&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;iterator&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;c&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;begin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;centroids&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; 
     &lt;span class=&quot;n&quot;&gt;c&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;centroids&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;c&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;// quick hack to get cluster index&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;clusterId&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;c&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;begin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;centroids&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;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;vector&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Point&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;iterator&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;it&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;points&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;begin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
         &lt;span class=&quot;n&quot;&gt;it&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;points&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;end&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;it&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;Point&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;double&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dist&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;distance&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;p&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;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dist&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;minDist&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;p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;minDist&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dist&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cluster&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;clusterId&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;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;it&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p&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;h3 id=&quot;computing-new-centroids&quot;&gt;Computing new centroids&lt;/h3&gt;

&lt;p&gt;After our first iteration, the clusters are really quite crude – we’ve randomly selected 5 points then formed clusters based on the closest random point. There’s no reason why this should produce meaningful clusters and indeed it doesn’t. However, the heart of &lt;em&gt;k&lt;/em&gt;-means is the update step, wherein we compute the centroids of the previous cluster and subsequently reassign points.&lt;/p&gt;

&lt;p&gt;As previously stated, we are going to majorly simplify the problem by computing the centroid of the points within a cluster rather than the partition of space. Thus all we really have to do is compute the mean coordinates of all the points in a cluster.&lt;/p&gt;

&lt;p&gt;To do this, I created two new vectors: on to keep track of the number of points in each cluster and the other to keep track of the sum of coordinates (then the average is just the latter divided by the former).&lt;/p&gt;

&lt;div class=&quot;language-cpp 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;vector&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;nPoints&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;vector&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;double&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sumX&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sumY&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// Initialise with zeroes&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;j&lt;/span&gt; &lt;span class=&quot;o&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;n&quot;&gt;j&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;k&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;j&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;nPoints&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;push_back&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;n&quot;&gt;sumX&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;push_back&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;n&quot;&gt;sumY&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;push_back&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;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;We then iterate through all the points and increment the correct indices of the above vectors (based on the point’s cluster ID). Importantly, now is a convenient time to reset the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;minDist&lt;/code&gt; attribute of the point, so that the subsequent iteration works as intended.&lt;/p&gt;

&lt;div class=&quot;language-cpp 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;c1&quot;&gt;// Iterate over points to append data to centroids&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;vector&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Point&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;iterator&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;it&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;points&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;begin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt; 
     &lt;span class=&quot;n&quot;&gt;it&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;points&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;end&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;it&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;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;clusterId&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cluster&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;nPoints&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;clusterId&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;n&quot;&gt;sumX&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;clusterId&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;it&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;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;sumY&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;clusterId&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;it&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;minDist&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;__DBL_MAX__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;// reset distance&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// Compute the new centroids&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;vector&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Point&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;iterator&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;c&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;begin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;centroids&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; 
     &lt;span class=&quot;n&quot;&gt;c&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;centroids&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;c&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;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;clusterId&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;c&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;begin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;centroids&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;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;n&quot;&gt;sumX&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;clusterId&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;nPoints&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;clusterId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;y&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sumY&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;clusterId&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;nPoints&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;clusterId&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;Now that we have the new centroids, the &lt;em&gt;k&lt;/em&gt;-means algorithm repeats. We recompute distances and reassign points to their nearest centroids. Then we can find the new centroids, recompute distances etc..&lt;/p&gt;

&lt;h3 id=&quot;writing-to-a-file&quot;&gt;Writing to a file&lt;/h3&gt;

&lt;p&gt;One final detail: after all of our &lt;em&gt;k&lt;/em&gt;-means iterations, we would like to be able to write the output to a file so that we can analyse the clustering. This is quite simple - we will just iterate through the points then print their coordinates and cluster IDs to a csv file.&lt;/p&gt;

&lt;div class=&quot;language-cpp 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;ofstream&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;myfile&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;myfile&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;output.csv&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;myfile&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;x,y,c&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;endl&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;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;vector&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Point&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;iterator&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;it&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;points&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;begin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt; 
     &lt;span class=&quot;n&quot;&gt;it&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;points&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;end&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;it&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;myfile&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;,&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;y&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;,&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cluster&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;endl&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;myfile&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;close&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;h2 id=&quot;testing&quot;&gt;Testing&lt;/h2&gt;

&lt;p&gt;In order to test that my &lt;em&gt;k&lt;/em&gt;-means implementation was working properly, I wrote a simple plotting script. I am somewhat embarrassed (in the context of a C++ post) to say that I wrote this in python.&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;nn&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;nn&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;nn&quot;&gt;seaborn&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sns&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Before clustering
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;df&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;n&quot;&gt;read_csv&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;mall_data.csv&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;header&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;df&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;s&quot;&gt;&quot;Annual income (k$)&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Spending Score (1-100)&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;sns&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;scatterplot&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;n&quot;&gt;df&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Annual income (k$)&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; 
                &lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;df&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Spending Score (1-100)&quot;&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;n&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Scatterplot of spending (y) vs income (x)&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# After clustering
&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;n&quot;&gt;figure&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;df&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;n&quot;&gt;read_csv&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;output.csv&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;sns&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;scatterplot&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;n&quot;&gt;df&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;y&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;df&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; 
                &lt;span class=&quot;n&quot;&gt;hue&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;df&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; 
                &lt;span class=&quot;n&quot;&gt;palette&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sns&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;color_palette&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;hls&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;n_colors&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;5&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;n&quot;&gt;xlabel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Annual income (k$)&quot;&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;n&quot;&gt;ylabel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Spending Score (1-100)&quot;&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;n&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Clustered: spending (y) vs income (x)&quot;&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;n&quot;&gt;show&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 result is quite pretty and it shows that – bar a few contentious points around the centre cluster – the clustering has worked as expected.&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/kmeans/kmeans-clustered.png&quot; style=&quot;width:80%;&quot; /&gt;
&lt;/center&gt;

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

&lt;p&gt;In conclusion, we have successfully implemented a simple &lt;em&gt;k&lt;/em&gt;-means algorithm in C++. Obviously there is much that could be improved about my program. Firstly, many simplifications were made, for example, we restricted the problem to two dimensions and also pre-set the number of clusters. However, there are more subtle issues that we neglected to discuss, including the random initialisation which may result in suboptimal clusters. In fact, there are algorithms like &lt;em&gt;k-means++&lt;/em&gt; that offer major improvements over &lt;em&gt;k&lt;/em&gt;-means by specifying better procedures to find the initial clusters.&lt;/p&gt;

&lt;p&gt;It is also worth mentioning the fundamental difficulties with &lt;em&gt;k&lt;/em&gt;-means: it acutely suffers from the ‘curse of dimensionality’, as data becomes more sparse in high dimensions, and it is relatively inefficient since there are four loops (over iterations, points, clusters, and dimensions). However, &lt;em&gt;k&lt;/em&gt;-means is often a great solution for quickly clustering small data and the algorithm is just about simple enough to explain to business stakeholders.&lt;/p&gt;

&lt;p&gt;In any case, the merits/disadvantages of &lt;em&gt;k&lt;/em&gt;-means aside, writing this program has given me a lot more confidence in C++ and I am keen to develop a more advanced understanding. I think it’s a good complement to my current interests in scientific/financial computing and it is pleasing to see that I am making more progress than I was a few years back.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>What we learnt building an enterprise-blockchain startup</title>
   <link href="https://reasonabledeviations.com/2019/09/01/what-we-learnt-enterprise-blockchain/"/>
   <updated>2019-09-01T00:00:00+00:00</updated>
   <id>https://reasonabledeviations.com/2019/09/01/what-we-learnt-enterprise-blockchain</id>
   <content type="html">&lt;p&gt;It has been almost a year since the idea of HyperVault was first conceived. In that time, we built HyperVault up from a single sentence, gained and lost team members along the way, developed a functional proof-of-concept over the short winter holidays, crashed out of a few competitions (also won a couple of prizes), and finally decided to open source. This post aims to be an honest reflection on the journey – highlighting both the good bits and the times we wanted to give up.
&lt;!--more--&gt;&lt;/p&gt;

&lt;h2 id=&quot;what-is-hypervault&quot;&gt;What is HyperVault?&lt;/h2&gt;

&lt;p&gt;At a high level, the idea behind HyperVault is to use distributed ledger technology (we preferred to avoid the B-word) to provide a secure access layer to sensitive digital resources. If your company has some super-secret documents, what’s the best way of sharing them with other people? From our market research, we found that most people would just chuck it into an email as an attachment. This is terrible from a security standpoint: system administrators can see everything that goes through and you have absolutely no access record or control whatsoever once the email goes into your ‘sent’ mailbox. HyperVault provides a provably tamper-proof system with smart sharing features, such as scheduled or location-based access.&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/fob/hypervault_summary.png&quot; style=&quot;width:100%;&quot; /&gt;
&lt;/center&gt;

&lt;h2 id=&quot;the-beginning-october-november-2018&quot;&gt;The Beginning: October-November 2018&lt;/h2&gt;

&lt;p&gt;HyperVault was born at a Hackbridge cluster – a fortnightly meet-up for Cambridge students who want to build side projects. Li Xi had been thinking about the applications of private blockchains in file sharing and met Robert and a group of other undergrads who were interested in joining the team.&lt;/p&gt;

&lt;p&gt;By far the hardest part of our HyperVault journey was balancing our desire to build something with the Cambridge term structure. An 8-week term, packed with lectures and supervisions, doesn’t give you much time for entrepreneurial activities. However, we were excited enough about the idea to have weekly team meetings in a comfortable room at Trinity College, where we focused on defining what exactly we wanted the product to be.&lt;/p&gt;

&lt;p&gt;At the same time, we were on a constant lookout for startup competitions that we could enter. Fortunately, Li Xi and Robert were also on the Cambridge Blockchain Society – one of whose sponsors (Stakezero Ventures) was launching the Future of Blockchain competition. This proved to be critical to HyperVault’s progress because it gave us a “fixed point” on the timeline – whatever happened, we needed to have a working product and polished pitch by March 2019, when the competition was scheduled to happen.&lt;/p&gt;

&lt;h2 id=&quot;winter-literally-and-figuratively&quot;&gt;Winter, literally and figuratively.&lt;/h2&gt;

&lt;p&gt;December was tough. We had to transition from talking at a high level about what kind of features we wanted to offer, how cool it’d be if we could do XYZ, into actually building a working prototype. To ensure that everyone was on the same page regarding the expected time commitments, in the last couple of weeks of term, we made a detailed project plan that outlined the deliverables, due dates, and expected time commitments (we concluded that it’d be at least 12 hours a week each).&lt;/p&gt;

&lt;p&gt;Three team members dropped out at this point. There weren’t any hard feelings – it is understandable for people to have conflicting priorities and it is better that they can say so upfront. This left Li Xi and Robert the considerable task of building a proof of concept, with Andrew taking the lead on the business plan. With the reduced manpower (and a good helping of classic underestimation), the weekly commitment was closer to 30 hours. It was a real hustle having to balance this with academics (and of course, winter festivities), but by the end of the holidays, we had our proof-of-concept. The main lesson to be drawn from this is the importance of having that “hard conversation” with your team. Give people the option to call it quits at the start, but have a “no ifs, no buts” understanding that come what may, your prototype/MVP must be done by a certain date.&lt;/p&gt;

&lt;h2 id=&quot;the-build-up-to-the-competition-january--march&quot;&gt;The build-up to the competition (January – March)&lt;/h2&gt;

&lt;p&gt;At this stage, our proof-of-concept was something like a Google Drive or Dropbox, except with a blockchain backend. We were on track, so moved into “Phase 2” – doing real market research and nailing down a business plan. Andrew tapped into his network at the Judge Business School to get feedback from his peers, which was incredibly useful. One of them mentioned that our product offering was quite similar to something they had used in a previous job. This was very worrying, and after some further googling we were deeply troubled to find out that there was a whole class of products – virtual data rooms – that were doing exactly what we had offered. Robert and Li Xi were a little discouraged by this, but Andrew reminded us about a very important fact of entrepreneurship: you never have to be first, you just have to do things better.&lt;/p&gt;

&lt;p&gt;Additionally, one of the oft-repeated pieces of advice from successful entrepreneurs is the need to focus on a very specific niche (a ‘vertical’) rather than trying to build a one-size-fits-all product. We saw that the incumbents had dominated the market for financial companies, so we decided to devote our attention to legal companies. With this in mind, we sat down as a team to really think through our business plan – we found the concept of a ”lean business canvas” enthralling – its almost scientific approach to iterating your product via hypothesis and experimentation really appealed to us (indeed, all three of us have a STEM background).&lt;/p&gt;

&lt;h2 id=&quot;competition-time-march-april&quot;&gt;Competition time (March, April)&lt;/h2&gt;

&lt;p&gt;Coming up on March, our academic commitments were noticeably picking up. But there was no time to waste, as it was competition-season. We agreed as a team that for any competition, there would need to be at least two of us there (three was unrealistic).&lt;/p&gt;

&lt;p&gt;The first competition we entered did not go so well – we failed to convince the judges, who traditionally favoured biotech ideas, that blockchain was anything more than a fad. Despite making it to the finals, it was clear that there was very little engagement with our idea. At the time, we blamed the judges for being too closed-minded, but in retrospect, we see that it’s really our responsibility in the pitch to sell the vision we have. Nevertheless, we used the feedback to refine our business plan and tidy up our proof-of-concept.&lt;/p&gt;

&lt;p&gt;The main competition in March, however, was much more engaging and challenging (more than 100 teams participated). Due to a last-minute change of competition dates, Li Xi cut short his travel plans and flew back from Geneva for the Finals – luckily, he made it back just in time for the pitch with only a few hours to spare and the pitch went quite smoothly. The judges were intrigued by the idea and asked us some particularly challenging technical questions regarding encryption, which Li Xi dealt with deftly. Coming out of it, we won the £2000 Future of Blockchain Nucypher prize, which was a strong vote of confidence.&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/fob/fob_pitch.png&quot; style=&quot;width:80%;&quot; /&gt;
&lt;figcaption&gt;Q&amp;amp;A at the Future of Blockchain Finals&lt;/figcaption&gt;
&lt;br /&gt;
&lt;/center&gt;

&lt;p&gt;The last competition we attended was the R3 Global Pitch Competition. R3 is an enterprise blockchain software firm working with a large ecosystem of more than 300 of the worlds’ largest companies. Even before the submission of our deck, R3 provided excellent guidance by connecting us to their legal teams and having numerous calls with us to refine our business plan, for which we were immensely grateful. We did well in the competition, winning a place in the global finals and subsequently being offered free office space at R3’s office in London to continue building HyperVault.&lt;/p&gt;

&lt;h2 id=&quot;the-end&quot;&gt;The end?&lt;/h2&gt;

&lt;p&gt;With this opportunity in mind, we were talking to VCs about the idea of scaling up. In particular, we had good chemistry with the folks at Stakezero and Wilbe Ventures, and they gave us a lot of very thoughtful advice, emphasising the importance of not just identifying a vertical, but also targeting the size of the customer and the key stakeholders in a company that could make things happen. We hadn’t put too much thought into this before; I guess we were implicitly making the naïve assumption that if we built a cool product, it would sell itself. Up until that point, in our team meetings, we had just said that we’d target “SMEs” (small and medium-sized enterprises). But after a lot more research and discussion, we came to the troubling realisation that our ideal customer was, in fact, an “elephant” – an organisation that would spend more than $100,000 a year on our product (see this excellent short &lt;a href=&quot;http://christophjanz.blogspot.com/2014/10/five-ways-to-build-100-million-business.html&quot;&gt;blog post&lt;/a&gt; for more) – they are the only ones who have 1) enough secure documents and 2) a need to share these documents with scalability.&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/fob/five_ways_business.png&quot; style=&quot;width:80%;&quot; /&gt;
&lt;figcaption&gt;Image from Christopher Janz's blog&lt;/figcaption&gt;
&lt;br /&gt;
&lt;/center&gt;

&lt;p&gt;Regardless of our successes in talking with smaller companies, big companies are an entirely different ball game. You need enterprise sales teams and a polished, audited product. In principle, there was nothing stopping us. We could have used R3’s office space to turn our proof of concept into a sleek MVP and earnestly begin the search for venture capital on the back of that. But ultimately, none of us felt particularly excited by this prospect. A startup is hard work – many hours spent fixing bugs, refining pitches, cold calling hundreds of companies, etc. The only thing keeping it together is the deep desire to build something and a fundamental belief that your product can “change the world”. Faced with the prospect of an enterprise sales cycle and a product which is really an improvement over current technology instead of something brand new (don’t get us wrong, we do still think it’s a major improvement), we realised that HyperVault as a commercial startup had run its course, at least until we had more experience.&lt;/p&gt;

&lt;h2 id=&quot;what-comes-next&quot;&gt;What comes next&lt;/h2&gt;

&lt;p&gt;We truly believe that distributed ledgers applied to file-sharing could be a significant improvement over current technologies. To that end, we decided as a team that the best way forward would be to open-source our codebase, such that it becomes an example of a real-world enterprise blockchain app for others to build on.&lt;/p&gt;

&lt;p&gt;We published most of our code on GitHub a few months ago and have already had someone reach out to us expressing interest in incorporating HyperVault into their product. We plan to continue cleaning up the codebase within the next couple of months, including comprehensive documentation and a contributors’ guide. Do leave a clap or comment below if you think this is something we should devote more time to!&lt;/p&gt;

&lt;p&gt;In any case, rather than inserting some banal Edison quote about failure, we’d like to end this post simply by saying that we are extremely grateful for the friendships we made, the people we met, and the chance to tell ourselves that we built something from nothing.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This post has been cross-posted on &lt;a href=&quot;https://medium.com/hackbridge/what-we-learnt-building-an-enterprise-blockchain-startup-58ea1aaf9521&quot;&gt;medium&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Graph algorithms and currency arbitrage, part 2</title>
   <link href="https://reasonabledeviations.com/2019/04/21/currency-arbitrage-graphs-2/"/>
   <updated>2019-04-21T00:00:00+00:00</updated>
   <id>https://reasonabledeviations.com/2019/04/21/currency-arbitrage-graphs-2</id>
   <content type="html">&lt;p&gt;In the &lt;a href=&quot;/2019/03/02/currency-arbitrage-graphs/&quot;&gt;previous post&lt;/a&gt; (which should definitely be read first!) we explored how graphs can be used to represent a currency market, and how we might use shortest-path algorithms to discover arbitrage opportunities. Today, we will apply this to real-world data. It should be noted that we are not attempting to build a functional arbitrage bot, but rather to explore how graphs could potentially be used to tackle the problem. Later on we’ll discuss why our methodology is unlikely to result in actionable arbitrage. 
&lt;!--more--&gt;&lt;/p&gt;

&lt;p&gt;Rather than using fiat currencies as presented in the previous post, we will examine a market of cryptocurrencies because it is much easier to acquire crypto order book data. We’ll narrow down the problem further by making two more simplifications. Firstly, we will focus on arbitrage within a single exchange. That is, we’ll look to see if there are pathways between different coins on an exchange which leave us with more of a coin than we started with. Secondly, we will only be considering a single snapshot of data from the exchange. Obviously markets are highly dynamic, with thousands of new bids and asks coming in each second. A proper arbitrage system needs to constantly be scanning for opportunities, but that’s out of the scope of this post.&lt;/p&gt;

&lt;p&gt;With all this in mind, the overall implementation strategy was as follows:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;For a given exchange, acquire the list of pairs that will form the vertices.&lt;/li&gt;
  &lt;li&gt;For each of these pairs, download a snapshot of the bid/ask.&lt;/li&gt;
  &lt;li&gt;Process these values accordingly, assigning them to directed edges on the graph.&lt;/li&gt;
  &lt;li&gt;Using Bellman-Ford, find and return negative-weight cycles if they exist.&lt;/li&gt;
  &lt;li&gt;Calculate the arbitrage that these negative-weight cycles correspond to.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;em&gt;The full code for this project can be found in this &lt;a href=&quot;https://github.com/robertmartin8/CryptoGraphArb/tree/master&quot;&gt;GitHub repo&lt;/a&gt;.s&lt;/em&gt;&lt;/p&gt;

&lt;script async=&quot;&quot; defer=&quot;&quot; src=&quot;https://buttons.github.io/buttons.js&quot;&gt;&lt;/script&gt;

&lt;p&gt;&lt;a class=&quot;github-button&quot; href=&quot;https://github.com/robertmartin8/CryptoGraphArb&quot; data-icon=&quot;octicon-star&quot; data-size=&quot;large&quot; aria-label=&quot;Star robertmartin8/MachineLearningStocks on GitHub&quot;&gt;Star&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;raw-data&quot;&gt;Raw data&lt;/h2&gt;

&lt;p&gt;For the raw data, I decided to use the &lt;a href=&quot;https://min-api.cryptocompare.com/documentation&quot;&gt;CryptoCompare API&lt;/a&gt; which has a load of free data compiled across multiple exchanges. To get started, you’ll need to register to get a free API key.&lt;/p&gt;

&lt;p&gt;As mentioned previously, we will only look at data from Binance. I chose Binance not because it has a large selection of altcoins, but because most altcoins can trade directly with multiple pairs (e.g. BTC, ETH, USDT, BNB). Some exchanges have many altcoins but you can only buy them with BTC – this is not well suited for arbitrage.&lt;/p&gt;

&lt;p&gt;Firstly, we need to find out which pairs Binance offers. This is done with a simple call (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AUTH&lt;/code&gt; is your API key string):&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;nn&quot;&gt;requests&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;json&lt;/span&gt; 

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;top_exchange_pairs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;url&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;s&quot;&gt;&quot;https://min-api.cryptocompare.com/data/v3/all/&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; 
        &lt;span class=&quot;s&quot;&gt;&quot;exchanges?topTier=true&amp;amp;api_key=&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;AUTH&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;r&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;requests&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;pairs_list.json&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;w&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dump&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;f&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;This is an excerpt from the resulting JSON file – for each exchange, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pairs&lt;/code&gt; field lists all other coins that the key coin can be traded with:&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;nl&quot;&gt;&quot;Data&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;nl&quot;&gt;&quot;Binance&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;nl&quot;&gt;&quot;isActive&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;true&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;nl&quot;&gt;&quot;isTopTier&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;true&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;nl&quot;&gt;&quot;pairs&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;nl&quot;&gt;&quot;ETH&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;PAX&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;TUSD&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;USDT&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;USDC&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;BTC&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;nl&quot;&gt;&quot;ONGAS&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;BTC&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;BNB&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;USDT&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;nl&quot;&gt;&quot;PHX&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;ETH&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;BNB&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;BTC&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;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
   &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;Coinbase&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;nl&quot;&gt;&quot;isActive&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;true&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;nl&quot;&gt;&quot;isTopTier&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;true&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;nl&quot;&gt;&quot;pairs&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;nl&quot;&gt;&quot;ETH&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;DAI&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;USD&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;USDC&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;EUR&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;GBP&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;BTC&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;nl&quot;&gt;&quot;BCH&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;BTC&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;GBP&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;EUR&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;USD&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;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;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;I then filtered out coins with fewer than three tradable pairs. These coins are unlikely to participate in arbitrage – we would rather have a graph that is more connected.&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;binance_connected_pairs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;pairs_list.json&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;r&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;f&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;json&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;load&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;pairs&lt;/span&gt; &lt;span class=&quot;o&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;s&quot;&gt;&quot;Data&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Binance&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;pairs&quot;&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;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;k&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;v&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;k&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;v&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pairs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;items&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;nb&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;v&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;mi&quot;&gt;3&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;We are now ready to download a snapshot of the available exchange rates for each of these coins.&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;nn&quot;&gt;os&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;tqdm&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# progress bar
&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;download_snapshot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pair_dict&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;outfolder&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;ow&quot;&gt;not&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;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;exists&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;outfolder&lt;/span&gt;&lt;span class=&quot;p&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;makedirs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;outfolder&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# Download data and write to files
&lt;/span&gt;    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p2s&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tqdm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pair_dict&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;items&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()):&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;url&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;s&quot;&gt;&quot;https://min-api.cryptocompare.com/data/&quot;&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;s&quot;&gt;&quot;ob/l1/top?fsyms=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;p1&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;amp;tsyms=&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;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;p2s&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;s&quot;&gt;&quot;&lt;/span&gt;
            &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;&amp;amp;e=Binance&amp;amp;api_key=&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;AUTH&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;r&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;requests&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;open&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;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;outfolder&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;p1&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;_pairs_snapshot.json&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;w&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dump&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;f&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;We can then run all of the above functions to produce a directory full of the exchange rate data for the listed pairs.&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;top_exchange_pairs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;connected&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;binance_connected_pairs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;download_snapshot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;connected&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;binance_data&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-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;nl&quot;&gt;&quot;EOS&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;nl&quot;&gt;&quot;BNB&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;nl&quot;&gt;&quot;BID&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;.2073&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;nl&quot;&gt;&quot;ASK&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;.2077&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;span class=&quot;nl&quot;&gt;&quot;BTC&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;nl&quot;&gt;&quot;BID&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;.0007632&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;nl&quot;&gt;&quot;ASK&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;.0007633&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;span class=&quot;nl&quot;&gt;&quot;ETH&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;nl&quot;&gt;&quot;BID&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;.02594&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;nl&quot;&gt;&quot;ASK&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;.025964&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;span class=&quot;nl&quot;&gt;&quot;USDT&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;nl&quot;&gt;&quot;BID&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;7.0441&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;nl&quot;&gt;&quot;ASK&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;7.046&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;span class=&quot;nl&quot;&gt;&quot;PAX&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;nl&quot;&gt;&quot;BID&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;7.0535&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;nl&quot;&gt;&quot;ASK&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;7.07&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;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;This excerpt reveals something that we glossed over completely in the previous post. As anyone who has tried to exchange currency on holiday will know, there are actually two exchange rates for a given currency pair depending on whether you are buying or selling the currency. In trading, these two prices are called the &lt;strong&gt;bid&lt;/strong&gt; (the current highest price someone will buy for) and the &lt;strong&gt;ask&lt;/strong&gt; (the current lowest price someone will sell for). As it happens, this is very easy to deal with in the context of graphs.&lt;/p&gt;

&lt;h2 id=&quot;preparing-the-data&quot;&gt;Preparing the data&lt;/h2&gt;

&lt;p&gt;Having downloaded the raw data, we must now prepare it so that it can be put into a graph. This effectively means parsing it from the raw JSON and putting it into a pandas dataframe. We will arrange it in the dataframe such that it constitutes an adjacency matrix:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Column ETH row BTC is the bid:
    &lt;ul&gt;
      &lt;li&gt;i.e someone will pay &lt;em&gt;x&lt;/em&gt; BTC to buy my 1 ETH&lt;/li&gt;
      &lt;li&gt;this is then the weight of the ETH $\to$ BTC edge.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Column BTC row ETH is the ask:
    &lt;ul&gt;
      &lt;li&gt;i.e I have to pay &lt;em&gt;y&lt;/em&gt; BTC to buy someone’s 1 ETH&lt;/li&gt;
      &lt;li&gt;the reciprocal of this is the weight of the BTC $\to$ ETH edge.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I chose this particular row-column scheme because it results in intuitive indexing: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;df.X.Y&lt;/code&gt; is the amount of Y gained by selling 1 unit of X, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;df.A.B * df.B.C * df.C.D&lt;/code&gt; is the total amount of D gained by trading 1 unit of A when trading via $A \to B \to C \to D$.&lt;/p&gt;

&lt;p&gt;The column headers will be the same as the row headers, consisting of all the coins we are considering. The function that creates the adjacency matrix is shown here:&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;create_adj_matrix&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pair_dict&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;folder&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;outfile&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;snapshot.csv&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# Union of 'from' and 'to' pairs
&lt;/span&gt;    &lt;span class=&quot;n&quot;&gt;flatten&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;lambda&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;l&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;item&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sublist&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;l&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;item&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sublist&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;keys&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;vals&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pair_dict&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;items&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;all_pairs&lt;/span&gt; &lt;span class=&quot;o&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;set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;keys&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;n&quot;&gt;flatten&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;values&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)))&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# Create empty df
&lt;/span&gt;    &lt;span class=&quot;n&quot;&gt;df&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;n&quot;&gt;DataFrame&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;all_pairs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;index&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;all_pairs&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;p1&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pair_dict&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;keys&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;open&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;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;folder&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;p1&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;_pairs_snapshot.json&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;r&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;res&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;load&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;quotes&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;res&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Data&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;RAW&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;p1&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;p2&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;quotes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;df&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;p1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;p2&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;nb&quot;&gt;float&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;quotes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;p2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;BID&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;df&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;p2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;p1&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;o&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;n&quot;&gt;quotes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;p2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;ASK&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;except&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;KeyError&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;k&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;s&quot;&gt;&quot;Error for &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;p1&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;p2&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;continue&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;df&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;to_csv&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;outfile&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;h2 id=&quot;putting-the-data-into-a-graph&quot;&gt;Putting the data into a graph&lt;/h2&gt;

&lt;p&gt;We will be using the &lt;a href=&quot;https://networkx.github.io/documentation/stable/&quot;&gt;NetworkX&lt;/a&gt; package, an intuitive yet extremely well documented library for dealing with all things graph-related in python.&lt;/p&gt;

&lt;p&gt;In particular, we will be using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nx.DiGraph&lt;/code&gt;, which is just a (weighted) directed graph. I was initially concerned that it’d be difficult to get the data in: python libraries often adopt their own weird conventions and you have to modify your data so that is in the correct format. This was not really the case with NetworkX, it turns out that we already did most of the hard work when we put the data into our pandas adjacency matrix.&lt;/p&gt;

&lt;p&gt;Firstly, we take negative logs as discussed in the &lt;a href=&quot;/2019/03/02/currency-arbitrage-graphs/&quot;&gt;previous post&lt;/a&gt;. Secondly, in our dataframe we currently have &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NaN&lt;/code&gt; whenever there is no edge between two vertices. To make a valid &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nx.DiGraph&lt;/code&gt;, we need to set these to zero. Lastly, we transpose the dataframe because NetworkX uses a different row/column convention. We then pass this processed dataframe into the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nx.Digraph&lt;/code&gt; constructor. Summarised in one line:&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;g&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;nx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;DiGraph&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;np&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;df&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fillna&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;n&quot;&gt;T&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;h2 id=&quot;bellman-ford&quot;&gt;Bellman-Ford&lt;/h2&gt;

&lt;p&gt;To implement Bellman-Ford, we make use of the funky &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;defaultdict&lt;/code&gt; data structure. As the name suggests, it works exactly like a python dict, except that if you query a key that is not present you get a certain default value back. The first part of our implementation is quite standard, as we are just doing the $n - 1$ edge-relaxations where &lt;em&gt;n&lt;/em&gt; is the number of vertices.&lt;/p&gt;

&lt;p&gt;But because the ‘classic’ Bellman-Ford does not actually return negative-weight cycles, the second part of our implementation is a bit more complicated. The key idea is that if after $n-1$ relaxations, there is an edge that can be relaxed further then that edge must be on a negative weight cycle. So to find this cycle we walk back along the predecessors until a cycle is detected, then return the cyclic portion of that walk. In order to prevent subsequent redundancy, we mark these vertices as ‘seen’ via another &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;defaultdict&lt;/code&gt;. This procedure adds a linear cost to Bellman-Ford since we have to iterate over all the edges, but the asymptotic complexity overall remains $O(VE)$.&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;bellman_ford_return_cycle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;g&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;n&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;g&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;nodes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;d&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;defaultdict&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;lambda&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;math&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;inf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# distances dict
&lt;/span&gt;    &lt;span class=&quot;n&quot;&gt;p&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;defaultdict&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;lambda&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;c1&quot;&gt;# predecessor dict
&lt;/span&gt;    &lt;span class=&quot;n&quot;&gt;d&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;s&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;0&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;n&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;u&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;v&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;g&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;edges&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;# Bellman-Ford relaxation
&lt;/span&gt;            &lt;span class=&quot;n&quot;&gt;weight&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;g&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;u&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;v&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;weight&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;n&quot;&gt;d&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;u&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;weight&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;d&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;v&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]:&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;d&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;v&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;d&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;u&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;weight&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;v&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;u&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# update pred
&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;# Find cycles if they exist
&lt;/span&gt;        &lt;span class=&quot;n&quot;&gt;all_cycles&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;seen&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;defaultdict&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;lambda&lt;/span&gt;&lt;span class=&quot;p&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;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;u&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;v&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;g&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;edges&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;seen&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;v&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]:&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;continue&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;# If we can relax further there must be a neg-weight cycle
&lt;/span&gt;            &lt;span class=&quot;n&quot;&gt;weight&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;g&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;u&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;v&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;weight&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;n&quot;&gt;d&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;u&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;weight&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;d&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;v&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]:&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;cycle&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;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;v&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;while&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;# Walk back along preds until a cycle is found
&lt;/span&gt;                    &lt;span class=&quot;n&quot;&gt;seen&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;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;
                    &lt;span class=&quot;n&quot;&gt;cycle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;append&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;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p&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;k&quot;&gt;if&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;n&quot;&gt;v&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;or&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cycle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
                        &lt;span class=&quot;k&quot;&gt;break&lt;/span&gt;
                &lt;span class=&quot;c1&quot;&gt;# Slice to get the cyclic portion
&lt;/span&gt;                &lt;span class=&quot;n&quot;&gt;idx&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cycle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;index&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;cycle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;append&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;all_cycles&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cycle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;idx&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;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;all_cycles&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;As a reminder, this function returns all negative-weight cycles reachable from a given source vertex (returning the empty list if there are none). To find all negative-weight cycles, we can simply call the above procedure on every vertex then eliminate duplicates.&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;all_negative_cycles&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;g&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;all_paths&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;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;v&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;g&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;nodes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;all_paths&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bellman_ford_negative_cycles&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;g&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;v&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;flattened&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;item&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sublist&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;all_paths&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;item&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sublist&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;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;n&quot;&gt;i&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;i&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;tuple&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;j&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;j&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;flattened&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;h2 id=&quot;tying-it-all-together&quot;&gt;Tying it all together&lt;/h2&gt;

&lt;p&gt;The last thing we need is a function that calculates the value of an arbitrage opportunity given a negative-weight cycle on a graph. This is easy to implement: we just find the total weight along the path then exponentiate the negative total (because our weights are the negative log of the exchange rates).&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;calculate_arb&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cycle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;g&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;verbose&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;total&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;p1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;zip&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cycle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cycle&lt;/span&gt;&lt;span class=&quot;p&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;n&quot;&gt;total&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;g&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;p1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;p2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;weight&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;arb&lt;/span&gt; &lt;span class=&quot;o&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;exp&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;total&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;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;verbose&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Path:&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cycle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&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;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;arb&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;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;g&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;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&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;arb&lt;/span&gt;


&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;find_arbitrage&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;filename&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;snapshot.csv&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;df&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;n&quot;&gt;read_csv&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;filename&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;header&lt;/span&gt;&lt;span class=&quot;o&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;n&quot;&gt;index_col&lt;/span&gt;&lt;span class=&quot;o&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;n&quot;&gt;g&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;nx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;DiGraph&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;np&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;df&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fillna&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;n&quot;&gt;T&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;nx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;negative_edge_cycle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;g&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;ARBITRAGE FOUND&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;=&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;15&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s&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;p&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;all_negative_cycles&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;g&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;calculate_arb&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;g&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;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;No arbitrage opportunities&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;p&gt;Running this function gives the following output:&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;ARBITRAGE FOUND
===============

Path: ['USDT', 'BAT', 'BTC', 'BNB', 'ZEC', 'USDT']
0.087%

Path: ['BTC', 'XRP', 'USDT', 'BAT', 'BTC']
0.05%

Path: ['BTC', 'BNB', 'ZEC', 'USDT', 'BAT', 'BTC']
0.087%

Path: ['BNB', 'ZEC', 'USDT', 'BAT', 'BTC', 'BNB']
0.087%

Path: ['USDT', 'BAT', 'BTC', 'XRP', 'USDT']
0.05%
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;0.09% is not exactly a huge amount of money, but it is still risk-free profit, right?&lt;/p&gt;

&lt;h2 id=&quot;why-wouldnt-this-work&quot;&gt;Why wouldn’t this work?&lt;/h2&gt;

&lt;p&gt;Notice that we haven’t mentioned exchange fees at any point. In fact, Binance charges a standard 0.1% commission on every trade. It is easy to modify our code to incorporate this: we just multiply each exchange rate by 0.999. But we don’t need to compute anything to see that we would certainly be losing much more money than gained from the arbitrage.&lt;/p&gt;

&lt;p&gt;Secondly, it is likely that this whole analysis is flawed because of the way the data was collected. The function &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;download_snapshot&lt;/code&gt; makes a request for each coin in sequence, taking a few seconds in total. But in these few seconds, prices may move – so really the above “arbitrage” may just be a result of our algorithm selecting some of the price movements. This could be fixed by using timestamps provided by the exchange to ensure that we are looking at the order book for each pair at the exact same moment in time.&lt;/p&gt;

&lt;p&gt;Thirdly, we have assumed that you can trade an infinite quantity of the bid and ask. An order consists of a price and a quantity, so we will only be able to fill a limited quantity at the ask price. Thus in practice we would have to look at the top few levels of the order book and consider how much of it we’d eat into.&lt;/p&gt;

&lt;p&gt;It is not difficult to extend our methodology to arb between different exchanges. We would just need to aggregate the top of the order book from each exchange, then put the best bid/ask onto the respective edges. Of course, to run this strategy live would require us to manage our inventory not just on a currency level but per currency per exchange, and factors like the congestion of the bitcoin network would come into play.&lt;/p&gt;

&lt;p&gt;Lastly, this analysis has only been for a single snapshot. A proper arbitrage bot would have to constantly look for opportunities simultaneously across multiple order books. I think this could be done by having a websocket stream which keeps the graph updated with the latest quotes, and using a more advanced method for finding negative-weight cycles that does not involve recomputing the shortest paths via Bellman-Ford.&lt;/p&gt;

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

&lt;p&gt;All this begs the question: why is it so hard to find arbitrage? The simple answer is that other people are doing it smarter, better, and (more importantly) faster. With highly optimised algorithms (probably implemented in C++), ‘virtual colocation’ of servers, and proper networking software/hardware, professional market makers are able to exploit these simple arbitrage opportunities extremely rapidly.&lt;/p&gt;

&lt;p&gt;In any case, the point of this post was not to develop a functional arbitrage bot but rather to demonstrate the power of graph algorithms in a non-standard use case. Hope you found it as interesting as I did!&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>Graph algorithms and currency arbitrage, part 1</title>
   <link href="https://reasonabledeviations.com/2019/03/02/currency-arbitrage-graphs/"/>
   <updated>2019-03-02T00:00:00+00:00</updated>
   <id>https://reasonabledeviations.com/2019/03/02/currency-arbitrage-graphs</id>
   <content type="html">&lt;p&gt;Arbitrage is the holy grail for traders and the bedrock of financial academia. Let’s say you are in an open marketplace with Alice selling oranges for \$1 each and Bob buying them for \$2. As a cunning trader, you realise you can buy an orange from Alice and immediately sell it to Bob for \$1 of “risk-free” profit. However, as you keep reaping this \$1 profit by buying up Alice’s oranges, she raises her prices. Eventually, Alice would be charging \$2 per orange and the arbitrage would be shut. In other words, acting on arbitrage opportunities causes the market to become more efficient, with everything as close to its fair price as possible. Because of this, the only way to profit from true arbitrage is to find and execute opportunities faster than everybody else.&lt;/p&gt;

&lt;p&gt;Currency exchange is a natural place to search for arbitrage because there are many different pairs that we can trade. For example, assume we are able to convert GBP to USD, then USD to EUR, then EUR to AUD for an effective rate of 1:2 GBP:AUD. If we were then able to directly swap AUD back to GBP in the ratio 2:1.1, this would be an arbitrage opportunity because we end up with more GBP than we started with while taking zero risk.&lt;/p&gt;

&lt;p&gt;Our goal is to develop a systematic method for detecting arbitrage opportunities by framing the problem in the language of graphs. This is a reasonably large topic, so we’ll split it into two parts. Part 1 (this post) will lay the theoretical groundwork, introducing graph algorithms and giving an overview of their application to currency arbitrage. In &lt;a href=&quot;/2019/04/21/currency-arbitrage-graphs-2/&quot;&gt;Part 2&lt;/a&gt; we will present an actual implementation of these ideas in python, applied to cryptocurrencies.&lt;/p&gt;

&lt;h2 id=&quot;graphs&quot;&gt;Graphs&lt;/h2&gt;

&lt;p&gt;Formally, we define a graph as a set of vertices (also called nodes) and edges. These edges can be &lt;em&gt;directed&lt;/em&gt; (i.e have little arrows on them) and may have &lt;em&gt;weights&lt;/em&gt;. It is completely up to us to decide what the vertices, edges, and weights represent. For example, if we are trying to model a road network we might say that the vertices are cities (or junctions), the edges are the roads themselves, and the weights are the length of the roads. The diagram below shows how a weighted directed graph is typically depicted.&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/grapharb/weighted_digraph.png&quot; style=&quot;width:70%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;It is useful to formulate real-world problems in terms of graphs because we have about three centuries worth of relevant theory – they were first investigated by Euler in 1736. In particular, there exist many efficient algorithms related to finding the shortest path along a graph, which have widespread applications e.g. in mapping.&lt;/p&gt;

&lt;p&gt;The &lt;a href=&quot;https://en.wikipedia.org/wiki/Bellman%E2%80%93Ford_algorithm&quot;&gt;Bellman-Ford algorithm&lt;/a&gt; finds the minimum weight path from a single source vertex to all other vertices on a weighted directed graph. For the purposes of this post, we don’t need to know anything other than the fact that once the algorithm terminates, each vertex is labelled with the total weight of the minimum weight path from the source to that vertex.&lt;/p&gt;

&lt;p&gt;Actually, the above statement has an incredibly important caveat. What happens if we try to find the minimum weight path from vertices C to D in the above graph? The obvious guess would be that it is the direct path along the CD edge, which has total weight 2.1. However, notice that we can in fact reduce this further by following the path CDECD, which would have a total weight of -1. But why stop there? If we go around the loop again, we can reduce it stil further. In fact, each time we traverse the loop, our minimum total weight will reduce by 1. Hence this is called a &lt;strong&gt;negative-weight cycle&lt;/strong&gt;, the existence of which means that the shortest path between C and D is not defined.&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/grapharb/neg_weight_cycle.png&quot; style=&quot;width:30%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;This is actually the main reason why we are choosing to use Bellman-Ford over another shortest path algorithm like Dijkstra, despite the latter being asymptotically more efficient. Bellman-Ford is able to detect if there is a negative-weight cycle and as will be seen shortly, this will be the key to detecting arbitrage opportunities.&lt;/p&gt;

&lt;h2 id=&quot;graphs-for-currency-arbitrage&quot;&gt;Graphs for currency arbitrage&lt;/h2&gt;

&lt;p&gt;How can graphs be used to model currency markets? At a very high level, we will assign currencies to different vertices, and let the edge weight represent the exchange rate. A simple example is presented below: in this market, we can convert 1 GBP to 1.27 USD, 1 USD to 1.43 AUD, etc.&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/grapharb/currency_graph.png&quot; style=&quot;width:30%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;To find the exchange rate between two currencies that do not share an edge, we simply find a path between the two currency vertices and walk along it, multiplying by each successive edge weight (exchange rate). If there are multiple possible paths, choose the path that has the greatest weight product, which corresponds to the most favourable rate.&lt;/p&gt;

&lt;p&gt;What would happen if we start with 1 GBP and convert it along GBP $\to$ USD $\to$ AUD $\to$ GBP? Well, multiplying out the edges we see that our initial 1 GBP becomes $1.27 \times 1.43 \times 0.55 \approx 0.999$ GBP. That is to say, we lose a tenth of a penny on the round trip. However, if this number were greater than 1, it would mean that by following this path we end up with &lt;em&gt;more&lt;/em&gt; GBP than we started with – this is textbook arbitrage.&lt;/p&gt;

&lt;p&gt;So the problem of finding arbitrage can thus be formally posed as follows: find a cycle in the graph such that multiplying all the edge weights along that cycle results in a value greater than 1. In fact we have already described an algorithm that can find such a path – the problem is equivalent to finding a negative-weight cycle, provided we do some preprocessing on the edges.&lt;/p&gt;

&lt;p&gt;Firstly, we note that Bellman-Ford computes the path weight by adding the individual edge weights. To make this work for exchange rates, which are multiplicative, an elegant fix is to first take the logs of all the edge weights. Thus when we sum edge weights along a path we are actually multiplying exchange rates – we can recover the multiplied quantity by exponentiating the sum. Secondly, Bellman-Ford attempts to find &lt;em&gt;minimum&lt;/em&gt; weight paths and &lt;em&gt;negative&lt;/em&gt; edge cycles, whereas our arbitrage problem is about maximising the amount of currency received. Thus as a simple modification, we must also make our log weights negative.&lt;/p&gt;

&lt;p&gt;With these two tricks in hand, we are able to apply Bellman-Ford. The minimal weight between two currency vertices corresponds to the optimal exchange rate, the value of which can be found by by exponentiating the negative sum of weights along the path. As a corollary, we have the wonderful insight that: &lt;strong&gt;a negative-weight cycle on the negative-log graph corresponds to an arbitrage opportunity&lt;/strong&gt;.&lt;/p&gt;

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

&lt;p&gt;We have seen that graphs provide an elegant framework for thinking about currency exchange. Concretely, by representing different currencies as vertices and using the negative log of the exchange rate as the edge weight we can apply shortest-path algorithms to find negative-weight cycles if they exist, which correspond to arbitrage opportunities. In the &lt;a href=&quot;/2019/04/21/currency-arbitrage-graphs-2/&quot;&gt;next post&lt;/a&gt;, we code up the methodology presented in this post and apply it to some real-world data. Should be fun!&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Portfolio optimisation: lessons learnt</title>
   <link href="https://reasonabledeviations.com/2018/09/27/lessons-portfolio-opt/"/>
   <updated>2018-09-27T00:00:00+00:00</updated>
   <id>https://reasonabledeviations.com/2018/09/27/lessons-portfolio-opt</id>
   <content type="html">&lt;p&gt;Over the past few months I have been busy doing a mixture of blockchain consulting and quantitative finance research. In particular, I have had the opportunity to investigate the interesting problem of portfolio management for cryptoassets – it was not my first experience with portfolio optimisation, having implemented efficient frontier portfolios at a roboadvisor startup, but this time I took the opportunity to do a deep dive into the subject.&lt;br /&gt;
&lt;!--more--&gt;&lt;/p&gt;

&lt;p&gt;I’m nowhere close to being an expert on the subject of portfolio optimisation – I’m just recounting some of the things I’ve learnt along the way.&lt;/p&gt;

&lt;h2 id=&quot;1-be-careful-with-in-sample-vs-out-of--testing&quot;&gt;1. Be careful with in-sample vs out-of- testing&lt;/h2&gt;

&lt;p&gt;For most people, ‘portfolio optimisation’ basically refers to mean-variance optimisation (MVO) in the style of Markowitz. MVO will tell you the mathematically optimal portfolio allocation, which sounds great, but the caveat is that it requires the (future) expected returns and the covariance matrix as inputs. For those of us who aren’t clairvoyant, these quantities are inaccessible, so we must make do with estimates. The quality of these estimates will be discussed in the next section, but for now I would like to highlight a common mistake, which is backtesting with a forward-looking bias.&lt;/p&gt;

&lt;p&gt;The standard way (in textbooks or pedagogical materials) to estimate the expected returns is to take the mean annual return over the past few years. However, it is surprisingly easy to accidentally let future data worm its way into the estimate, resulting in vastly overinflated performance. One of the early mistakes I made was to optimise an equity portfolio using data from 2006-2010, then testing it from 2006-2015. The problem here is the overlap – the 2006 test has had access to data up to 2010, so performance in the 2006-2010 portion of the backtest will be very good.&lt;/p&gt;

&lt;p&gt;To be fair, this is often quite an obvious mistake to diagnose, because your portfolios will outperform the benchmark by a ridiculous margin and you will realise that something is wrong. But there are also more subtle ways that you can include future data (e.g. survivorship bias), in which case it may not be so clear.&lt;/p&gt;

&lt;h2 id=&quot;2-be-extra-careful-about-expected-returns&quot;&gt;2. Be extra careful about expected returns&lt;/h2&gt;

&lt;p&gt;Despite their strong theoretical guarantees, efficient frontier portfolios often have poor real-life performance owing to estimation errors in the inputs.&lt;/p&gt;

&lt;p&gt;Anyone who has ever tried to pick stocks will know how absurd it is to expect that a stock’s mean return over the past few years will be a good indicator of its future returns. Such simple relationships have almost certainly been arbitraged away – I therefore contend that mean historical returns are almost pure noise. The problem with this is that MVO has no way of distinguishing noise and signal, so often what happens is that the optimiser highlights the noise in the input. This is why there is a ‘running joke’ in the portfolio optimisation literature that a mean-variance optimiser is really just an “error maximiser”&lt;sup id=&quot;fnref:michaud&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:michaud&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;

&lt;p&gt;In practice, I have found that standard MVO can perform at least in line with the benchmark for equities, but for other asset classes (particularly those which are primarily driven by speculation, like cryptocurrency), the mean historical return is a poor estimator of future return (at least for medium-long term time horizons). However, in the next section we will see a simple way round this.&lt;/p&gt;

&lt;h2 id=&quot;3-you-can-do-better-than-1n&quot;&gt;3. You can do better than 1/N&lt;/h2&gt;

&lt;p&gt;A significant body of research suggests that, in light of the aforementioned failures of MVO, we should instead use 1/N portfolios&lt;sup id=&quot;fnref:degrappa&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:degrappa&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;2&lt;/a&gt;&lt;/sup&gt; – it is noted that they often beat efficient frontier optimisation significantly in out-of-sample testing.&lt;/p&gt;

&lt;p&gt;However, an interesting paper by &lt;a href=&quot;https://reasonabledeviations.com/notes/papers/defense_optimisation&quot;&gt;Kritzman et al. (2010)&lt;/a&gt; finds that it is not the case that there is anything special about 1/N diversification: it is just that the expected returns are an incredibly poor estimator and any optimisation scheme which relies on them will likely go astray.&lt;/p&gt;

&lt;p&gt;The easiest way to avoid this problem is to not provide the expected returns to the optimiser, and just optimise on the sample covariance matrix instead. Effectively we are saying that although previous returns won’t predict future returns, previous risks might predict future risks. This is intuitively a lot more reasonable – the sample covariance matrix really seems like it should contain a lot of information. Empirical results support this, showing that minimum variance portfolios outperform both standard MVO and 1/N diversification.&lt;/p&gt;

&lt;p&gt;In my own work I have found that the standard minimum variance portfolio is a very good starting point, from which you can try a lot of new things:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Shrinkage estimators on the covariance matrix&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://reasonabledeviations.com/2018/08/15/exponential-covariance/&quot;&gt;Exponential weighting&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Different historical windows&lt;/li&gt;
  &lt;li&gt;Additional cost terms in the objective function (e.g. small-weights penalty)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;4-dont-ignore-rebalance-costs&quot;&gt;4. Don’t ignore rebalance costs&lt;/h2&gt;

&lt;p&gt;When it comes to investment strategy or algorithmic trading, most people leave the “realities” like commission, slippage, and latency to the last stage. However, because of the general high turnover nature of efficient frontier portfolios, this could be quite a dangerous mistake.&lt;/p&gt;

&lt;p&gt;In the context of portfolio management, the two main realities that must be accounted for in all backtests are commission and slippage. Latency is not very important because of the longer time horizons involved. Commissions are not hard to analyse: just multiply your broker’s percentage commission by the turnover. Slippage, on the other, hand is a little bit more subtle (though it is only really an issue in less liquid markets). There are a number of variables that affect slippage, but the important ones are transaction volume and urgency (refer to models such as that of &lt;a href=&quot;https://www.courant.nyu.edu/~almgren/papers/optliq.pdf&quot;&gt;Almgren&lt;/a&gt; for more).&lt;/p&gt;

&lt;p&gt;Specifically, in the case of cryptoasset portfolios, one major problem is the difference in liquidity between bitcoin and a small altcoin like OmiseGo. A portfolio that shifts positions too much among the altcoins may look like it performs well in backtests, but when traded in real life the 0.5% slippage will be a killer.&lt;/p&gt;

&lt;p&gt;Once all transaction costs have been included, you may find that it can be difficult to outperform a buy-and-hold market-cap weighted benchmark. Incidentally, this is another failure with 1/N portfolios: for reasonably volatile assets, there is high turnover at each rebalance and thus a large transaction cost.&lt;/p&gt;

&lt;h2 id=&quot;5-overfitting&quot;&gt;5. Overfitting&lt;/h2&gt;

&lt;p&gt;One thing that I respect a lot about machine learning educators is that they are fundamentally honest about one of the major issues in the subject: overfitting. Essentially, it is possible to encourage most classifiers to wiggle their decision boundary to accommodate all of the training examples at the cost of generalisation ability. This is done by adjusting the hyperparameters (which do things like specify learning rates) and seeing which ones perform better. Hyperparameter tuning is not a bad thing, but it must be done responsibly lest we simply choose the best parameters that fit the particular train/test setup.&lt;/p&gt;

&lt;p&gt;When it comes to standard portfolio optimisation, one doesn’t really have to worry about this because there aren’t any hyperparameters. However, once you start adding different terms to the cost function (each scaled by some parameter), things get a little bit more complicated. For example, let us consider the simple example of minimum variance optimsiation with an L2 regularisation term.&lt;/p&gt;

\[\underset{w}{\text{minimise}} ~ \left\{w^T \Sigma w + \gamma w^T w \right\}\]

&lt;p&gt;The performance of this portfolio varies greatly depending on the choice of $\gamma$. A natural instinct is to run the optimisation for 10 different values of $\gamma$, but if this is not done carefully, we are probably fitting to the noise within the train and test sets.&lt;/p&gt;

&lt;p&gt;Remember that ceteris paribus, a simpler model should be preferred. Black-Litterman, or optimisation with an exotic objective with multiple parameters may be seem to improve performacne on the backtest, but the proof will always be in the out-of-sample pudding. Approach portfolio optimisation like you would any financial machine learning task: with a healthy dose of skepticism.&lt;/p&gt;

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

&lt;p&gt;I still have a lot to learn about portfolio optimisation, particularly with respect to optimisation of higher moments (skew and kurtosis) and things like copula, but the lessons highlighted in this post definitely still apply. If you found this interesting, check out PyPortfolioOpt on &lt;a href=&quot;https://github.com/robertmartin8/&quot;&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;references&quot;&gt;References&lt;/h2&gt;

&lt;div class=&quot;footnotes&quot; role=&quot;doc-endnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:michaud&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;Michaud, R. (1989). “The Markowitz Optimization Enigma: Is Optimization Optimal?” Financial Analysts Journal 45(1), 31–42. &lt;a href=&quot;#fnref:michaud&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:degrappa&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;Demiguel, Victor &amp;amp; Garlappi, Lorenzo &amp;amp; Uppal, Raman. (2009). Optimal Versus Naive Diversification: How Inefficient is the 1/N Portfolio Strategy?. Review of Financial Studies. 22. 10.1093/rfs/hhm075. &lt;a href=&quot;#fnref:degrappa&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;
</content>
 </entry>
 
 <entry>
   <title>Exponential Covariance</title>
   <link href="https://reasonabledeviations.com/2018/08/15/exponential-covariance/"/>
   <updated>2018-08-15T00:00:00+00:00</updated>
   <id>https://reasonabledeviations.com/2018/08/15/exponential-covariance</id>
   <content type="html">&lt;p&gt;For the past few months, I have been doing a lot of research into portfolio optimisation, whose main task can be summarised as follows: is there a way of combining a set of risky assets to produce superior risk-adjusted returns compared to a market-cap weighted benchmark?
The answer of Markowitz (1952) is in the affirmative, with some major caveats. Given the expected returns and the covariance matrix (which encodes asset volatilities and correlations), one can find the combination of asset weights which maximises the Sharpe ratio. But because we don’t know the expected returns or future covariance matrix a priori, we commonly replace these with the mean historical return and sample covariance matrix. The problem is that these are very noisy estimators (especially the mean return), so much so that a significant body of research suggests that a naive diversification strategy (giving each asset equal weight) outperforms most weighting schemes. However, work by &lt;a href=&quot;https://reasonabledeviations.com/notes/papers/defense_optimisation&quot;&gt;Kritzman, Page and Turkington (2010)&lt;/a&gt; affirms the intuition that there must be some information in the sample covariance matrix; accordingly, they observe that minimum variance portfolios can beat 1/N diversification.&lt;/p&gt;

&lt;p&gt;In my own research, I have found this to be true. Minimum variance optimisation and its variants (no pun intended) can significantly outperform both 1/N diversification and a market-cap weighted benchmark. The nice thing about minimum variance optimisation is that success largely depends on how well you can estimate the covariance matrix, which is easier than estimating future returns. The most common methods are&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Sample covariance&lt;/strong&gt; – standard, unbiased and efficient, but it is known to have high estimation error which is particularly dangerous in the context of a quadratic optimiser.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Shrinkage estimators&lt;/strong&gt; – pioneered by &lt;a href=&quot;https://reasonabledeviations.com/notes/papers/ledoit_wolf_covariance&quot;&gt;Ledoit and Wolf&lt;/a&gt;, shrinkage estimators attempt to reduce the estimation error by blending the sample covariance matrix with a highly structured estimator.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Robust covariance estimates&lt;/strong&gt; – estimators that are robust to recording errors, such as Rousseeuw’s &lt;a href=&quot;http://scikit-learn.org/stable/modules/covariance.html#minimum-covariance-determinant&quot;&gt;Minimum Covariance Determinant &lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I have been experimenting with a new alternative, which I call the &lt;strong&gt;exponential covariance matrix&lt;/strong&gt; (to be specific, the exponentially-weighted sample covariance matrix). In this post, I will give a brief outline of the motivation and conceptual aspects of the exponential covariance. However, because I am currently using this professionally, I will be intentionally vague regarding implementation details.&lt;/p&gt;

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

&lt;p&gt;In many technical indicators, we see the use of an exponential moving average (EMA) rather than the simple moving average (SMA). The EMA captures the intuition that recent prices are (exponentially) more relevant than previous prices. If we let $p_0$ denote today’s price, $p_1$ denote yesterday’s price, $p_n$ denote the price &lt;em&gt;n&lt;/em&gt; days ago, the exponentially weighted mean is given by:&lt;/p&gt;

\[\alpha \left[ p_0 + (1-\alpha) p_1 + (1-\alpha)^2 p_2 + \ldots + (1-\alpha)^n p_n + \ldots\right]\]

&lt;p&gt;$\alpha$ parameterises the decay rate ($0 &amp;lt; |\alpha| &amp;lt; 1$): by observation we note that higher $\alpha$ gives more weight to recent results, and lower $\alpha$ causes the exponential mean to tend to the arithmetic mean. Additionally, because $1/\alpha = 1 + (1-\alpha) + (1-\alpha)^2 + \ldots$, we note that ‘weights’ sum to one.&lt;/p&gt;

&lt;p&gt;In practice, we do not compute the infinite sum above. Rather, observing that the weights rapidly become negligible, we limit the calculation to some window. This window is not to be confused with the &lt;em&gt;span&lt;/em&gt; of the EMA, which is another way of specifying the decay rate – a good explanation can be found on the &lt;a href=&quot;https://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.ewm.html&quot;&gt;pandas documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The EMA is useful because it ‘reacts’ to recent data much better than the SMA owing to the exponential weighting scheme, while still preserving the memory of the timeseries.&lt;/p&gt;

&lt;h2 id=&quot;covariance&quot;&gt;Covariance&lt;/h2&gt;

&lt;p&gt;Covariance, like correlation, measures how two random variables &lt;em&gt;X&lt;/em&gt; and &lt;em&gt;Y&lt;/em&gt; move together. Let us think about how we would define this metric. For variables that have high covariance, we would expect that when $x$ is high, so is $y$. When $y$ is low, $x$ should be too. To capture this idea, we can proceed as follows.&lt;/p&gt;

&lt;p&gt;For each pair $(x_i, y_i)$ in the population, we measure how far away $x_i$ and $y_i$ are from their respective means, then multiply these distances together. If these differences have the same sign, i.e $x_i$ is greater than $\bar{x}$ and $y_i$ is greater than $\bar{y}$, there is a positive contribution to the covariance. If $x_i$ is less than $\bar{x}$ but $y_i$ is greater than $\bar{y}$, there is a negative contribution. We can then sum over all observations, and divide by the number of observations to get some kind of ‘average co-variation’. In fact, this is the exact definition of the &lt;strong&gt;population covariance&lt;/strong&gt;:&lt;/p&gt;

\[Cov(x,y) = \frac{1}{N} \sum_{i=1}^N (x_i - \bar{x})(y_i - \bar{y})\]

&lt;p&gt;Please note that in practice you would always use the &lt;strong&gt;sample covariance&lt;/strong&gt; instead, which has a factor of $1/(N-1)$ rather than $1/N$, but this is not something we will worry about here.&lt;/p&gt;

&lt;h2 id=&quot;covariance-of-asset-returns&quot;&gt;Covariance of asset returns&lt;/h2&gt;

&lt;p&gt;One would think that the easiest approach is to take two price series (e.g. stock prices for AAPL and GOOG), then compute the daily percentage change or log returns, before feeding these into a covariance calculation. This does work, and is the standard approach. But in my view, this is carelessly throwing away a good deal of information, because:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Covariance does not preserve the order of observations.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You will get the same covariance whether you provide $(x_2, y_2), (x_{17}, y_{17}), (x_8, y_8), \ldots$ or $(x_1, y_1), (x_2, y_2), (x_3, y_3), \ldots$.&lt;/p&gt;

&lt;p&gt;However, in the case of time series, the order of the returns is of fundamental importance. Thus we need some way of incorporating the sequential nature of the data into the definition of covariance. Fortunately, it is simple to apply our intuition of the EMA to come up with a similar metric for covariance.&lt;/p&gt;

&lt;p&gt;We will rewrite our previous definition as follows:&lt;/p&gt;

\[Cov(x,y) = \frac{1}{N} \left[ (x_1 - \bar{x})(y_1 - \bar{y}) +  (x_2 - \bar{x})(y_2 - \bar{y}) + \ldots +  (x_N - \bar{x})(y_N - \bar{y}) \right]\]

&lt;p&gt;Rather than letting $(x_i, y_i)$ be any observations from the dataset, let us preserve the order by saying that $(x_i, y_i)$ denotes the returns of asset &lt;em&gt;X&lt;/em&gt; and &lt;em&gt;Y&lt;/em&gt; $i$ days ago. Thus $(x_1 - \bar{x})(y_1 - \bar{y})$ specifically refers to the co-variation of the returns yesterday.&lt;/p&gt;

&lt;p&gt;The next step should now be clear. We simply give each co-variation term an exponential weight as follows:&lt;/p&gt;

\[Cov(x,y) = \frac{\alpha}{N} \left[ (x_1 - \bar{x})(y_1 - \bar{y}) +  (1-\alpha)(x_2 - \bar{x})(y_2 - \bar{y}) + \ldots +  (1-\alpha)^N(x_N - \bar{x})(y_N - \bar{y}) \right]\]

&lt;p&gt;Or more simply:&lt;/p&gt;

\[Cov(x,y) = \frac{\alpha}{N} \sum_{i=1}^N (1-\alpha)^{i-1}(x_i - \bar{x})(y_i - \bar{y})\]

&lt;p&gt;And we are done! This simple procedure is all that is required to incorporate the temporal nature of asset returns into the covariance matrix.&lt;/p&gt;

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

&lt;p&gt;This post has presented a modification of the covariance matrix especially suited to time series like asset returns. It is simple to extend this to an exponential covariance matrix and use it in portfolio optimisation – it is reasonable to suggest that this matrix will be positive definite if the sample covariance matrix is positive definite.&lt;/p&gt;

&lt;p&gt;I have used the exponential covariance to great effect regarding portfolio optimisation on real assets. Backtested results have affirmed that the exponential covariance matrix strongly outperforms both the sample covariance and shrinkage estimators when applied to minimum variance portfolios.&lt;/p&gt;

&lt;p&gt;At some stage in future, I will consider implementing this in my portfolio optimisation package &lt;a href=&quot;https://github.com/robertmartin8/PyPortfolioOpt&quot;&gt;PyPortfolioOpt&lt;/a&gt;, but for the time being this post will have to suffice. Please &lt;a href=&quot;https://reasonabledeviations.com/about/&quot;&gt;drop me a note&lt;/a&gt; if you’d like to use the ideas herein for any further research or software.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Update: as of 20/9/18, exponential covariance has been added to PyPortfolioOpt!&lt;/em&gt;&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Stormy Seas for Proof of Work</title>
   <link href="https://reasonabledeviations.com/2018/06/25/51-attack/"/>
   <updated>2018-06-25T00:00:00+00:00</updated>
   <id>https://reasonabledeviations.com/2018/06/25/51-attack</id>
   <content type="html">&lt;p&gt;In this post we will be examining one of the main problems with Proof of Work (PoW) – not the energy inefficiency (as it is debatable how much of a problem this really is), but something more fundamental with the consensus process. In the past couple of months we have seen a number of cryptocurrencies fall victim to 51% attacks. Verge, Bitcoin Gold, ZenCash, and Electroneum are just a few coins that have been targeted, resulting in a total equivalent theft of $5 million (not to mention the subsequent loss in market value of the coins).
&lt;!--more--&gt;&lt;/p&gt;

&lt;p&gt;51% attacks are a basic problem in distributed ledger technology, covered in any crypto 101 course. Essentially, each individual node in a decentralised network is responsible for &lt;strong&gt;validating&lt;/strong&gt;  transactions and optionally submitting a block of these transactions to the blockchain – doing so requires the node to solve hash puzzles (this is why it is called Proof of Work). The beauty of this system is that each node has a say in what happens, proportional to the amount of &lt;strong&gt;hash power&lt;/strong&gt; they contribute – thus the system is a democracy of sorts. However, a natural corollary of this is that any node or group of nodes that achieves a majority of the hash power can ‘outvote’ the rest of the network, allowing them to conduct a 51% attack.&lt;/p&gt;

&lt;p&gt;Standard theory dictates that if there are enough independent nodes on a distributed ledger, we can reap the benefits of democracy while knowing that it would be immensely costly for a malicious party to achieve 51% of the hash power. This may be true for cryptocurrencies with many active nodes (like Bitcoin and Ethereum), but with the proliferation of multitudinous altcoins, we may not be able to say the same for more obscure tokens. It is thus the case that 51% attacks have gone from being a textbook problem to something &lt;em&gt;very real&lt;/em&gt;, actively destroying both reputation and value in the crypto space.&lt;/p&gt;

&lt;p&gt;In this post, we will explore how an attacker might go about conducting a 51% attack, examine the features that make a coin most vulnerable, and comment on prevention and mitigation. An obvious disclaimer: nothing in this post should be construed as a recommendation or a practical guide on conducting a 51% attack.&lt;/p&gt;

&lt;h2 id=&quot;malicious-actors&quot;&gt;Malicious actors&lt;/h2&gt;

&lt;p&gt;It is no secret that cryptocurrency has attracted a large number of malicious parties, ranging from exchange-hackers to phishing scammers. Part of the reason for this is the underlying anonymity/psuedonymity of the system. If someone were to steal one million USD, they would likely require a network of offshore bank accounts to get away with it. However, if you were to steal 200BTC, all you’d have to do is convert it to a privacy coin like monero on any one of the exchanges that doesn’t require KYC, and law enforcement would have a very hard time catching up with you.&lt;/p&gt;

&lt;p&gt;In this post we are dealing with a narrow subset of malicious actors: those who play by the rules. There are multiple ways a 51% attacker can ‘legally’ (in terms of the blockchain protocol) use their majority hash power to behave maliciously, one example being the denial of service to users in a network. But by far the most direct way of benefiting at the cost of others is to &lt;strong&gt;double spend&lt;/strong&gt;, which is the when the same coin is sent to multiple parties in a network.&lt;/p&gt;

&lt;h2 id=&quot;how-a-51-attacker-might-double-spend&quot;&gt;How a 51% attacker might double spend&lt;/h2&gt;

&lt;p&gt;Once an attacker has identified a suitable target (more on this later), this is a rough sketch of how they might profit. Again, this is purely educational and hypothetical – by no means is it a practical guide.&lt;/p&gt;

&lt;p&gt;I will refer to the target coin as TCOIN. Many of these steps also require an anonymous “exit coin” – I will use Monero (XMR) as an example.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Acquire some TCOIN anonymously, e.g. via an offline swap of fiat for XMR then XMR for TCOIN.&lt;/li&gt;
  &lt;li&gt;Set up an account with exchange A and exchange B. Clearly these exchanges should have minimal KYC.&lt;/li&gt;
  &lt;li&gt;Send 1000 TCOIN from your TCOIN address to that of exchange A, then immediately cash it out to XMR.&lt;/li&gt;
  &lt;li&gt;Acquire 51% of the TCOIN network’s hash power, then make a new TCOIN transfer to exchange B. This transaction should be put into a block that orphans the previous block, so although exchange A thinks they have received your TCOIN and you have cashed it out to XMR, in reality it is exchange B that has received the TCOIN.&lt;/li&gt;
  &lt;li&gt;On exchange B, convert TCOIN to XMR and send it to your monero wallet.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In general terms, this describes how the double spend lets you manufacture 1000 TCOIN from thin air (at the expense of the first exchange). An optional additional step is to first short TCOIN, because we have seen that 51% attacks severely reduce public trust in the coin’s development team and tend to lead to a sudden price drop.&lt;/p&gt;

&lt;p&gt;Thus far this has all been theoretical – we have presented a textbook situation of how a double spend might work &lt;em&gt;in theory&lt;/em&gt;. In the next section, we will understand the worrying practical reality of the situation.&lt;/p&gt;

&lt;h2 id=&quot;vulnerability-of-different-coins&quot;&gt;Vulnerability of different coins&lt;/h2&gt;

&lt;p&gt;One would think that there should be a perfect correlation between the market cap of a coin and its overall hash rate – that is, the demand for a coin should be correlated with the ‘strength’ of the network validation. However, empirically this is not the case. Because of the rise of ill-informed speculation, we see that some coins with a high market cap (a few hundred million USD) have a surprisingly low hash rate. This is the clear yet disturbing message of &lt;a href=&quot;https://www.crypto51.app/&quot;&gt;crypto51.app&lt;/a&gt;, a simple site which displays the cost of becoming a 51% attacker (over 1 hour) for different PoW cryptocurrencies.&lt;/p&gt;

&lt;p&gt;I submit that there are four main features an attacker would look for in an ideal target coin, apart from the obvious factor of having a smaller hash rate (and thus a lower attack cost).&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;High market cap (as a proxy for interest/liquidity in the coin)&lt;/li&gt;
  &lt;li&gt;Low transaction time (i.e very few block confirmations)&lt;/li&gt;
  &lt;li&gt;Support on KYC-free exchanges&lt;/li&gt;
  &lt;li&gt;A common hashing algorithm, or at least one that is supported on a mining marketplace like &lt;a href=&quot;https://www.nicehash.com/&quot;&gt;NiceHash&lt;/a&gt;. The advantage of this for an attacker is that they can just rent hash power anonymously rather than having to acquire hardware.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Using python’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;requests&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;beautifulsoup&lt;/code&gt;, I scraped the data from crypto51 in order to do my own analysis (available on a &lt;a href=&quot;https://nbviewer.Jupyter.org/github/robertmartin8/RandomWalks/blob/master/51attack.ipynb&quot;&gt;Jupyter notebook&lt;/a&gt;) of which coins were most vulnerable:&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;nn&quot;&gt;requests&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;bs4&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;BeautifulSoup&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;r&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;requests&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;https://www.crypto51.app/&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;soup&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;BeautifulSoup&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;lxml&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;p&quot;&gt;[]&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;headers&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;table&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;soup&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;find&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'table'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;column_names&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;table&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;find&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'thead'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;find_all&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'th'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;headers&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;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;strip&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;i&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;column_names&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;rows&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;table&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;find&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'tbody'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;find_all&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'tr'&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;row&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rows&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;n&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;strip&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;i&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;row&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;find_all&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'td'&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;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;df&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;n&quot;&gt;DataFrame&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;n&quot;&gt;columns&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;headers&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;I then processed the data and generated a plot of the attack cost versus the market cap, coloured by NiceHash-ability (all log-transformed). This is very easy to do using pandas plotting:&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;df&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;plot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;scatter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Log Market Cap&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Log Attack Cost&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; 
                &lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Log NiceHash&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;colormap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;plasma&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;p&gt;In a graph like this, a target coin should be as close to the lower-right quadrant as possible (high market cap but low attack cost). The lighter the datapoint, the easier it is to attack via NiceHash, which may or may not be important to an attacker.&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/51attack/attackcost-mcap.png&quot; style=&quot;width:100%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;A linear trendline fits the data with $R^2 = 0.65$, and because a straight line on log-log axes corresponds to a power law relationship, we can calculate the coefficients as follows:&lt;/p&gt;

\[\text{1h attack cost} = 8.3 \times 10^{-5} \cdot (\text{mcap})^{0.92}\]

&lt;p&gt;Actually, it is not the general relationship that matters but rather the specific outliers – altcoins below the trendline are those with especially low attack costs relative to their market cap.&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/51attack/attackcost-mcap-2.png&quot; style=&quot;width:100%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;The top portion is self explanatory. In the bottom-left quadrant are coins that are easy to attack, but also have such small market caps that they are likely not worth attacking (e.g. very poor exchange support). I thus determine the danger zone to be coins with slightly higher market caps (in the order of USD 10 million to 100 million). However, I happened to notice that some of the recently attacked altcoins (listed in the introduction) formed a narrow band slightly above my danger zone. Perhaps this is a liquidity sweet-spot that makes it easier for attackers to exit onto another exchange, or perhaps it’s a coincidence.&lt;/p&gt;

&lt;p&gt;In any case, if I were holding something like BTCP (Bitcoin Private), I’d be a little bit worried. It has a market cap of more than \$200 million, and reasonably liquid trading pairs, but costs less than \$500 an hour to attack. If the attacker were unwilling to set up hardware, they might prefer to target something like EMC2 (Einsteinium), which has a higher NiceHash-ability.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;EDIT: 3 months after writing this post, BTCP was 51%-attacked!! I hope this post had nothing to do with it.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Now, in a perfectly efficient market, one would imagine that the possible financial benefit from conducting a 51% attack would not outweigh the financial costs. However, recent attacks show that this is clearly not the case (especially because one can now short the target coin prior to the attack). So there is a clear problem in the crypto markets.&lt;/p&gt;

&lt;h3 id=&quot;what-can-we-do-about-it&quot;&gt;What can we do about it?&lt;/h3&gt;

&lt;p&gt;An obvious solution is to ditch PoW and go with Proof of Stake (PoS). Much easier said than done. Arguably the most mature PoS development effort is that of Ethereum, in the form of Casper. Yet despite the undeniable talent of the dev team, solving the &lt;a href=&quot;https://ethereum.stackexchange.com/questions/2402/what-exactly-is-the-nothing-at-stake-problem&quot;&gt;nothing-at-stake problem&lt;/a&gt; and ironing out the wrinkles in the implementation is not proving to be a straightforward task. Delegated PoS may be a stepping stone, but the concessions with regard to decentralisation may be a bit off-putting. So assuming that PoW is still the de-facto consensus mechanism, what can the ecosystem do to reduce 51% attacks? Here are some ideas.&lt;/p&gt;

&lt;p&gt;Speculators/investors should consider the hash rate of any PoW coins they are looking to invest in, and be aware that a low hash rate makes that blockchain vulnerable for a 51% attack – certainly not good for their investment.&lt;/p&gt;

&lt;p&gt;New PoW altcoins should &lt;em&gt;not&lt;/em&gt; use the same hashing algorithm as big coins, even if it’s easier to implement – a large miner can just point their hash power at the new blockchain and immediately become a majority. Dev teams for these projects should implement programmatic checks for 51% attacks: a quick response can be critical. Fiat reserves should be maintained, which can quickly be used to add hashing power to the network if a potential 51% attack is detected.&lt;/p&gt;

&lt;p&gt;Exchanges should be wary of what tokens they list, and should increase the required block confirmations for transactions, making it more costly for attackers to rewrite the recent history of a blockchain. Yes, this would make it slightly more inconvenient for users, but 51% attacks can directly lead to material losses.&lt;/p&gt;

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

&lt;p&gt;Proof of Work is a genius solution to the distributed consensus problem, and I often have to remind myself just how amazing the original bitcoin protocol is. But I believe that in PoW, 51% attacks &lt;em&gt;should be possible&lt;/em&gt; – they are a direct consequence of democracy. For large decentralised networks like that of bitcoin or Ethereum this is not a problem, but owing to the speculative interest in cryptocurrencies, the demand for some altcoins has separated entirely from the security of their network, resulting in a kind of arbitrage which allows malicious actors to profit by conducting a double spending 51% attack.&lt;/p&gt;

&lt;p&gt;This post’s analysis of coin vulnerability has not been perfect: the main flaw is our use of market cap as a proxy for the profitability of a 51% attack. In reality, the profitability is a function of block confirmation times and exchange liquidity. Market cap is arguably correlated with the two, but it is not as direct.&lt;/p&gt;

&lt;p&gt;I don’t claim to have all the solutions to the problem of 51% attacks, but it is evident that no single party can solve the issue alone. Until the different players (miners, users, exchanges, investors) in the ecosystem contribute their part, coins with low hash rates will continue to be low hanging fruit for the aspiring 51% attacker; I’d wager that we’ll see a few more big attacks before the year ends.&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>Evolving cellular automata to solve problems, part 2</title>
   <link href="https://reasonabledeviations.com/2018/06/01/evolving-automata-2/"/>
   <updated>2018-06-01T00:00:00+00:00</updated>
   <id>https://reasonabledeviations.com/2018/06/01/evolving-automata-2</id>
   <content type="html">&lt;p&gt;We will be picking up where the &lt;a href=&quot;/2018/05/25/evolving-cellular-automata/&quot;&gt;previous post&lt;/a&gt; left off. As a brief summary, we are attempting to replicate the results of &lt;a href=&quot;https://reasonabledeviations.com/notes/papers/evolving_cellular_automata&quot;&gt;&lt;em&gt;Evolving Cellular Automata with Genetic Algorithms&lt;/em&gt;&lt;/a&gt; (Mitchell, Crutchfield and Das 1996), dealing with the density classification task for 1D binary cellular automata (CAs). To put it simply, we are trying to design a ruleset such that the final configuration of a cellular automaton after &lt;em&gt;M&lt;/em&gt; iterations is either all 1s or all 0s depending on which class was more common in the initial state. The caveat is that each cell in the universe can only make its decision based on the three neighbours to the left and right. 
&lt;!--more--&gt;&lt;/p&gt;

&lt;p&gt;We examined the failures of the majority ruleset, an ‘obvious’ solution in which each cell updates to the local majority among its neighbourhood, and thus see the need for something more sophisticated. At the same time, we saw that the space of possible solutions is in the order of $10^{38}$, meaning that any kind of brute force method will not be feasible. This invites the use of &lt;strong&gt;genetic algorithms&lt;/strong&gt; (GAs), with which we could evolve a CA to perform this task without ever explicitly devising a ruleset.&lt;/p&gt;

&lt;h2 id=&quot;genetic-algorithms-in-the-context-of-cas&quot;&gt;Genetic algorithms in the context of CAs&lt;/h2&gt;

&lt;p&gt;A complete treatment of genetic algorithms is not in the scope of this post (the &lt;a href=&quot;https://en.wikipedia.org/wiki/Genetic_algorithm&quot;&gt;wikipedia page&lt;/a&gt; is a good start), but I will attempt to cover the salient points:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;We start with a &lt;strong&gt;population&lt;/strong&gt; of different possible &lt;em&gt;rulesets&lt;/em&gt;. This is a very important point – the individuals in the population are &lt;em&gt;not&lt;/em&gt; the cellular automata, they are what we are trying to optimise, which in this case is the ruleset.&lt;/li&gt;
  &lt;li&gt;Rulesets in the population have different &lt;strong&gt;fitnesses&lt;/strong&gt;, corresponding to how well they solve the $\rho_c = 1/2$ task. We will define the fitness &lt;em&gt;F&lt;/em&gt; to be the proportion of random ICs on which the ruleset succeeds (becomes all 1 or all 0 correctly). So if we test a ruleset on 100 random ICs and 35 are classified correctly, $F = 0.35$&lt;/li&gt;
  &lt;li&gt;In each &lt;strong&gt;generation&lt;/strong&gt;, we compute the fitness for each ruleset in the population. The fittest rulesets (the &lt;strong&gt;elites&lt;/strong&gt;) are cloned to the next generation, but we also &lt;strong&gt;crossover&lt;/strong&gt; (i.e breed) these rulesets to form offspring, which are then &lt;strong&gt;mutated&lt;/strong&gt; before joining the next generation.&lt;/li&gt;
  &lt;li&gt;Over many generations, we should see the fitness increase, as the traits of the favourable rulesets are passed onto the next generation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The GA acts as a more efficient way to traverse the huge space of possible rulesets in search of one that can suitably solve the density classification task – it does &lt;em&gt;not&lt;/em&gt; guarantee that the global optimum will be reached.&lt;/p&gt;

&lt;h2 id=&quot;implementation&quot;&gt;Implementation&lt;/h2&gt;

&lt;p&gt;For the most part, the functions used in this post are the same as in part 1, though took care to make obvious optimisations because the evolution of the CAs will involve repeated calls of the same functions.&lt;/p&gt;

&lt;p&gt;The initial population consists of random rulesets. To generate a single random ruleset, we zip the the rule inputs (7-bit binary strings) with random 1s or 0s which are obtained from the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;uniform_random_binary&lt;/code&gt; function we defined. For convenience, we can uniquely refer to a given ruleset with a hexadecimal ID, which is made by converting the binary string of all rule outputs (assuming ordered inputs) into hexadecimal.&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;random_ruleset&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;n_rules&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;rule_values&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;uniform_random_binary&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;n_rules&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;hex_id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;hex&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rule_values&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&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;hex_id&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;zip&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rule_keys&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;n_rules&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rule_values&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;To generate the initial population of 100 (or more generally, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;n_ic&lt;/code&gt;) ICs, it is not as simple as running &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;random_ruleset&lt;/code&gt; 100 times because we want exactly half of the ICs to have a majority of 1s.&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;generate_random_ICs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;universe_size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;n_ic&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;n&quot;&gt;ics&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;cutoff&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;int&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;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;universe_size&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;c1&quot;&gt;# Exactly half of the initial conditions will have p &amp;lt; 0.5
&lt;/span&gt;    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;n_ic&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;//&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;minority&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;uniform_random_binary&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;universe_size&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;n&quot;&gt;cutoff&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;majority&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;uniform_random_binary&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;universe_size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cutoff&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; 
                                         &lt;span class=&quot;n&quot;&gt;universe_size&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;n&quot;&gt;ics&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;minority&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;majority&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;ics&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Prior to implementing crossover and mutation, we will define a helper function that converts a hex ID into a ruleset, by converting the hexadecimal to binary and zipping this with a list of 7-bit rule inputs:&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;hex_to_ruleset&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;hex_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;n_rules&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;128&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;rule_values&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;bin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;hex_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:].&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;zfill&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;n_rules&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;nb&quot;&gt;dict&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;zip&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rule_keys&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;n_rules&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rule_values&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;As discussed in the paper, crossover involves pointwise swaps between two parent rulesets, and mutation involves randomly switching bits.&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;crossover_and_mutate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;hex1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;hex2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;n_rules&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;128&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; 
                         &lt;span class=&quot;n&quot;&gt;n_crossover_points&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;n&quot;&gt;n_mutations&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;bin1&lt;/span&gt; &lt;span class=&quot;o&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;bin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;hex1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:].&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;zfill&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;n_rules&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;bin2&lt;/span&gt; &lt;span class=&quot;o&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;bin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;hex2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:].&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;zfill&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;n_rules&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# Crossover: get indices of unequal, then
&lt;/span&gt;    &lt;span class=&quot;c1&quot;&gt;# randomly select an index to swap
&lt;/span&gt;    &lt;span class=&quot;n&quot;&gt;unequal_indices&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;i&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;digit&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;enumerate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bin1&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;bin2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&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;digit&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;nb&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;unequal_indices&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;n_crossover_points&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;crossover_points&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;random&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sample&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;unequal_indices&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; 
                                         &lt;span class=&quot;n&quot;&gt;n_crossover_points&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;c&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;crossover_points&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;bin1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;c&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;bin2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# Mutation
&lt;/span&gt;    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;n_mutations&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;idx&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;random&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;randrange&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;n_rules&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;# Flip bit
&lt;/span&gt;        &lt;span class=&quot;n&quot;&gt;bin1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;idx&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;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ow&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;bool&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bin1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;idx&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;nb&quot;&gt;hex&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bin1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&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;h3 id=&quot;metaparameters&quot;&gt;Metaparameters&lt;/h3&gt;

&lt;p&gt;As with most optimisation methods, genetic algorithms have a number of metaparameters that can drastically change the resulting behaviour, for example the number of generations, the mutation rate, the size of the initial population of chromosomes, etc.&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;GEN&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;50&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;N_CHROMOSOMES&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;c1&quot;&gt;# number of rulesets in each generation
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;N_CROSSOVER_POINTS&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;n&quot;&gt;N_MUTATIONS&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In addition, we shouldn’t forget the CA parameters:&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;N&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;59&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# universe size
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;MAX_ITER&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;c1&quot;&gt;# max number of iterations to perform the task
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;N_IC&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;c1&quot;&gt;# number of ICs each ruleset is tested on
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;N_RULES&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;128&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# number of rules in each ruleset
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;These are mostly similar to the choices of Mitchell et al., though I have opted for fewer generations and a much smaller universe (they use 100 and 149 respectively), which should be faster to compute and serve as a proof-of-concept.&lt;/p&gt;

&lt;p&gt;It is important to note that genetic algorithms can be quite sensitive to initial conditions, which is why we will parameterise each run by a random seed.&lt;/p&gt;

&lt;h3 id=&quot;evolution&quot;&gt;Evolution&lt;/h3&gt;

&lt;p&gt;We are now ready to implement the actual genetic algorithm. The first thing to do is to set the random seed and generate the initial population. We will use a dictionary, with the keys being hex IDs and values being the rulesets.&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;random&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;seed&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;n&quot;&gt;population&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;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;N_CHROMOSOMES&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ruleset&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;random_ruleset&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;N_RULES&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;population&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;id&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;ruleset&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In each generation, we will first compute the fitness of each ruleset on 100 random ICs:&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;generation_ics&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;generate_random_ICs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;N&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;N_IC&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;fitness_log&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;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;hex_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;chromosome&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;population&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;items&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# Evaluate performance on random ICs.
&lt;/span&gt;    &lt;span class=&quot;n&quot;&gt;fitness&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;config&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;generation_ics&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;# Count the majority
&lt;/span&gt;        &lt;span class=&quot;n&quot;&gt;majority&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;1&quot;&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;nb&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;config&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;2&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;# Iterate automaton
&lt;/span&gt;        &lt;span class=&quot;n&quot;&gt;hist&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;iterate_automata&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;chromosome&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;MAX_ITER&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; 
                                &lt;span class=&quot;n&quot;&gt;verbose&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;c1&quot;&gt;# If the CA has classified correctly, increment fitness
&lt;/span&gt;        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;hist&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;o&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;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;majority&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;nb&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;fitness&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;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;hex_id&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;hex_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;  Fitness&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fitness&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;fitness_log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;hex_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fitness&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;Next, we determine who are the &lt;em&gt;elites&lt;/em&gt; (the top 20 rulesets):&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;c1&quot;&gt;# Find top 20 in population
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fitness_log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sort&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;lambda&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;x&lt;/span&gt;&lt;span class=&quot;p&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;n&quot;&gt;reverse&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;elites&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;i&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;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fitness_log&lt;/span&gt;&lt;span class=&quot;p&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;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Generation best:&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fitness_log&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;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Least fit elite:&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fitness_log&lt;/span&gt;&lt;span class=&quot;p&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;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;As suggested by the paper, we will copy these elites directly to the next generation, then form the rest of the population by breeding these elites with replacement.&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;c1&quot;&gt;# Copy the top 20 to the next generation
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;new_population&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;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;elites&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;new_population&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&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;hex_to_ruleset&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Crossover and mutate random pairs of elites
&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;N_CHROMOSOMES&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;parent1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;parent2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;random&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sample&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;elites&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; 
    &lt;span class=&quot;n&quot;&gt;child_hex&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;crossover_and_mutate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;parent1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;parent2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; 
                                     &lt;span class=&quot;n&quot;&gt;N_CROSSOVER_POINTS&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; 
                                     &lt;span class=&quot;n&quot;&gt;N_MUTATIONS&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;new_population&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;child_hex&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;hex_to_ruleset&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;child_hex&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;population&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;new_population&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;We then repeat the above for many generations, and hopefully should see the fitnesses increase.&lt;/p&gt;

&lt;h2 id=&quot;initial-observations&quot;&gt;Initial observations&lt;/h2&gt;

&lt;p&gt;Judging by the success of the previous post, I was expecting the script to work immediately and yield interesting results. Alas, programming is rarely so simple. The genetic algorithm was slow (I’m used to the 5-10 seconds it takes to train most &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sklearn&lt;/code&gt; models), but what is worse, it &lt;em&gt;was not able to solve the task&lt;/em&gt;. After 50 generations, the GA has not managed to beat random guessing:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-txt&quot;&gt;Gen 49 
==========
hex_id 0x455d652de3a5534054ed51a12093d25   Fitness 50
hex_id 0x455d652de3a5534054ec51a12093d27   Fitness 50
hex_id 0x455d652de3a5534054ed51212093d25   Fitness 50
hex_id 0x455d652de3a55b40146d51a12093d27   Fitness 50
...
Generation best: ('0x455d652de3a5534054ed51a12093d25', 50)
Least fit elite: ('0x455d652de3a55b40146d51a12093d277', 50)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;A closer analysis revealed that many of the final rulesets corresponded to the naïve strategy of mapping any possible input to a 1, which results in the ruleset being correct about 50% of the time.&lt;/p&gt;

&lt;p&gt;It should be noted that Mitchell et al. trained their genetic algorithm across 300 different runs, so it could be the case that this was one of the unlucky runs in which the GA fails to learn. But testing multiple random seeds yielded no further improvements.&lt;/p&gt;

&lt;h2 id=&quot;a-possible-fix&quot;&gt;A possible fix&lt;/h2&gt;

&lt;p&gt;I hypothesised that the initial population contained too many rulesets that had a clear majority of 1s or 0s, disproportionately mapping their input to one of the two regardless of the IC. Remember that this was &lt;em&gt;by design&lt;/em&gt;, we chose to sample rulesets and ICs from a distribution uniform over the density of 1s rather than the unbiased distribution which is binomial over the density of 1s. In light of the poor empirical results, I modified the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;random_ruleset&lt;/code&gt; function to use the unbiased distribution instead:&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;random_ruleset2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;n_rules&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;random_binary&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;bin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;random&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;randint&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;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;n_rules&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;n&quot;&gt;rule_values&lt;/span&gt; &lt;span class=&quot;o&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;random_binary&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:].&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;zfill&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;n_rules&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;hex_id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;hex&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rule_values&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&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;hex_id&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;zip&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rule_keys&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;n_rules&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rule_values&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;Because it seemed that I would have to test multiple parameters, I also refactored the GA script into a function:&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;evolve_automata&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;59&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;n_crossover_points&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;n&quot;&gt;n_mutations&lt;/span&gt;&lt;span class=&quot;o&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;random_seed&lt;/span&gt;&lt;span class=&quot;o&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;n&quot;&gt;n_ic&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;n&quot;&gt;n_gen&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;50&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; 
                &lt;span class=&quot;n&quot;&gt;ruleset_generator&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&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;Using this new ruleset generator, we are much less likely to have naïve outputs of all 1s or all 0s. My guess was that although this would result in the GA starting with poorer fitnesses, they would not get ‘stuck’ with naïve random-guessing strategies. However, testing revealed that fitness still converged to 50%, just at a much slower rate.&lt;/p&gt;

&lt;p&gt;To combat this, I reasoned that the simplest way to escape local optima is to pump up the mutation rate in the hope that elites will be found that beat random guessing, and these will pass on their traits to the next generation.&lt;/p&gt;

&lt;p&gt;Finally, we achieved some progress. I noticed that the combination of a high mutation rate and an unbiased random ruleset meant that the GA didn’t always get stuck with a 50% fitness. For &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;N_MUTATIONS = 6&lt;/code&gt;, tested over 20 different seeds, mean elite fitnesses in the last 10 generations were:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-txt&quot;&gt;[83.5, 52.2, 92.6, 51.2, 51.1, 50.0, 51.5, 51.5, 51.7, 79.2, 
 83.1, 51.7, 73.5, 57.2, 51.6, 51.2, 52.1, 50.0, 78.7, 82.9]
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Eight of the 20 runs resulted in fitnesses beyond the 50% level, however only one of the 20 beat 90%. This falls very far short of Mitchell et al’s claim to have at least 90% fitness on all 300 of the runs, but it is certainly progress.&lt;/p&gt;

&lt;p&gt;Interestingly, increasing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;N_MUTATIONS&lt;/code&gt; to 12 produced worse results, with the maximum mean elite fitness being 80.2 and only six of the 20 runs resulting significantly beating 50%. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;N_MUTATIONS=18&lt;/code&gt; does even worse, with only one of the 20 beating 50%. I think this is because having many mutations reduces the impact of crossover (which is only at a single point), so we don’t get the benefits of ‘meiosis’.&lt;/p&gt;

&lt;h2 id=&quot;analysis-of-evolved-cas&quot;&gt;Analysis of evolved CAs&lt;/h2&gt;

&lt;p&gt;We will examine one of the rulesets which achieved a fitness of above 90%. For some different initial configurations, this is what we observe:&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/automata_evolution/analysis.png&quot; style=&quot;width:100%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;Remember that purple is alive and white is dead; I have displayed the actual majority class above each subplot. Thus the fifth subplot represents an error, because despite the majority of 1s, the final state is all 0s.&lt;/p&gt;

&lt;p&gt;As in the paper, the ruleset works by quickly reaching a steady state of all 0s unless large blocks of 1s are found – it is not really the sophisticated emergent behaviour that I was hoping to see. The only exception is the first subplot, in which some semblance of the particle strategy is achieved.&lt;/p&gt;

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

&lt;p&gt;Though we did achieve some interesting results, I don’t think that this exploration overall was a success. I could not replicate the results of Mitchell et al., even using a simple universe of 59 cells rather than 149. The default setup used in the paper kept getting stuck at 50% fitness, and it was only after I deviated from their suggestions that I managed to do better.&lt;/p&gt;

&lt;p&gt;I have become somewhat disillusioned regarding the scientific value of the paper (though I still find it fascinating). The sophisticated particle strategy discovered by Mitchell et al. is remarkable, yet they seem to downplay the fact that it was observed only in a few of the 300 runs of the genetic algorithm. This is hardly reproducible, and really feels to me like grabbing at patterns in the random darkness.&lt;/p&gt;

&lt;p&gt;Additionally, the evolution of CAs with genetic algorithms is a very slow process (I had to leave my code running over a few nights), and the results are very inconsistent. I can only imagine how long it would have taken for Mitchell et al. to conduct 300 runs with a universe size of 149 (before the turn of the millennium!). This severely limits the practical applications, and the prospect that evolved CAs could tackle more complex tasks like image processing (as suggested in the paper’s conclusion) seems chimerical.&lt;/p&gt;

&lt;p&gt;Nevertheless, I am happy to have attempted to replicate the results, because it has been a while since I’ve played around with genetic algorithms. It was also some extra practice with python’s standard language features, of which I make extensive use. Perhaps some time in future I may realise that my implementation had a flaw and author a part 3, but for now I’m content to stay in this local optimum.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The code for this post, along with plots and data, is available as a &lt;a href=&quot;https://github.com/robertmartin8/RandomWalks/blob/master/EvolvingCellularAutomata.ipynb&quot;&gt;Jupyter notebook&lt;/a&gt; on GitHub.&lt;/em&gt;&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>Evolving cellular automata to solve problems, part 1</title>
   <link href="https://reasonabledeviations.com/2018/05/25/evolving-cellular-automata/"/>
   <updated>2018-05-25T00:00:00+00:00</updated>
   <id>https://reasonabledeviations.com/2018/05/25/evolving-cellular-automata</id>
   <content type="html">&lt;p&gt;Recently I finished reading &lt;a href=&quot;https://www.goodreads.com/book/show/5597902-complexity&quot;&gt;&lt;em&gt;Complexity: A Guided Tour&lt;/em&gt;&lt;/a&gt;, by Melanie Mitchell, which reminded me a lot of &lt;a href=&quot;https://en.wikipedia.org/wiki/G%C3%B6del,_Escher,_Bach&quot;&gt;&lt;em&gt;Gödel, Escher Bach&lt;/em&gt;&lt;/a&gt; (indeed, the book is dedicated to Douglas Hofstadter). It has reminded me that &lt;strong&gt;emergence&lt;/strong&gt; is an incredibly fascinating concept – simple individual units somehow coming together to result in complex behaviour that cannot really be explained in terms of the components. In this post, we analyse the emergent behaviour of a cellular automaton and attempt to use it to solve a simple problem.&lt;!--more--&gt;&lt;/p&gt;

&lt;p&gt;One of the first examples of emergence discussed in the book is the army ant: even in groups of 100, these ants are completely useless and will walk in circles until they die of exhaustion. But in a colony, they show remarkable sophistication and a “superintelligence”. I would highly recommend the article &lt;a href=&quot;https://reasonabledeviations.com/notes/papers/army_ants_collective_intelligence&quot;&gt;&lt;em&gt;Army Ants: a Collective Intelligence&lt;/em&gt;&lt;/a&gt;, by Nigel Franks, and this &lt;a href=&quot;https://www.youtube.com/watch?v=JsfiUR0ZzLw&quot;&gt;short David Attenborough clip&lt;/a&gt; for more information. Apart from ants, Mitchell also makes consistent reference to &lt;strong&gt;cellular automata&lt;/strong&gt;, the study of which really led to the growth of complexity science as a distinct field.&lt;/p&gt;

&lt;p&gt;Cellular automata (CAs) have managed to divert the attention of many great scientists – Stephen Wolfram considers them the foundation of a “new kind of science”. I am clearly not one of these great thinkers, because I have never really understood what the fuss is all about. Although I have written about Conway’s Game of Life (a special case of CAs) in a &lt;a href=&quot;/2017/06/10/conway-python/&quot;&gt;previous post&lt;/a&gt;), I admit that I was more interested in the implementation than in the CAs.&lt;/p&gt;

&lt;p&gt;But &lt;em&gt;Complexity&lt;/em&gt; has given me a fascinating new perspective. In Chapter 11, Mitchell introduces the idea that CAs can be designed (or should I say evolved) to solve computational tasks. Although it is widely known that the Game of Life is Turing-complete (arguably more interesting than Mitchell’s idea), this always felt far too abstract for me to appreciate – the proof relies on a demonstration that some logic gates can be emulated, rather than actually constructing a nontrivial program. However, in &lt;a href=&quot;https://reasonabledeviations.com/notes/papers/evolving_cellular_automata&quot;&gt;&lt;em&gt;Evolving Cellular Automata with Genetic Algorithms&lt;/em&gt;&lt;/a&gt; (the journal article on which Chapter 11 is based), Mitchell and coauthors tangibly demonstrate that genetic algorithms can be used to evolve a CA to perform a simple but nontrivial task.&lt;/p&gt;

&lt;p&gt;The paper is very interesting, but does not come with source code (in their defense, it was written in 1996 and they refer to the internet as the “World Wide Web”). I think that their work deserves a modern recreation and analysis, with the benefit of intuitive code in python and more processing power.&lt;/p&gt;

&lt;p&gt;Because this is quite a large topic, I will split it into two parts. Today’s post will set up the problem, dealing with notation and terminology, as well as discussing the nature of the CAs considered. We will leave it to a future post to actually apply genetic algorithms to evolve CAs.&lt;/p&gt;

&lt;h2 id=&quot;contents&quot;&gt;Contents&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;#a-quick-introduction-to-cellular-automata&quot;&gt;A quick introduction to cellular automata&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#computation-with-cellular-automata&quot;&gt;Computation with cellular automata&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#the-majority-ruleset&quot;&gt;The majority ruleset&lt;/a&gt;
    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#generating-the-majority-ruleset&quot;&gt;Generating the majority ruleset&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#generating-a-random-initial-configuration&quot;&gt;Generating a random initial configuration&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#iterating-an-automaton&quot;&gt;Iterating an automaton&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#space-time-diagrams&quot;&gt;Space-time diagrams&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#tying-it-together&quot;&gt;Tying it together&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#conclusion&quot;&gt;Conclusion&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;a-quick-introduction-to-cellular-automata&quot;&gt;A quick introduction to cellular automata&lt;/h2&gt;

&lt;p&gt;A cellular automaton can be thought of as a set of units (cells) whose states change based on simple rules. In the Game of Life, the &lt;em&gt;universe&lt;/em&gt; is a 2D grid of binary cells (either alive or dead), with the update rules as follows:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;A living cell (black) will die if it has fewer than two live neighbours or more than three live neighbours – roughly corresponding to underpopulation and overpopulation respectively&lt;/li&gt;
  &lt;li&gt;A dead cell (white) will come alive if it has exactly three living neighbours (captures the idea of reproduction).&lt;/li&gt;
&lt;/ul&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/conway/beacon.png&quot; style=&quot;width:90%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;Note that to begin applying the rules, some &lt;strong&gt;initial condition&lt;/strong&gt; must be specified – in practice, small changes in this starting point can drastically affect the resulting behaviour.&lt;/p&gt;

&lt;p&gt;For our purposes we will be dealing with an arguably simpler model: a 1D binary CA. In this case, the universe consists of a row of &lt;em&gt;N&lt;/em&gt; cells, each of which can either be dead (0) or alive (1), and is updated by looking at the &lt;strong&gt;radius&lt;/strong&gt; &lt;em&gt;R&lt;/em&gt; of surrounding cells. So if $R=3$, any update rules must map a 7-bit input (the 3 cells on either side, and the cell itself) to a 1-bit output (whether the cell will be dead or alive). A given rule might look something like this, bearing in mind that the output bit determines the new state of the middle cell:&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/automata_evolution/example_rule.png&quot; style=&quot;width:80%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;A &lt;strong&gt;ruleset&lt;/strong&gt; is the set of mappings between all possible input configurations and their output states. We may as well begin to speak in specific terms referring to $R=3$, in which case a ruleset is a mapping between all possible 7-bit binary strings to a binary output:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Input&lt;/th&gt;
      &lt;th&gt;Output&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;0000001&lt;/td&gt;
      &lt;td&gt;1&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;0000010&lt;/td&gt;
      &lt;td&gt;0&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;…&lt;/td&gt;
      &lt;td&gt;…&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;0100101&lt;/td&gt;
      &lt;td&gt;0&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;0100110&lt;/td&gt;
      &lt;td&gt;1&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;…&lt;/td&gt;
      &lt;td&gt;…&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;1111110&lt;/td&gt;
      &lt;td&gt;1&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;1111111&lt;/td&gt;
      &lt;td&gt;0&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;If we assume that the input is ordered, then as a convenience of notation we can say that a ruleset is equal to the binary string formed by concatenating the output bits – in the above example, this is $10…01…10$.&lt;/p&gt;

&lt;p&gt;It is very important to be comfortable with this concept: we are representing rulesets with binary strings of length 128, because there are 128 possible 7-bit inputs. Later on, we will also be using binary numbers to represent configurations of cells, so please remember to distinguish the two.&lt;/p&gt;

&lt;p&gt;Because we are dealing with long 1D universes, animation is probably not the visualisation tool of choice (though it &lt;a href=&quot;https://github.com/robertmartin8/PyGameofLife&quot;&gt;works very nicely&lt;/a&gt; for 2D). Instead, we will use a &lt;strong&gt;space-time&lt;/strong&gt; diagram, which essentially stacks the generations on top of each other (with time going down the page). Here’s an example, with a simple rule that switches input bits:&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/automata_evolution/spacetime_expl.png&quot; style=&quot;width:60%;&quot; /&gt;
&lt;/center&gt;

&lt;h2 id=&quot;computation&quot;&gt;Computation&lt;/h2&gt;

&lt;p&gt;I am going to conveniently sidestep the issue of properly defining “computation”. As far as this post is concerned, computation is what a computer does – a CA “computes” if it solves some task for a variable input. I would refer you to &lt;em&gt;Complexity&lt;/em&gt; if you’d like a meaningful discussion.&lt;/p&gt;

&lt;p&gt;The paper’s main objective is the &lt;strong&gt;density classification task&lt;/strong&gt;, in which the CA must decide whether the ‘density’ of 1s in the initial configuration is greater than some cutoff proportion. Because density is typically denoted by $\rho$, this is more precisely referred to as the $\rho_c = 1/2$ task.&lt;/p&gt;

&lt;p&gt;To put it simply, the CA must decide whether the initial setup had more living or dead cells (1s or 0s). If there were more 1s, then after some number of iterations, we want the CA to display all 1s. Otherwise, if there were more 0s, we want the CA to display all 0s. An example mapping is shown below:&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/automata_evolution/density_task.png&quot; style=&quot;width:90%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;You may be questioning the difficulty of that task. After all, it’s as easy as iterating over the universe and counting the number of 1s, then determining the output based on this value! But remember the catch: each cell only has access to a fixed neighbourhood of three cells on either side. There is nowhere  we can store the count: each cell can only make a decision based on its neighbours and change its state accordingly.&lt;/p&gt;

&lt;h2 id=&quot;the-majority-ruleset&quot;&gt;The majority ruleset&lt;/h2&gt;

&lt;p&gt;Before we bother with genetic algorithms, it is worth checking how a simple solution performs. For this, we will use the &lt;strong&gt;majority ruleset&lt;/strong&gt;, in which each cell will just change its state to match the majority of its 7-cell neighbourhood.&lt;/p&gt;

&lt;p&gt;Although it is conceptually straightforward, it is at this stage that we need to consider the details of the implementation in python. We will be using strings of binary digits to store configurations; they are efficient and easy to work with. At a high level, we need to do the following:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;generate the majority ruleset (of course we are not going to hard code all 128 rules)&lt;/li&gt;
  &lt;li&gt;generate a random initial configuration (i.e a random binary number)&lt;/li&gt;
  &lt;li&gt;iterate the initial configuration according to the majority ruleset&lt;/li&gt;
  &lt;li&gt;plot the space-time diagram&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here are some of the global constants that we need for now, though we will introduce many more when we get onto genetic algorithms:&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;R&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;c1&quot;&gt;# size of neighbourhood
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;N_RULES&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&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;2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;R&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;c1&quot;&gt;#  number of rules in a ruleset
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;N&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;79&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# number of cells in universe
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The number of rules is $2^7 = 128$ (the number of possible 7-bit inputs). Incidentally, this means that the number of possible rulesets is $2^{128}$ – a clear indication that we will &lt;em&gt;not&lt;/em&gt; be able to find a solution by brute force.&lt;/p&gt;

&lt;h3 id=&quot;generating-the-majority-ruleset&quot;&gt;Generating the majority ruleset&lt;/h3&gt;

&lt;p&gt;We are going to treat rulesets as python dictionaries. This is quite natural, since a ruleset is essentially a bijective mapping between all possible 7-bit inputs (keys) and the output bits (values). We will generate the keys and values separately.&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;rule_keys&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;n_rules&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;radius&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;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;rule_input&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;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;n_rules&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;binary&lt;/span&gt; &lt;span class=&quot;o&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;bin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:].&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;zfill&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;radius&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;n&quot;&gt;rule_input&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;binary&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;rule_input&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This could also be refactored into a one-line generator expression:&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;rule_keys&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;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;bin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:].&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;zfill&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;7&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;i&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;N_RULES&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;But out of respect for Guido I have kept it as a function with a loop inside (which is slightly faster anyway). The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[2:]&lt;/code&gt; is used to slice away the ugly &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0b&lt;/code&gt; that python prepends to a binary number, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;zfill&lt;/code&gt; pads the string with leading zeroes.&lt;/p&gt;

&lt;p&gt;To complete the majority ruleset, we just need to know if there are more 1s or 0s in the input. We will use a bit of python trickery to make this more concise:&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;majority_ruleset&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;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rule_input&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rule_keys&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;N_RULES&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# 1 if the majority of the neighbourhood is 1.
&lt;/span&gt;    &lt;span class=&quot;n&quot;&gt;out&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sum&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;d&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;1&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;d&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rule_input&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;R&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;majority_ruleset&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;update&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rule_input&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;out&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 statement &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(sum(d == &quot;1&quot; for d in rule_input) &amp;gt; R&lt;/code&gt; is True if there are more than 3 living cells in the input (remember that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;R = 3&lt;/code&gt;), and False otherwise. We then cast this to an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;int&lt;/code&gt; (1 or 0) then a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;str&lt;/code&gt; (“1” or “0”), and update the ruleset with this key-value pair. This is an excerpt from the resulting ruleset:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-txt&quot;&gt; {
     '1010010': '0',
     '1010011': '1',
     '1010100': '0',
     '1010101': '1',
     '1010110': '1',
     '1010111': '1',
     '1011000': '0',
     '1011001': '1',
 }
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&quot;generating-a-random-initial-configuration&quot;&gt;Generating a random initial configuration&lt;/h3&gt;

&lt;p&gt;One might think that the best way to generate a random configuration is just to combine &lt;em&gt;N&lt;/em&gt; random bits where &lt;em&gt;N&lt;/em&gt; is the size of the universe. However, consider the density of 1s in such a configuration – it is a (literal) textbook example of a &lt;em&gt;binomial distribution&lt;/em&gt; with $\rho \sim B(N, 0.5)$.&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/automata_evolution/binomial.png&quot; style=&quot;width:60%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;Notice the strong peak at 0.5, meaning that most of the initial configurations will have a roughly equal split of 0s and 1s. This is not ideal for us to explore the density classification task, because we do want to have enough easy cases (i.e clear majorities).&lt;/p&gt;

&lt;p&gt;As such, we will adopt a slightly different strategy:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Randomly choose a density of 1s that we want in the configuration (sampled from a uniform distribution).&lt;/li&gt;
  &lt;li&gt;Form an ordered universe with that number of 1s, and the rest 0s&lt;/li&gt;
  &lt;li&gt;Shuffle the universe&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A small snippet that does this is 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;n&quot;&gt;n_ones&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;random&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;randrange&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;n&quot;&gt;N&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;binary&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;1&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;n_ones&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;0&quot;&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;N&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;n_ones&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;random&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sample&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;binary&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;N&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;We could use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;random.shuffle()&lt;/code&gt; in the third line, but that works in-place (and requires a list), so the chosen solution is cleaner. In the end, I decided to generalise the above slightly, letting you change the range of the uniform sample and specify the length of the output string:&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;uniform_random_binary&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lower&lt;/span&gt;&lt;span class=&quot;o&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;n&quot;&gt;upper&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;c1&quot;&gt;# The defualt upper bound is the length
&lt;/span&gt;    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;upper&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;upper&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;length&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;n_ones&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;random&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;randrange&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;lower&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;upper&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;binary&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;1&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;n_ones&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;0&quot;&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;length&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;n_ones&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;s&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;random&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sample&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;binary&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;length&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;h3 id=&quot;iterating-an-automaton&quot;&gt;Iterating an automaton&lt;/h3&gt;

&lt;p&gt;To remain general, our iteration function should take in a configuration, a ruleset, and a radius. One iteration consists of applying the appropriate rule to each bit in the universe’s configuration, which is done by getting a cell’s neighbours as a 7-bit binary string, then using the ruleset dictionary to find the output bit.&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;single_iteration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ruleset&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;radius&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;next_iteration&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;&quot;&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# Support wrapping by extending either end
&lt;/span&gt;    &lt;span class=&quot;n&quot;&gt;extended_config&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;config&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;radius&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;config&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;radius&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;i&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;radius&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;config&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;radius&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;neighbours&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;extended_config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;radius&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;radius&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;n&quot;&gt;next_iteration&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ruleset&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;neighbours&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;next_iteration&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The complexity in the above implementation arises because we need a circular universe, so we must extend the configuration to allow for ‘wrapping’.&lt;/p&gt;

&lt;p&gt;To iterate multiple times, we repeatedly apply the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;single_iteration()&lt;/code&gt; function:&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;iterate_automata&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;initial&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ruleset&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;n_iter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;radius&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;p&quot;&gt;,&lt;/span&gt;
                     &lt;span class=&quot;n&quot;&gt;verbose&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;config&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;initial&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;history&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;config&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;_&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;n_iter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;config&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;single_iteration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ruleset&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;radius&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;verbose&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;history&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;config&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;history&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This function also keeps track of the CAs history, which is necessary if we want to use a space-time diagram for visualisation.&lt;/p&gt;

&lt;h3 id=&quot;space-time-diagrams&quot;&gt;Space-time diagrams&lt;/h3&gt;

&lt;p&gt;If we run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;iterate_automata()&lt;/code&gt; with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;verbose=True&lt;/code&gt;, it’ll print the universe at each time step, so in a sense that &lt;em&gt;is&lt;/em&gt; a space-time diagram. However, it is fair to ask for something more stylish, for which we will use matplotlib’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;imshow&lt;/code&gt;:&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;plot_spacetime_diagram&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;binary_history&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;filename&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;arr&lt;/span&gt; &lt;span class=&quot;o&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;array&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([[&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;digit&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;digit&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;row&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;row&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;binary_history&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;n&quot;&gt;figure&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;n&quot;&gt;imshow&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;arr&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;s&quot;&gt;&quot;Purples_r&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;n&quot;&gt;filename&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;plt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;show&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;n&quot;&gt;plt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;savefig&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;filename&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;h3 id=&quot;tying-it-together&quot;&gt;Tying it together&lt;/h3&gt;

&lt;p&gt;Now that we have implemented the pieces, testing the majority ruleset is very simple:&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;random&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;seed&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;n&quot;&gt;initial_config&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;uniform_random_binary&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;N&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;majority_history&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;iterate_automata&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;initial_config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                                    &lt;span class=&quot;n&quot;&gt;majority_ruleset&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                                    &lt;span class=&quot;n&quot;&gt;n_iter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;N&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; 
                                    &lt;span class=&quot;n&quot;&gt;radius&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;R&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;plot_spacetime_diagram&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;majority_history&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;Looking at the space-time diagrams for different random seeds shows that in general, the majority ruleset &lt;em&gt;does not&lt;/em&gt; consistently solve the $\rho_c = 1/2$ task:&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/automata_evolution/maj_results.png&quot; style=&quot;width:100%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;The above results are very similar to what Mitchell et al. found (though they use $N = 149$), which gives us a good indication that our implementation thus far has been accurate.&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/automata_evolution/maj_rule_paper.png&quot; style=&quot;width:40%;&quot; /&gt;
&lt;/center&gt;

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

&lt;p&gt;In this post, we have laid the framework for using cellular automata to solve a computational task, namely, classifying the density of living cells in the initial conditions.&lt;/p&gt;

&lt;p&gt;We have examined the failings of a ‘local’ solution that naively treats a cell’s immediate neighbourhood as a proxy for the entire universe, and thus see the need for &lt;em&gt;emergent computation&lt;/em&gt;, in which individual cells somehow learn to appreciate the rest of the universe and come up with a global solution.&lt;/p&gt;

&lt;p&gt;In the &lt;a href=&quot;/2018/06/01/evolving-automata-2/&quot;&gt;next post&lt;/a&gt;, we discuss how genetic algorithms can be applied to &lt;em&gt;evolve&lt;/em&gt; the cellular automata to solve the density classification task, rather than having to supply a hand-crafted rule, or do a brute force search over all possible rulesets (which we have shown to be infeasible). Stay tuned.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Classifying financial time series using Discrete Fourier Transforms</title>
   <link href="https://reasonabledeviations.com/2018/04/19/classifying-timeseries/"/>
   <updated>2018-04-19T00:00:00+00:00</updated>
   <id>https://reasonabledeviations.com/2018/04/19/classifying-timeseries</id>
   <content type="html">&lt;p&gt;A financial time series represents the collective decisions of many individual traders; it seems reasonable to me that the nature of these decisions may differ based on the underlying asset. For example, a company with a higher market cap may be more liquid, and subject to larger individual buy/sell orders including institutional investment.  Thus, there is a case to be made that information such as the market cap of a company should be ‘encoded’ into its price movements. While these characteristics may be difficult to pinpoint on a chart, in principle it may be possible for a machine learning algorithm to find statistical relationships between the time series and the market cap of the company. This post will investigate that claim.
&lt;!--more--&gt;&lt;/p&gt;

&lt;p&gt;Concretely, a raw dataset will be constructed, consisting of 100-day price charts which belong to companies that are respectively in the top and bottom 1000 tickers in the Russell 3000 ordered by market cap. We will then run feature extraction (which is the core of this post), before applying a standard XGBoost classifier.&lt;/p&gt;

&lt;p&gt;This post thus represents an intuitive (if naive) first approximation for classifying whether a given price chart belongs to a stock with high or low market cap. The hypothesis is that this problem is learnable, so the goal is to show that a machine learning model can classify a time series with &lt;strong&gt;greater than 50% accuracy&lt;/strong&gt;. Though accuracy often has flaws as a metric, in this case it is sufficient because the classes will be almost perfectly balanced.&lt;/p&gt;

&lt;h2 id=&quot;a-discussion-on-classifying-time-series&quot;&gt;A discussion on classifying time series&lt;/h2&gt;

&lt;p&gt;Because much of real life data is temporal in nature, classifying and clustering time series has widespread importance, with such applications as:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;speech recognition&lt;/li&gt;
  &lt;li&gt;medical data such as ECGs&lt;/li&gt;
  &lt;li&gt;cyber-kinetic prosthetics: classifying electrical activity of muscles in terms of what action it corresponds to&lt;/li&gt;
  &lt;li&gt;meteorological data&lt;/li&gt;
  &lt;li&gt;seismic data&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Some general approaches to classifying time series are as follows (a thorough survey can be found in &lt;a href=&quot;http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.258.9630&amp;amp;rep=rep1&amp;amp;type=pdf&quot;&gt;Rani &amp;amp; Sikka 2012&lt;/a&gt;):&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Directly clustering with an algorithm like k-Nearest Neighbours. What is important here is a relevant distance metric between two different time series. Euclidean distance may not be sufficient as it has trouble dealing with timeseries that have been shifted slightly (either vertically or horizontally).&lt;/li&gt;
  &lt;li&gt;Hidden Markov Models, which model the timeseries as a Markov Process, whose transition matrix may be used as input to a classifier.&lt;/li&gt;
  &lt;li&gt;LSTM Neural Networks, which are fed in a time series and contain a sequential ‘memory’. The output of a cell can be run through the sigmoid function to convert it into a binary class value.&lt;/li&gt;
  &lt;li&gt;Extracting summary statistics, then passing these results into a standard classifier.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This post will explore the last method. On the surface, it seems to be the most straightforward approach, but the difficulty is in choosing which aspects of the time series should be used. There are existing libraries that can extract a multitude of features, e.g. &lt;a href=&quot;https://github.com/blue-yonder/tsfresh&quot;&gt;tsfresh&lt;/a&gt;, but although it is very likely that there is predictive power somewhere in here, it would be nice to find a simpler (and perhaps faster) approach.&lt;/p&gt;

&lt;p&gt;Thus, I have decided to take the Discrete Fourier Transform (DFT) of the time series, and to use the largest terms as features. Such a method is really under the domain of &lt;strong&gt;spectral analysis&lt;/strong&gt;, a well established subfield of time series analysis. However, we will adopt a rather informal approach. It should be noted that the Fourier Transform certainly shouldn’t be used to forecast the future of a financial time series because of the strong assumptions of periodicity. But it should be able to extract the main (sinusoidal) signals from a time series, which we can then put into a classifier.&lt;/p&gt;

&lt;h2 id=&quot;preparing-the-raw-data&quot;&gt;Preparing the raw data&lt;/h2&gt;

&lt;p&gt;The data used in this project will be:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;keystats.csv&lt;/code&gt;: parsed yahoo finance key statistics from which the latest market cap of securities will be ascertained. In order to reproduce something like this dataset (though only for the S&amp;amp;P500), please refer to my &lt;a href=&quot;https://github.com/robertmartin8/MachineLearningStocks&quot;&gt;repo on GitHub&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;My &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;stock_prices&lt;/code&gt; database, which I discussed in &lt;a href=&quot;/2018/02/01/stock-price-database/&quot;&gt;this post&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After extracting the data from the csv files and MySQL database, I was left with a pandas dataframe &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sorted_tickers_by_mcap&lt;/code&gt;, containing tickers and their market caps, sorted by market cap in ascending order.&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;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Number of tickers with mcap data available:&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
       &lt;span class=&quot;nb&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sorted_tickers_by_mcap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sorted_tickers_by_mcap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;head&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sorted_tickers_by_mcap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tail&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-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Number of tickers with mcap data available: 3382
Ticker
heat      67830.0
aryx     103730.0
gmet     336260.0
coco    1090000.0
dvox    1140000.0
Name: Market Cap, dtype: float64
Ticker
xom      3.632700e+11
msft     4.304200e+11
goog     4.942700e+11
googl    4.944500e+11
aapl     5.413300e+11
Name: Market Cap, dtype: float64
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;I then extracted the top and bottom 1000 tickers (removing the tickers for which data was not available), resulting in two lists of tickers: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;top_mcap&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bottom_mcap&lt;/code&gt;:&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;Available top market cap: 831
Available bottom market cap: 862
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;These tickers are then sorted by market cap, and 100-day windows are made from the last 750 datapoints (roughly corresponds to 3 market-years). 3 years was chosen as the cutoff because market cap values from more than three years ago are likely to be significantly different from the latest values, as companies grow or shrink over time. A solution would be to use the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;keystats&lt;/code&gt; dataset to find the market cap for a company &lt;em&gt;in a given year&lt;/em&gt;, but I have opted for a simpler workaround in this post.&lt;/p&gt;

&lt;p&gt;Here is the function I wrote to create the time series snapshots from the raw price data.&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;create_snapshots&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;n_days&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;750&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;window_size&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;n&quot;&gt;window_shift&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;50&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&quot;&quot;&quot;
    Returns list of time series of length windowsize, shifted
    by window_shift, for the last n_days.
    &quot;&quot;&quot;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;df&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;n&quot;&gt;read_sql&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;s&quot;&gt;&quot;SELECT adj_close_price FROM daily_price &quot;&lt;/span&gt;
                    &lt;span class=&quot;sa&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;WHERE ticker_id=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ticker_id&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;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;conn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;df&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;df&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;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;n_days&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:]&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;snapshots&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;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;range&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;n&quot;&gt;n_days&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;window_shift&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;window&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;df&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;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;window_size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;window&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;window&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'adj_close_price'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;values&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;window&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;window_size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;snapshots&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;window&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;snapshots&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;These snapshots are then stacked into a numpy array 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;n&quot;&gt;dataset&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;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&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;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;enumerate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;top_mcap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;ticker_snapshots&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;create_snapshots&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;k&quot;&gt;except&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Exception&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;print&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;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;continue&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;ticker_snapshots&lt;/span&gt; &lt;span class=&quot;o&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;c_&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;array&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ticker_snapshots&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;ones&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ticker_snapshots&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))]&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;dataset&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ticker_snapshots&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;i&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;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;enumerate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bottom_mcap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;ticker_snapshots&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;create_snapshots&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;k&quot;&gt;except&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Exception&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;print&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;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;continue&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;ticker_snapshots&lt;/span&gt; &lt;span class=&quot;o&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;c_&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;array&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ticker_snapshots&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;zeros&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ticker_snapshots&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))]&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;dataset&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ticker_snapshots&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;dataset&lt;/span&gt; &lt;span class=&quot;o&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;vstack&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dataset&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;shape&lt;/span&gt;&lt;span class=&quot;p&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;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;101&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 first 10 rows of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dataset&lt;/code&gt; are presented below. Note how the last column is always 1 – this is because the first half of the dataset consists of the top market cap tickers (classified as 1).&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;array([[35.639999, 35.610001, 39.220001, ..., 37.189999, 37.060001,
         1.      ],
       [40.369999, 40.369999, 40.419998, ..., 36.189999, 35.889999,
         1.      ],
       [37.810001, 37.849998, 38.419998, ..., 34.68    , 34.959999,
         1.      ],
       ...,
       [41.369999, 40.02    , 39.860001, ..., 35.279999, 35.27    ,
         1.      ],
       [38.5     , 38.669998, 38.599998, ..., 41.169998, 41.27    ,
         1.      ],
       [35.360001, 35.439999, 36.34    , ..., 42.740002, 43.349998,
         1.      ]])
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;We have now finished preparing the raw data for analysis. However, we still need to extract the features and preprocess the data for machine learning specifically. This will be covered in the next section.&lt;/p&gt;

&lt;h2 id=&quot;methodology&quot;&gt;Methodology&lt;/h2&gt;

&lt;p&gt;It is a remarkable fact of mathematics that periodic functions (at least the well-behaved ones) can be decomposed into sines and cosines. Although price data certainly isn’t periodic, if we treat the whole time series as one period, we can apply a Discrete Fourier Transform (DFT) to extract the main ‘signals’ in a time series. My motivation for using a DFT is that it can be used to de-noise a time series by ignoring the smaller terms, and that the coefficients of the resulting terms can be fed into a classifier. Additionally, there exist very efficient implementations in numpy, such as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;np.fft.fft()&lt;/code&gt;. One potential problem is that the results of the DFT are actually complex numbers:&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;array([ 3.86450001e+02+0.j        , -7.77637336e+00+2.22326054j,
       -3.85445493e+00+2.69405956j, -2.39862764e+00+4.72442769j,
       -1.07054807e+00+1.46787384j,  1.49997000e-01+0.j        ,
       -1.07054807e+00-1.46787384j, -2.39862764e+00-4.72442769j,
       -3.85445493e+00-2.69405956j, -7.77637336e+00-2.22326054j])
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This begs the question as to how we can input the DFT terms into a classifier. We will implement and compare two possible methods.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Taking the modulus (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;np.abs&lt;/code&gt;) of each term and using that as a feature&lt;/li&gt;
  &lt;li&gt;Splitting up the real and imaginary parts to use as separate features.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Clearly, we would expect the latter to have more predictive power, but at the cost of using twice as many terms.&lt;/p&gt;

&lt;p&gt;There is another decision we have to make, namely, how many terms of the DFT we want to use. As it is impossible to know a priori how much information the Fourier terms hold, we’ll examine the first 5, 15, and 30 terms.&lt;/p&gt;

&lt;p&gt;So in total, we have 6 datasets to test. Now, in order to avoid the data mining bias, we will apply a Bonferroni correction. The idea is that each additional test increases our chance of finding a spurious yet statistically significant relationship, so to counteract this, the significance level must be decreased to $\frac{\alpha}{m}$, where $m$ is the number of tests. So if the original significance level was $\alpha = 0.05$, the Bonferroni correction would imply a new significance of $\frac{\alpha}{6} = 0.00833$.&lt;/p&gt;

&lt;h2 id=&quot;feature-extraction-and-data-preprocessing&quot;&gt;Feature extraction and data preprocessing&lt;/h2&gt;

&lt;p&gt;Adopting standard scikit-learn notation, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;X&lt;/code&gt; will denote the feature array and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;y&lt;/code&gt; the target vector. Using the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dataset&lt;/code&gt; that we prepared before:&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;n&quot;&gt;dataset&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;n&quot;&gt;y&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dataset&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;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Prior to putting our data through a DFT, I am first going to standardise the time series (scale everything to the range [0,1]) to ensure that we don’t actually learn some boring correlation between the share price of a company and its market cap. To maximise the efficiency of this computation, bearing in mind that this computation needs to be done for more than 20k rows, it is important to (ab)use numpy’s broadcasting:&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;numerator&lt;/span&gt; &lt;span class=&quot;o&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;n&quot;&gt;X&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;min&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;n&quot;&gt;reshape&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;len&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;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;denominator&lt;/span&gt; &lt;span class=&quot;o&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;nb&quot;&gt;max&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;o&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;nb&quot;&gt;min&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;n&quot;&gt;denominator&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;denominator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;reshape&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;len&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;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;X_scaled&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;numerator&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;denominator&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;We will now define functions to take the Fourier Transform, then slice for the first &lt;em&gt;k&lt;/em&gt; terms, before either taking the modulus or splitting into real and imaginary parts.&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;generate_modulus_dataset&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;k&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;np&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;abs&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;fft&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fft&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;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;k&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;generate_complex_dataset&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;k&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;fourier&lt;/span&gt; &lt;span class=&quot;o&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;fft&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fft&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;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;k&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;np&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;column_stack&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fourier&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;real&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fourier&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;imag&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;With these methods ready, creating all of the datasets is simple:&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;modulus_5&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;generate_modulus_dataset&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;X_scaled&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;modulus_15&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;generate_modulus_dataset&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;X_scaled&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;15&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;modulus_30&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;generate_modulus_dataset&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;X_scaled&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;30&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;complex_5&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;generate_complex_dataset&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;X_scaled&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;complex_15&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;generate_complex_dataset&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;X_scaled&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;15&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;complex_30&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;generate_complex_dataset&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;X_scaled&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;30&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;h2 id=&quot;machine-learning&quot;&gt;Machine Learning&lt;/h2&gt;

&lt;p&gt;We are finally ready to apply machine learning using python’s wonderfully intuitive &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sklearn&lt;/code&gt;. I have chosen to use an XGBoost classifier – it trains quickly and normally produces good results with the default parameters. It should be noted that no hyperparameter optimisation will be done: the point of this experiment is to see if this problem is learnable (rather than trying to achieve excellent results). I have written the classification script as a function which can be easily applied to each of the datasets.&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;nn&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;nn&quot;&gt;xgboost&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;XGBClassifier&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;sklearn.metrics&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;precision_score&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;accuracy_score&lt;/span&gt;


&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;classify&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;features&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;target&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;return_acc&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;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;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y_train&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y_test&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&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;features&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;target&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;clf&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;XGBClassifier&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;clf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&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;y_train&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;y_pred&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;clf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&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;n&quot;&gt;acc&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;accuracy_score&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;y_test&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y_pred&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;prec&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;precision_score&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;y_test&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y_pred&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;return_acc&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;acc&lt;/span&gt;
    &lt;span class=&quot;k&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;s&quot;&gt;&quot;Accuracy:&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&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;mi&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;acc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;%&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&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;s&quot;&gt;&quot;Precision:&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&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;mi&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;prec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&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;p&gt;It is then a simple matter of applying the function to any of our datasets:&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;classify&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;modulus_5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y&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;Running the above resulted in a cross-validated accuracy of 58%. Given balanced classes, this is clearly a significant difference, so in principle the experiment could be stopped here and we can conclude that &lt;strong&gt;classifying stock charts based on market cap is a learnable problem&lt;/strong&gt;. This result was especially surprising to me because this particular example is just using the moduli of the first 5 terms of the Fourier Transform, which really ignores a lot of the price chart. To see what I’m talking about, this is what a time series made of the absolute values of the first 5 Fourier terms looks like:&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/fourier/5_fourier_terms.png&quot; style=&quot;width:70%&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;Although you can see that some of the main trends are captured, it is clearly a crude approximation that ignores a lot of variation.&lt;/p&gt;

&lt;p&gt;Despite having achieved our initial goal of demonstrating the learnability of this problem, seeing as we have already prepared the datasets it may be interesting to do further analysis.&lt;/p&gt;

&lt;h3 id=&quot;comparison-with-a-benchmark&quot;&gt;Comparison with a benchmark&lt;/h3&gt;

&lt;p&gt;What happens if we try to naively classify based on the whole time series, i.e using each of the 100 days’ prices as a feature?&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;%%&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;time&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;classify&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;X_scaled&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y&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-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Accuracy:
 65.34%
Precision:
 64.48%
CPU times: user 18.6 s, sys: 135 ms, total: 18.7 s
Wall time: 19.2 s
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;So actually the naive benchmark has a much better accuracy. But note the relatively long compute time of 18.7s. The question is whether any of our other datasets can reach comparable accuracies more efficiently.&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;%%&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;time&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;classify&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;complex_5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y&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-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Accuracy:
 60.96%
Precision:
 60.17%
CPU times: user 1.96 s, sys: 18.5 ms, total: 1.97 s
Wall time: 2.04 s
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;It makes sense that the complex dataset has a higher accuracy than the modulus dataset for the same number of Fourier terms: bear in mind that it includes twice as many features (since we have split the real and imaginary part).&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;%%&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;time&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;classify&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;modulus_15&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y&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-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Accuracy:
 59.45%
Precision:
 57.72%
CPU times: user 3.77 s, sys: 62.8 ms, total: 3.83 s
Wall time: 4.33 s
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This is an interesting result. 15 modulus terms (15 features) underperform 5 complex terms (i.e 10 features). This tells us that the actual complex number holds a lot of information.&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;%%&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;time&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;classify&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;complex_15&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y&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-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Accuracy:
 64.97%
Precision:
 63.75%
CPU times: user 3.02 s, sys: 26.1 ms, total: 3.04 s
Wall time: 3.09 s
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Using 15 complex terms (i.e 30 total features), comparable accuracy to the benchmark has been achieved, in about a sixth of the time!&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;%%&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;time&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;classify&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;modulus_30&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y&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-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Accuracy:
 59.74%
Precision:
 57.98%
CPU times: user 3.16 s, sys: 35.1 ms, total: 3.19 s
Wall time: 3.33 s
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&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;%%&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;time&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;classify&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;complex_30&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y&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-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Accuracy:
 66.63%
Precision:
 65.24%
CPU times: user 11.9 s, sys: 136 ms, total: 12.1 s
Wall time: 12.6 s
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;We can observe the diminishing returns: using more Fourier terms increases accuracy only slightly. However, it is a pretty remarkable result that using 30 complex terms (i.e 60 features), we can beat the benchmark while also being 1.5x faster.&lt;/p&gt;

&lt;p&gt;To better visualise how accuracy varies with the number of terms, we can generate multiple datasets and run our classification method on them:&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;scores&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;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;range&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;mi&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;dataset&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;generate_modulus_dataset&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;X_scaled&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;acc&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;classify&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dataset&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;return_acc&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;scores&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;acc&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;Doing the same for the complex datasets and plotting the results yields:&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/fourier/fourier_accuracy.png&quot; style=&quot;width:80%;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;It is interesting to note that the modulus datasets never beat the benchmark: it seems that taking the modulus of the complex terms throws away too much information. The complex datasets are most predictive at around 40 terms, but the plateau and  gradual decrease after that is a clear sign that overfitting occurs.&lt;/p&gt;

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

&lt;p&gt;In this post, we have seen that we can determine whether a given 100-day time series of stock prices belongs to a high market cap or low market cap stock, with more than 65% accuracy. This was done by extracting the main signals from a time series with a Discrete Fourier Transform, which were then used as features in a classifier. The DFT is not only able to outperform the naive solution of using each day’s price as a feature, but can also do so with a large speedup. The implication of this is that the benchmark overfits to noise, and thus this is a practical example of how reducing the information available to a classifier can actually improve performance.&lt;/p&gt;

&lt;p&gt;I think this is an important lesson for anyone applying machine learning to a real-world problem: throwing data at a classifier or deep learning model is &lt;strong&gt;not&lt;/strong&gt; a solid approach. First try to extract important features or at least discard those that only contribute noise.&lt;/p&gt;

&lt;p&gt;Future work on this topic could involve comparing the classification performance of the DFT method to a classifier that uses different summary statistics, or perhaps even a clustering methodology like k-Nearest Neighbours. But for now I am satisfied that this simple and intuitive method is sufficient to do significantly better than random guessing.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>DIY MachineLearningStocks</title>
   <link href="https://reasonabledeviations.com/2018/03/10/mlstocks-edu/"/>
   <updated>2018-03-10T00:00:00+00:00</updated>
   <id>https://reasonabledeviations.com/2018/03/10/mlstocks-edu</id>
   <content type="html">&lt;p&gt;I recently released a machine learning stock prediction project &lt;a href=&quot;https://github.com/robertmartin8/MachineLearningStocks&quot;&gt;on GitHub&lt;/a&gt;, unimaginatively named &lt;strong&gt;MachineLearningStocks&lt;/strong&gt;. It is a project that I’ve put quite a lot of time into, and is in fact a simplified version of a system that I’ve been using to live trade. This post doesn’t really offer anything on top of the existing readme, but I figured it would be good to have a copy (with some minor changes) here as well. 
&lt;!--more--&gt;&lt;/p&gt;

&lt;p&gt;MachineLearningStocks is designed to be an intuitive and highly extensible template project applying machine learning to making stock predictions. My hope is that the project will help readers to not only understand the overall workflow of using machine learning to predict stock movements, but also to appreciate some of the many subtleties. I’ve also provided a long list of possible improvements you could make, or ideas for moving forward, to put some spice into the vanilla project.&lt;/p&gt;

&lt;p&gt;Concretely, we will be cleaning and preparing a dataset of historical stock prices and fundamentals using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pandas&lt;/code&gt;, after which we will apply a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;scikit-learn&lt;/code&gt; classifier to discover the relationship between stock fundamentals (e.g. PE ratio, debt/equity, float, etc) and the subsequent annual price change (compared with the an index). We then conduct a simple backtest, before generating predictions on current data.&lt;/p&gt;

&lt;p&gt;While I would not live trade based off of the predictions from this exact code, I do believe that you can use this project as starting point for a profitable trading system – I have actually used code based on this project to live trade, with pretty decent results (around 20% returns on backtest and 10-15% on live trading).&lt;/p&gt;

&lt;p&gt;This project has quite a lot of personal significance for me. It was my first proper python project, one of my first real encounters with ML, and the first time I used git. At the start, my code was rife with bad practice and inefficiency: I have since tried to amend most of this, but please be warned that some minor issues may remain (feel free to raise an issue, or fork and submit a PR). Both the project and myself as a programmer have evolved a lot since the first iteration, but there is always room to improve.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;As a disclaimer, this is a purely educational project. Be aware that backtested performance may often be deceptive – trade at your own risk!&lt;/em&gt;&lt;/p&gt;

&lt;script async=&quot;&quot; defer=&quot;&quot; src=&quot;https://buttons.github.io/buttons.js&quot;&gt;&lt;/script&gt;

&lt;p&gt;&lt;a class=&quot;github-button&quot; href=&quot;https://github.com/robertmartin8/MachineLearningStocks&quot; data-icon=&quot;octicon-star&quot; data-size=&quot;large&quot; aria-label=&quot;Star robertmartin8/MachineLearningStocks on GitHub&quot;&gt;Star&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;contents&quot;&gt;Contents&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;#overview&quot;&gt;Overview&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#quickstart&quot;&gt;Quickstart&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#preliminaries&quot;&gt;Preliminaries&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#historical-data&quot;&gt;Historical data&lt;/a&gt;
    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#historical-stock-fundamentals&quot;&gt;Historical stock fundamentals&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#historical-price-data&quot;&gt;Historical price data&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#creating-the-training-dataset&quot;&gt;Creating the training dataset&lt;/a&gt;
    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#preprocessing-historical-price-data&quot;&gt;Preprocessing historical price data&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#features&quot;&gt;Features&lt;/a&gt;
        &lt;ul&gt;
          &lt;li&gt;&lt;a href=&quot;#valuation-measures&quot;&gt;Valuation measures&lt;/a&gt;&lt;/li&gt;
          &lt;li&gt;&lt;a href=&quot;#financials&quot;&gt;Financials&lt;/a&gt;&lt;/li&gt;
          &lt;li&gt;&lt;a href=&quot;#trading-information&quot;&gt;Trading information&lt;/a&gt;&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#parsing&quot;&gt;Parsing&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#backtesting&quot;&gt;Backtesting&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#current-fundamental-data&quot;&gt;Current fundamental data&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#stock-prediction&quot;&gt;Stock prediction&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#unit-testing&quot;&gt;Unit testing&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#where-to-go-from-here&quot;&gt;Where to go from here&lt;/a&gt;
    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#data-acquisition&quot;&gt;Data acquisition&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#data-preprocessing&quot;&gt;Data preprocessing&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#machine-learning&quot;&gt;Machine learning&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#contributing&quot;&gt;Contributing&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;overview&quot;&gt;Overview&lt;/h2&gt;

&lt;p&gt;The overall workflow to use machine learning to make stocks prediction is as follows:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Acquire historical fundamental data – these are the &lt;em&gt;features&lt;/em&gt; or &lt;em&gt;predictors&lt;/em&gt;&lt;/li&gt;
  &lt;li&gt;Acquire historical stock price data – this is will make up the dependent variable, or label (what we are trying to predict).&lt;/li&gt;
  &lt;li&gt;Preprocess data&lt;/li&gt;
  &lt;li&gt;Use a machine learning model to learn from the data&lt;/li&gt;
  &lt;li&gt;Backtest the performance of the machine learning model&lt;/li&gt;
  &lt;li&gt;Acquire current fundamental data&lt;/li&gt;
  &lt;li&gt;Generate predictions from current fundamental data&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is a very generalised overview, but in principle this is all you need to build a fundamentals-based ML stock predictor.&lt;/p&gt;

&lt;h2 id=&quot;quickstart&quot;&gt;Quickstart&lt;/h2&gt;

&lt;p&gt;For readers who want to throw away the instruction manual and play immediately, just clone the project, then download and unzip the &lt;a href=&quot;https://pythonprogramming.net/data-acquisition-machine-learning/&quot;&gt;data file&lt;/a&gt; into the same directory. Then, open an instance of terminal and cd to the project’s file path, e.g&lt;/p&gt;

&lt;div class=&quot;language-bash 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;cd &lt;/span&gt;Users/User/Desktop/MachineLearningStocks
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Then, run the following in terminal:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;pip &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-r&lt;/span&gt; requirements.txt
python download_historical_prices.py
python parsing_keystats.py
python backtesting.py
python current_data.py
pytest &lt;span class=&quot;nt&quot;&gt;-v&lt;/span&gt;
python stock_prediction.py
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Otherwise, follow the step-by-step guide below.&lt;/p&gt;

&lt;h2 id=&quot;preliminaries&quot;&gt;Preliminaries&lt;/h2&gt;

&lt;p&gt;This project uses python 3, and the common data science libraries &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pandas&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;scikit-learn&lt;/code&gt;. A full list of requirements is included in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;requirements.txt&lt;/code&gt; file. To install all of the requirements at once, run the following code into terminal:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;pip &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-r&lt;/span&gt; requirements.txt
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;To get started, clone this project and unzip it. This will become our working directory, so make sure you &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cd&lt;/code&gt; your terminal instance into this directory.&lt;/p&gt;

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

&lt;p&gt;Data acquisition and preprocessing is probably the hardest part of most machine learning projects. But it is a necessary evil, so it’s best to not fret and just carry on.&lt;/p&gt;

&lt;p&gt;For this project, we need three datasets:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Historical stock fundamentals&lt;/li&gt;
  &lt;li&gt;Historical stock prices&lt;/li&gt;
  &lt;li&gt;Historical S&amp;amp;P500 prices&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We need the S&amp;amp;P500 index prices as a benchmark: a 5% stock growth does not mean much if the S&amp;amp;P500 grew 10% in that time period, so all stock returns must be compared to those of the index.&lt;/p&gt;

&lt;h3 id=&quot;historical-stock-fundamentals&quot;&gt;Historical stock fundamentals&lt;/h3&gt;

&lt;p&gt;Historical fundamental data is actually very difficult to find (for free, at least). Although sites like &lt;a href=&quot;https://www.quandl.com/&quot;&gt;Quandl&lt;/a&gt; do have datasets available, you often have to pay a pretty steep fee.&lt;/p&gt;

&lt;p&gt;It turns out that there is a way to parse this data, for free, from &lt;a href=&quot;https://finance.yahoo.com/&quot;&gt;Yahoo Finance&lt;/a&gt;. I will not go into details, because &lt;a href=&quot;https://pythonprogramming.net/data-acquisition-machine-learning/&quot;&gt;Sentdex has done it for us&lt;/a&gt;. On his page you will be able to find a file called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;intraQuarter.zip&lt;/code&gt;, which you should download, unzip, and place in your working directory. Relevant to this project is the subfolder called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_KeyStats&lt;/code&gt;, which contains html files that hold stock fundamentals for all stocks in the S&amp;amp;P500 between 2003 and 2013, sorted by stock. However, at this stage, the data is unusable – we will have to parse it into a nice csv file before we can do any ML.&lt;/p&gt;

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

&lt;p&gt;In the first iteration of the project, I used &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pandas-datareader&lt;/code&gt;, an extremely convenient library which can load stock data straight into &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pandas&lt;/code&gt;. However, after Yahoo Finance changed their UI, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;datareader&lt;/code&gt; no longer worked, so I switched to &lt;a href=&quot;https://www.quandl.com/&quot;&gt;Quandl&lt;/a&gt;, which has free stock price data for a few tickers, and a python API. However, as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pandas-datareader&lt;/code&gt; has been &lt;a href=&quot;https://github.com/ranaroussi/fix-yahoo-finance&quot;&gt;fixed&lt;/a&gt;, we will use that instead.&lt;/p&gt;

&lt;p&gt;Likewise, we can easily use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pandas-datareader&lt;/code&gt; to access data for the SPY ticker. Failing that, one could manually download it from &lt;a href=&quot;https://finance.yahoo.com/quote/%5EGSPC/history?p=%5EGSPC&quot;&gt;yahoo finance&lt;/a&gt;, place it into the project directory and rename it &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sp500_index.csv&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The code for downloading historical price data can be run by entering the following into terminal:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;python download_historical_prices.py
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;creating-the-training-dataset&quot;&gt;Creating the training dataset&lt;/h2&gt;

&lt;p&gt;Our ultimate goal for the training data is to have a ‘snapshot’ of a particular stock’s fundamentals at a particular time, and the corresponding subsequent annual performance of the stock.&lt;/p&gt;

&lt;p&gt;For example, if our ‘snapshot’ consists of all of the fundamental data for AAPL on the date 28/1/2005, then we also need to know the percentage price change of AAPL between 28/1/05 and 28/1/06. Thus our algorithm can learn how the fundamentals impact the annual change in the stock price.&lt;/p&gt;

&lt;p&gt;In fact, this is a slight oversimplification. In fact, what the algorithm will eventually learn is how fundamentals impact the &lt;em&gt;outperformance of a stock relative to the S&amp;amp;P500 index&lt;/em&gt;. This is why we also need index data.&lt;/p&gt;

&lt;h3 id=&quot;preprocessing-historical-price-data&quot;&gt;Preprocessing historical price data&lt;/h3&gt;

&lt;p&gt;When &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pandas-datareader&lt;/code&gt; downloads stock price data, it does not include rows for weekends and public holidays (when the market is closed).&lt;/p&gt;

&lt;p&gt;However, referring to the example of AAPL above, if our snapshot includes fundamental data for 28/1/05 and we want to see the change in price a year later, we will get the nasty surprise that 28/1/2006 is a Saturday. Does this mean that we have to discard this snapshot?&lt;/p&gt;

&lt;p&gt;By no means – data is too valuable to callously toss away. As a workaround, I instead decided to ‘fill forward’ the missing data, i.e we will assume that the stock price on Saturday 28/1/2006 is equal to the stock price on Friday 27/1/2006.&lt;/p&gt;

&lt;h3 id=&quot;features&quot;&gt;Features&lt;/h3&gt;

&lt;p&gt;Below is a list of some of the interesting variables that are available on Yahoo Finance.&lt;/p&gt;

&lt;h4 id=&quot;valuation-measures&quot;&gt;Valuation measures&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;‘Market Cap’&lt;/li&gt;
  &lt;li&gt;Enterprise Value&lt;/li&gt;
  &lt;li&gt;Trailing P/E&lt;/li&gt;
  &lt;li&gt;Forward P/E&lt;/li&gt;
  &lt;li&gt;PEG Ratio&lt;/li&gt;
  &lt;li&gt;Price/Sales&lt;/li&gt;
  &lt;li&gt;Price/Book&lt;/li&gt;
  &lt;li&gt;Enterprise Value/Revenue&lt;/li&gt;
  &lt;li&gt;Enterprise Value/EBITDA&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;financials&quot;&gt;Financials&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;Profit Margin&lt;/li&gt;
  &lt;li&gt;Operating Margin&lt;/li&gt;
  &lt;li&gt;Return on Assets&lt;/li&gt;
  &lt;li&gt;Return on Equity&lt;/li&gt;
  &lt;li&gt;Revenue&lt;/li&gt;
  &lt;li&gt;Revenue Per Share&lt;/li&gt;
  &lt;li&gt;Quarterly Revenue Growth&lt;/li&gt;
  &lt;li&gt;Gross Profit&lt;/li&gt;
  &lt;li&gt;EBITDA&lt;/li&gt;
  &lt;li&gt;Net Income Avi to Common&lt;/li&gt;
  &lt;li&gt;Diluted EPS&lt;/li&gt;
  &lt;li&gt;Quarterly Earnings Growth&lt;/li&gt;
  &lt;li&gt;Total Cash&lt;/li&gt;
  &lt;li&gt;Total Cash Per Share&lt;/li&gt;
  &lt;li&gt;Total Debt&lt;/li&gt;
  &lt;li&gt;Total Debt/Equity&lt;/li&gt;
  &lt;li&gt;Current Ratio&lt;/li&gt;
  &lt;li&gt;Book Value Per Share&lt;/li&gt;
  &lt;li&gt;Operating Cash Flow&lt;/li&gt;
  &lt;li&gt;Levered Free Cash Flow&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;trading-information&quot;&gt;Trading information&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;Beta&lt;/li&gt;
  &lt;li&gt;50-Day Moving Average&lt;/li&gt;
  &lt;li&gt;200-Day Moving Average&lt;/li&gt;
  &lt;li&gt;Avg Vol (3 month)&lt;/li&gt;
  &lt;li&gt;Shares Outstanding&lt;/li&gt;
  &lt;li&gt;Float&lt;/li&gt;
  &lt;li&gt;% Held by Insiders&lt;/li&gt;
  &lt;li&gt;% Held by Institutions&lt;/li&gt;
  &lt;li&gt;Shares Short&lt;/li&gt;
  &lt;li&gt;Short Ratio&lt;/li&gt;
  &lt;li&gt;Short % of Float&lt;/li&gt;
  &lt;li&gt;Shares Short (prior month)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;parsing&quot;&gt;Parsing&lt;/h3&gt;

&lt;p&gt;However, all of this data is locked up in HTML files. Thus, we need to build a parser. In this project, I did the parsing with regex, but please note that generally it is &lt;a href=&quot;https://stackoverflow.com/questions/1732348/regex-match-open-tags-except-xhtml-self-contained-tags&quot;&gt;really not recommended&lt;/a&gt; to use regex to parse HTML. However, I think regex probably wins out for ease of understanding (this project being educational in nature), and from experience regex works fine in this case.&lt;/p&gt;

&lt;p&gt;This is the exact regex used:&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;sa&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'&amp;gt;'&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;re&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;escape&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;variable&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;sa&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'.*?(\-?\d+\.*\d*K?M?B?|N/A[\\n|\s]*|&amp;gt;0|NaN)%?(&amp;lt;/td&amp;gt;|&amp;lt;/span&amp;gt;)'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;While it looks pretty arcane, all it is doing is searching for the first occurence of the feature (e.g. “Market Cap”), then it looks forward until it finds a number immediately followed by a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;/td&amp;gt;&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;/span&amp;gt;&lt;/code&gt; (signifying the end of a table entry). The complexity of the expression above accounts for some subtleties in the parsing:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;the numbers could be preceeded by a minus sign&lt;/li&gt;
  &lt;li&gt;Yahoo Finance sometimes uses K, M, and B as abbreviations for thousand, million and billion respectively.&lt;/li&gt;
  &lt;li&gt;some data are given as percentages&lt;/li&gt;
  &lt;li&gt;some datapoints are missing, so instead of a number we have to look for “N/A” or “NaN.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both the preprocessing of price data and the parsing of keystats are included in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;parsing_keystats.py&lt;/code&gt;. Run the following in your terminal:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;python parsing_keystats.py
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You should see the file &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;keystats.csv&lt;/code&gt; appear in your working directory. Now that we have the training data ready, we are ready to actually do some machine learning.&lt;/p&gt;

&lt;h2 id=&quot;backtesting&quot;&gt;Backtesting&lt;/h2&gt;

&lt;p&gt;Backtesting is arguably the most important part of any quantitative strategy: you must have some way of testing the performance of your algorithm before you live trade it.&lt;/p&gt;

&lt;p&gt;Despite its importance, I originally did not want to include backtesting code in this repository. The reasons were as follows:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Backtesting is messy and empirical. The code is not very pleasant to use, and in practice requires a lot of manual interaction.&lt;/li&gt;
  &lt;li&gt;Backtesting is very difficult to get right, and if you do it wrong, you will be deceiving yourself with high returns.&lt;/li&gt;
  &lt;li&gt;Developing and working with your backtest is probably the best way to learn about machine learning and stocks – you’ll see what works, what doesn’t, and what you don’t understand.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Nevertheless, because of the importance of backtesting, I decided that I can’t really call this a ‘template machine learning stocks project’ without backtesting. Thus, I have included a simplistic backtesting script. Please note that there is a fatal flaw with this backtesting implementation that will result in &lt;em&gt;much&lt;/em&gt; higher backtesting returns. It is quite a subtle point, but I will let you figure that out :)&lt;/p&gt;

&lt;p&gt;Run the following in terminal:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;python backtesting.py
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

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

&lt;pre&gt;&lt;code class=&quot;language-txt&quot;&gt;Classifier performance
======================
Accuracy score:  0.81
Precision score:  0.75

Stock prediction performance report
===================================
Total Trades: 177
Average return for stock predictions:  37.8 %
Average market return in the same period:  9.2%
Compared to the index, our strategy earns  28.6 percentage points more
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Again, the performance looks too good to be true and almost certainly is.&lt;/p&gt;

&lt;h2 id=&quot;current-fundamental-data&quot;&gt;Current fundamental data&lt;/h2&gt;

&lt;p&gt;Now that we have trained and backtested a model on our data, we would like to generate actual predictions on current data.&lt;/p&gt;

&lt;p&gt;As always, we can scrape the data from good old Yahoo Finance. My method is to literally just download the statistics page for each stock (here is the &lt;a href=&quot;https://finance.yahoo.com/quote/AAPL/key-statistics?p=AAPL&quot;&gt;page&lt;/a&gt; for Apple), then to parse it using regex as before.&lt;/p&gt;

&lt;p&gt;In fact, the regex should be almost identical, but because Yahoo has changed their UI a couple of times, there are some minor differences. This part of the projet has to be fixed whenever yahoo finance changes their UI, so if you can’t get the project to work, the problem is most likely here.&lt;/p&gt;

&lt;p&gt;Run the following in terminal:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;python current_data.py
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The script will then begin downloading the HTML into the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;forward/&lt;/code&gt; folder within your working directory, before parsing this data and outputting the file &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;forward_sample.csv&lt;/code&gt;. You might see a few miscellaneous errors for certain tickers (e.g. ‘Exceeded 30 redirects.’), but this is to be expected.&lt;/p&gt;

&lt;h2 id=&quot;stock-prediction&quot;&gt;Stock prediction&lt;/h2&gt;

&lt;p&gt;Now that we have the training data and the current data, we can finally generate actual predictions. This part of the project is very simple: the only thing you have to decide is the value of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;OUTPERFORMANCE&lt;/code&gt; parameter (the percentage by which a stock has to beat the S&amp;amp;P500 to be considered a ‘buy’). I have set it to 10 by default, but it can easily be modified by changing the variable at the top of the file. Go ahead and run the script:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;python stock_prediction.py
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

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

&lt;pre&gt;&lt;code class=&quot;language-txt&quot;&gt;21 stocks predicted to outperform the S&amp;amp;P500 by more than 10%:
NOC FL SWK NFX LH NSC SCHL KSU DDS GWW AIZ ORLY R SFLY SHW GME DLX DIS AMP BBBY APD
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&quot;unit-testing&quot;&gt;Unit testing&lt;/h2&gt;

&lt;p&gt;I have included a number of unit tests (in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tests/&lt;/code&gt; folder) which serve to check that things are working properly. However, due to the nature of the some of this projects functionality (downloading big datasets), you will have to run all the code once before running the tests. Otherwise, the tests themselves would have to download huge datasets (which I don’t think is optimal).&lt;/p&gt;

&lt;p&gt;I thus recommend that you run the tests after you have run all the other scripts (except, perhaps, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;stock_prediction.py&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;To run the tests, simply enter the following into a terminal instance in the project directory:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;pytest &lt;span class=&quot;nt&quot;&gt;-v&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Please note that it is not considered best practice to include an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;__init__.py&lt;/code&gt; file in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tests/&lt;/code&gt; directory (see &lt;a href=&quot;https://docs.pytest.org/en/latest/goodpractices.html&quot;&gt;here&lt;/a&gt; for more), but I have done it anyway because it is uncomplicated and functional.&lt;/p&gt;

&lt;h2 id=&quot;where-to-go-from-here&quot;&gt;Where to go from here&lt;/h2&gt;

&lt;p&gt;I have stated that this project is extensible, so here are some ideas to get you started and possibly increase returns (no promises).&lt;/p&gt;

&lt;h3 id=&quot;data-acquisition&quot;&gt;Data acquisition&lt;/h3&gt;

&lt;p&gt;My personal belief is that better quality data is THE factor that will ultimately determine your performance. Here are some ideas:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Explore the other subfolders in Sentdex’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;intraQuarter.zip&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;Parse the annual reports that all companies submit to the SEC (have a look at the &lt;a href=&quot;https://www.sec.gov/edgar/searchedgar/companysearch.html&quot;&gt;Edgar Database&lt;/a&gt;)&lt;/li&gt;
  &lt;li&gt;Try to find websites from which you can scrape fundamental data (this has been my solution).&lt;/li&gt;
  &lt;li&gt;Ditch US stocks and go global – perhaps better results may be found in markets that are less-liquid. It’d be interesting to see whether the predictive power of features vary based on geography.&lt;/li&gt;
  &lt;li&gt;Buy Quandl data, or experiment with alternative data.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;data-preprocessing&quot;&gt;Data preprocessing&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;Build a more robust parser using BeautifulSoup&lt;/li&gt;
  &lt;li&gt;In this project, I have just ignored any rows with missing data, but this reduces the size of the dataset considerably. Are there any ways you can fill in some of this data?
    &lt;ul&gt;
      &lt;li&gt;hint: if the PE ratio is missing but you know the stock price and the earnings/share…&lt;/li&gt;
      &lt;li&gt;hint 2: how different is Apple’s book value in March to its book value in June?&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Some form of feature engineering
    &lt;ul&gt;
      &lt;li&gt;e.g, calculate &lt;a href=&quot;https://www.investopedia.com/terms/g/graham-number.asp&quot;&gt;Graham’s number&lt;/a&gt; and use it as a feature&lt;/li&gt;
      &lt;li&gt;some of the features are probably redundant. Why not remove them to speed up training?&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Speed up the construction of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;keystats.csv&lt;/code&gt;.
    &lt;ul&gt;
      &lt;li&gt;hint: don’t keep appending to one growing dataframe! Split it into chunks&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;machine-learning&quot;&gt;Machine learning&lt;/h3&gt;

&lt;p&gt;Altering the machine learning stuff is probably the easiest and most fun to do.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;The most important thing if you’re serious about results is to find the problem with the current backtesting setup and fix it. This will likely be quite a sobering experience, but if your backtest is done right, it should mean that any observed outperformance on your test set can be traded on (again, do so at your own discretion).&lt;/li&gt;
  &lt;li&gt;Try a different classifier – there is plenty of research that advocates the use of SVMs, for example. Don’t forget that other classifiers may require feature scaling etc.&lt;/li&gt;
  &lt;li&gt;Hyperparameter tuning: use gridsearch to find the optimal hyperparameters for your classifier. But make sure you don’t overfit!&lt;/li&gt;
  &lt;li&gt;Make it &lt;em&gt;deep&lt;/em&gt; – experiment with neural networks (an easy way to start is with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sklearn.neural_network&lt;/code&gt;).&lt;/li&gt;
  &lt;li&gt;Change the classification problem into a regresion one: will we achieve better results if we try to predict the stock &lt;em&gt;price&lt;/em&gt; rather than whether it outperformed?&lt;/li&gt;
  &lt;li&gt;Run the prediction multiple times (perhaps using different hyperparameters?) and select the &lt;em&gt;k&lt;/em&gt; most common stocks to invest in. This is especially important if the algorithm is not deterministic (as is the case for Random Forest)&lt;/li&gt;
  &lt;li&gt;Experiment with different values of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;outperformance&lt;/code&gt; parameter.&lt;/li&gt;
  &lt;li&gt;Try to plot the importance of different features to ‘see what the machine sees’.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;contributing&quot;&gt;Contributing&lt;/h2&gt;

&lt;p&gt;Feel free to fork, play around, and submit PRs. I would be very grateful for any bug fixes or more unit tests.&lt;/p&gt;

&lt;p&gt;This project was originally based on Sentdex’s excellent &lt;a href=&quot;https://www.youtube.com/playlist?list=PLQVvvaa0QuDd0flgGphKCej-9jp-QdzZ3&quot;&gt;machine learning tutorial&lt;/a&gt;, but it has since evolved far beyond that and the code is almost completely different. The complete series is also on &lt;a href=&quot;https://pythonprogramming.net/machine-learning-python-sklearn-intro/&quot;&gt;his website&lt;/a&gt;.&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>Creating a stock price database with MariaDB and python</title>
   <link href="https://reasonabledeviations.com/2018/02/01/stock-price-database/"/>
   <updated>2018-02-01T00:00:00+00:00</updated>
   <id>https://reasonabledeviations.com/2018/02/01/stock-price-database</id>
   <content type="html">&lt;p&gt;One of my interests is exploring the applications of machine learning to financial markets. As part of this hobby, I’ve spent many more hours parsing and processing data than I have actually applying machine learning. I’ve worked broadly with two datasets in particular: historical financial statistics (e.g. P/E ratio, price/book) make up the features that my algorithms learn from, but the actual backbone of any strategy is historical price data.&lt;/p&gt;

&lt;p&gt;My main data source has been Yahoo Finance. Although they’ve deprecated their official API, they still have the same data on their website, meaning that it can be scraped if you can be bothered. I discovered a crude but functional way of doing this (detailed in this &lt;a href=&quot;/2017/07/30/yahoo-historical-prices/&quot;&gt;post&lt;/a&gt;), but then discovered an extremely convenient &lt;a href=&quot;https://github.com/ranaroussi/fix-yahoo-finance&quot;&gt;python library&lt;/a&gt; that does the same thing much more efficiently, with a direct pandas-datareader interface.&lt;/p&gt;

&lt;p&gt;However, it remained a concern for me that one day the winds would change and Yahoo Finance would deprecate this hidden API permanently. So I decided to make a hoard of this data, in the form of a stock price database. I think creating your own securities database is an important step for anyone looking to get into algorithmic investing more seriously, so I’ve decided to share how I’ve done so. As this is my first financial database, there may be inefficiencies in the schema, but overall I believe that the solution presented here is relatively robust, and definitely sufficient for my purposes.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Edit as of Feb 2022: I probably wouldn’t recommend following the steps in this post – there are better resources elsewhere. Nevertheless, I’m leaving this post up for personal nostalgia.&lt;/em&gt;&lt;/p&gt;

&lt;h2 id=&quot;contents&quot;&gt;Contents&lt;/h2&gt;

&lt;!-- TOC depthFrom:1 depthTo:6 withLinks:1 updateOnSave:0 orderedList:0 --&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;#choosing-a-database-management-system&quot;&gt;Choosing a database management system&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#prerequisites&quot;&gt;Prerequisites&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#database-schema&quot;&gt;Database schema&lt;/a&gt;
    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#table-exchange&quot;&gt;Table: exchange&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#table-security&quot;&gt;Table: security&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#table-datavendor&quot;&gt;Table: data_vendor&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#table-dailyprice&quot;&gt;Table: daily_price&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#populating-the-database&quot;&gt;Populating the database&lt;/a&gt;
    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#adding-an-exchange-and-a-list-of-securities&quot;&gt;Adding an exchange and a list of securities&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#adding-price-data&quot;&gt;Adding price data&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#updating-data&quot;&gt;Updating data&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#conclusion&quot;&gt;Conclusion&lt;/a&gt;
&lt;!-- /TOC --&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;choosing-a-database-management-system&quot;&gt;Choosing a database management system&lt;/h2&gt;

&lt;p&gt;When it comes to choosing a database system, there are a somewhat distressing number of decisions that you have to make. Do you want a relational database or NoSQL? If you choose relational, which system are you going to go with? Within that system, what storage engine should you use?&lt;/p&gt;

&lt;p&gt;I think this chart from &lt;a href=&quot;https://nuodb.com/products/database-comparison&quot;&gt;nuodb&lt;/a&gt; sums up the various options quite well:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://nuodb.com/sites/default/files/styles/nuodb_extra_large/public/2020-05/database-comparison.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;It was quite clear to me that a SQL relational database was what I wanted, after all, price data is highly structured and I need very quick read speed. But even after narrowing it down to a traditional RDBMS, you still have to choose the exact system. A quick look cut my options down to SQLite, MySQL, or PostgreSQL.&lt;/p&gt;

&lt;p&gt;I decided to go with MySQL, because I felt that I didn’t need the application-embedding that SQLite offered, nor did I need all the advanced features of PostgreSQL. In the end though, I chose MariaDB, which is an open-source fork of MySQL that seemed to offer all of the features that MySQL did, with a few minor improvements (see an interesting discussion &lt;a href=&quot;https://blog.panoply.io/a-comparative-vmariadb-vs-mysql&quot;&gt;here&lt;/a&gt;). 
Lastly, you have to choose a storage engine. The default for anything after MariaDB 10.2 is InnoDB, and I felt that the burden of proof was on the alternatives to demonstrate superiority for my purposes. They didn’t do so, hence I stuck with InnoDB. The &lt;a href=&quot;https://mariadb.com/kb/en/library/choosing-the-right-storage-engine/&quot;&gt;official documentation&lt;/a&gt; gives a very clear exposition on the topic of storage engines.&lt;/p&gt;

&lt;h2 id=&quot;prerequisites&quot;&gt;Prerequisites&lt;/h2&gt;

&lt;p&gt;A complete guide to setting up MariaDB/MySQL is outside of the scope of this post. Please refer to the official webpages for more. However, if you’re on a mac and already have homebrew, it’s really quite easy:&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;brew install mariadb
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Then it should be a simple matter of starting the MariaDB server and logging in:&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;mysql.server start
mysql -u root
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;MySQL and MariaDB are pretty much interchangeable (at least for now), which is why you will interact with MariaDB via &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mysql&lt;/code&gt; commands.&lt;/p&gt;

&lt;p&gt;The requirements for this project, which can be installed easily via &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pip&lt;/code&gt;, are as follows:&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;pandas==0.22.0
PyMySQL==0.8.0
pandas_datareader==0.5.0
fix_yahoo_finance==0.0.21
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Lastly, although it’s completely up to you, I would recommend having some other GUI database software on your computer to help visualise things. I like &lt;a href=&quot;https://sequelpro.com/&quot;&gt;Sequel Pro&lt;/a&gt; (macOS only), which is open-source, elegant, and designed specifically for MySQL/MariaDB.&lt;/p&gt;

&lt;h2 id=&quot;database-schema&quot;&gt;Database schema&lt;/h2&gt;

&lt;p&gt;A database schema sets out how all of the information is going to be organised in your database. Optimal schema design is a really huge topic that is often the subject of university courses, as well as being a typical interview task for prospective database administrators. I wasn’t naive enough to think that I could come up with the perfect schema from nothing, so I took to google to learn from other people’s mistakes. This was especially important because I knew I would end up with quite a lot of data: I was imagining at least 15 years of daily data for 5000 tickers, which is about 30 million rows. There are lots of conflicting opinions on financial database schema, but in the end I decided to follow the advice of an &lt;a href=&quot;https://www.quantstart.com/articles/Securities-Master-Database-with-MySQL-and-Python&quot;&gt;article from Quantstart&lt;/a&gt;. The main advantage of their proposed schema over some of the other suggestions I saw online is flexibility: it really makes no assumptions about what securities you’re interested in. As long as they can be represented as a time series of prices, they’ll fit into the database nicely.&lt;/p&gt;

&lt;p&gt;Before getting started generating the tables, we mustn’t forget to generate the database! After logging in to MariaDB, just run the following in the console:&lt;/p&gt;

&lt;div class=&quot;language-sql 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;CREATE&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;DATABASE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;stock_prices&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;We can then proceed to generate the tables. As per the linked article, we will have four tables: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;exchange&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;security&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;data_vendor&lt;/code&gt;, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;daily_price&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Just a quick note: if you’re worried about the eventual size of the database, you could consider setting up on an external drive. On unix systems, there is a simple ‘hack’ for this: navigate to your MySQL folder (for me this was &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/usr/local/var/mysql&lt;/code&gt;), find the correct database folder, then drag it to your external drive. Now, create a &lt;strong&gt;symbolic link&lt;/strong&gt; in the mysql directory, by running in terminal:&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;ln -s [path to drive] [database folder path]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;However, I think in most cases this is unnecessary: my 23 million datapoints only take up 3.65 GB.&lt;/p&gt;

&lt;h3 id=&quot;table-exchange&quot;&gt;Table: exchange&lt;/h3&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;exchange&lt;/code&gt; table contains information (the name and currency-traded) of the exchange. This is a parent of table &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;security&lt;/code&gt;.&lt;/p&gt;

&lt;div class=&quot;language-sql 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;CREATE&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;TABLE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;IF&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;EXISTS&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;`exchange`&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;nv&quot;&gt;`id`&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;INT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;11&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NULL&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;AUTO_INCREMENT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;nv&quot;&gt;`name`&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;VARCHAR&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;nv&quot;&gt;`currency`&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;CHAR&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NULL&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;DEFAULT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;nv&quot;&gt;`created_date`&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;DATETIME&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NULL&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;DEFAULT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;CURRENT_TIMESTAMP&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt;
  &lt;span class=&quot;nv&quot;&gt;`last_updated`&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;DATETIME&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NULL&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;DEFAULT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;CURRENT_TIMESTAMP&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;ON&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;UPDATE&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;CURRENT_TIMESTAMP&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;PRIMARY&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;KEY&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;`id`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;ENGINE&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;InnoDB&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;DEFAULT&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;CHARACTER&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;SET&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;utf8&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;h3 id=&quot;table-security&quot;&gt;Table: security&lt;/h3&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;security&lt;/code&gt; holds all of the relevant information about the actual companies for which we will collect data. Actually the only thing we really need here is the ticker (and which exchange it is traded on), but I thought it might be nice to have some other data like the sector and industry.&lt;/p&gt;

&lt;div class=&quot;language-sql 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;DROP&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;TABLE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;IF&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;EXISTS&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;`security`&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;TABLE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;IF&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;EXISTS&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;`security`&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;nv&quot;&gt;`id`&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;INT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;11&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NULL&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;AUTO_INCREMENT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;nv&quot;&gt;`exchange_id`&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;INT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;11&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;nv&quot;&gt;`ticker`&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;VARCHAR&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;nv&quot;&gt;`name`&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;VARCHAR&lt;/span&gt;&lt;span class=&quot;p&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;k&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;nv&quot;&gt;`sector`&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;VARCHAR&lt;/span&gt;&lt;span class=&quot;p&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;k&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;nv&quot;&gt;`industry`&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;VARCHAR&lt;/span&gt;&lt;span class=&quot;p&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;k&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;nv&quot;&gt;`created_date`&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;DATETIME&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NULL&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;DEFAULT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;CURRENT_TIMESTAMP&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt;
  &lt;span class=&quot;nv&quot;&gt;`last_updated`&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;DATETIME&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NULL&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;DEFAULT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;CURRENT_TIMESTAMP&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;ON&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;UPDATE&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;CURRENT_TIMESTAMP&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;PRIMARY&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;KEY&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;`id`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;INDEX&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;`exchange_id`&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;`exchange_id`&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;ASC&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;INDEX&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;`ticker`&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;`ticker`&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;ASC&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;CONSTRAINT&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;`fk_exchange_id`&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;FOREIGN&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;KEY&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;`exchange_id`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;REFERENCES&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;`exchange`&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;`id`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;ON&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;DELETE&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NO&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ACTION&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;ON&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;UPDATE&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NO&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ACTION&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;ENGINE&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;InnoDB&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;DEFAULT&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;CHARACTER&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;SET&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;utf8&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 main features of interest are the foreign key on the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;exchange_id&lt;/code&gt;, and the fact that we have added an index on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ticker&lt;/code&gt;.&lt;/p&gt;

&lt;h3 id=&quot;table-data_vendor&quot;&gt;Table: data_vendor&lt;/h3&gt;

&lt;p&gt;Because I was planning to get all of my data from Yahoo Finance, I didn’t think I would need a table like this. However, I suppose that as I cast my net wider in future, it may be useful to have this flexibility.&lt;/p&gt;

&lt;div class=&quot;language-sql 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;DROP&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;TABLE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;IF&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;EXISTS&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;`data_vendor`&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;TABLE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;IF&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;EXISTS&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;`data_vendor`&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;nv&quot;&gt;`id`&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;INT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;11&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NULL&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;AUTO_INCREMENT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;nv&quot;&gt;`name`&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;VARCHAR&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;32&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;nv&quot;&gt;`website_url`&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;VARCHAR&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;255&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NULL&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;DEFAULT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;nv&quot;&gt;`created_date`&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;DATETIME&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NULL&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;DEFAULT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;CURRENT_TIMESTAMP&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt;
  &lt;span class=&quot;nv&quot;&gt;`last_updated`&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;DATETIME&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NULL&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;DEFAULT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;CURRENT_TIMESTAMP&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;ON&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;UPDATE&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;CURRENT_TIMESTAMP&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;PRIMARY&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;KEY&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;`id`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;ENGINE&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;InnoDB&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;AUTO_INCREMENT&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;k&quot;&gt;DEFAULT&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;CHARACTER&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;SET&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;utf8&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;h3 id=&quot;table-daily_price&quot;&gt;Table: daily_price&lt;/h3&gt;

&lt;p&gt;This is the real heart of the database. In fact, we could probably denormalise and squish the other tables into this one, but in accordance with the Zen of Python:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Explicit is better than implicit. 
Sparse is better than dense.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class=&quot;language-sql 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;DROP&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;TABLE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;IF&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;EXISTS&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;`daily_price`&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;TABLE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;IF&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;EXISTS&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;`daily_price`&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;nv&quot;&gt;`id`&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;INT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;11&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NULL&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;AUTO_INCREMENT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;nv&quot;&gt;`data_vendor_id`&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;INT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;11&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;nv&quot;&gt;`ticker_id`&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;INT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;11&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;nv&quot;&gt;`price_date`&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;DATE&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;nv&quot;&gt;`created_date`&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;DATETIME&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NULL&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;DEFAULT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;CURRENT_TIMESTAMP&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt;
  &lt;span class=&quot;nv&quot;&gt;`last_updated`&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;DATETIME&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NULL&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;DEFAULT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;CURRENT_TIMESTAMP&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;ON&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;UPDATE&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;CURRENT_TIMESTAMP&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt;
  &lt;span class=&quot;nv&quot;&gt;`open_price`&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;DECIMAL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;11&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;k&quot;&gt;NULL&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;DEFAULT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;nv&quot;&gt;`high_price`&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;DECIMAL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;11&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;k&quot;&gt;NULL&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;DEFAULT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;nv&quot;&gt;`low_price`&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;DECIMAL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;11&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;k&quot;&gt;NULL&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;DEFAULT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;nv&quot;&gt;`close_price`&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;DECIMAL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;11&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;k&quot;&gt;NULL&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;DEFAULT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;nv&quot;&gt;`adj_close_price`&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;DECIMAL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;11&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;k&quot;&gt;NULL&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;DEFAULT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;nv&quot;&gt;`volume`&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;BIGINT&lt;/span&gt;&lt;span class=&quot;p&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;k&quot;&gt;NULL&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;DEFAULT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;PRIMARY&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;KEY&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;`id`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;INDEX&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;`price_date`&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;`price_date`&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;ASC&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;INDEX&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;`ticker_id`&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;`ticker_id`&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;ASC&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;CONSTRAINT&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;`fk_ticker_id`&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;FOREIGN&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;KEY&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;`ticker_id`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;REFERENCES&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;`security`&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;`id`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;ON&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;DELETE&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NO&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ACTION&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;ON&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;UPDATE&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NO&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ACTION&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;CONSTRAINT&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;`fk_data_vendor_id`&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;FOREIGN&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;KEY&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;`data_vendor_id`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;REFERENCES&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;`data_vendor`&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;`id`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;ON&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;DELETE&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NO&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ACTION&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;ON&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;UPDATE&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NO&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ACTION&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;ENGINE&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;InnoDB&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;DEFAULT&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;CHARACTER&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;SET&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;utf8&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;Notice the indexes (I believe ‘indices’ is incorrect in this context) on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;price_date&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ticker_id&lt;/code&gt;: these are important, because for the most part I know I will be wanting to select stock prices either by date or by ticker.&lt;/p&gt;

&lt;p&gt;Putting it all together, we have the following ER diagram:&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/findb_price_schema.png&quot; style=&quot;width:80%;&quot; /&gt;
&lt;/center&gt;

&lt;h2 id=&quot;populating-the-database&quot;&gt;Populating the database&lt;/h2&gt;

&lt;p&gt;Let’s start by connecting to the database via python. We could do this via an ORM, but it might be a bit simpler to use a MySQL interface. As seems to be a recurring theme, there are &lt;a href=&quot;https://stackoverflow.com/questions/372885/how-do-i-connect-to-a-mysql-database-in-python&quot;&gt;many options&lt;/a&gt; for interacting with MariaDB from python. In previous projects I had used &lt;a href=&quot;http://mysql-python.sourceforge.net/MySQLdb.html&quot;&gt;MySQLdb&lt;/a&gt;, which is generally straightforward but a hassle to install. In order to make this tutorial simpler, I’ve chosen to use a popular pure-python alternative, &lt;a href=&quot;https://github.com/PyMySQL/PyMySQL&quot;&gt;PyMySQL&lt;/a&gt; – functionality is almost identical, but it is &lt;em&gt;much&lt;/em&gt; easier to install. Let’s proceed. The top of our python file will begin with the relevant imports.&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;nn&quot;&gt;time&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&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;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;pandas_datareader&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pdr&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;pymysql.cursors&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;fix_yahoo_finance&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;yf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pdr_override&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;Connecting to the database is very simple; if you are having a problem here, it is likely because of the MySQL login details.&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;conn&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pymysql&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;connect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;host&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'localhost'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; 
                       &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'root'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; 
                       &lt;span class=&quot;n&quot;&gt;db&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'stock_prices'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;cursor&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;conn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cursor&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;Just a quick note: it is quite difficult to format the code properly for these blog posts, so if you are copy-pasting (which I don’t recommend!) then please be aware that there are some forced line breaks below that will raise errors in python (particularly within long SQL statements).&lt;/p&gt;

&lt;h3 id=&quot;adding-an-exchange-and-a-list-of-securities&quot;&gt;Adding an exchange and a list of securities&lt;/h3&gt;

&lt;p&gt;We will start by manually writing an exchange to the database:&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;sql&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;INSERT INTO exchange (name, currency) VALUES ('NYSE', 'USD')&quot;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;cursor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;execute&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sql&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;conn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;commit&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;As a rule of thumb, after every set of like database operations (e.g. after you’ve added/deleted a few things), you should throw in a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;conn.commit()&lt;/code&gt; which makes the changes permanent.&lt;/p&gt;

&lt;p&gt;After adding the exchange, we need to give it some children (i.e securities). Luckily, there are &lt;a href=&quot;https://www.nasdaq.com/screening/company-list.aspx&quot;&gt;official lists&lt;/a&gt; in csv format containing all of the tickers in the NYSE, NASDAQ and AMEX. Head to that link and download the NYSE list. Within my project directory, I put this in a subfolder called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;data&lt;/code&gt;, and named it &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nyse_tickers.csv&lt;/code&gt;. Parsing these tickers is not very difficult:&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;nyse&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;n&quot;&gt;read_csv&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'data/nyse_tickers.csv'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Number of NYSE tickers:&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;nyse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;nyse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;drop&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'LastSale'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'MarketCap'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'IPOyear'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'Summary Quote'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
           &lt;span class=&quot;s&quot;&gt;'Unnamed: 9'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'ADR TSO'&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;n&quot;&gt;inplace&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;nyse&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;s&quot;&gt;'ticker'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'name'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'sector'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'industry'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;nyse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'exchange_id'&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;n&quot;&gt;nyse&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;nyse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cols&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;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cols&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;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Notice that I’ve done some weird manipulations with the column names, and I’ve added a new constant column corresponding to the exchange ID of NYSE (in our &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;exchange&lt;/code&gt; table). This allows for an elegant snippet to add the data to our database:&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;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;row&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;nyse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;itertuples&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;index&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;k&quot;&gt;try&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;cursor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;execute&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;INSERT INTO security (exchange_id, 
        ticker, name, sector, industry) VALUES (%s, %s, %s, %s, %s)&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; 
        &lt;span class=&quot;n&quot;&gt;row&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;except&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;# Assume that the exception is because sector
&lt;/span&gt;        &lt;span class=&quot;c1&quot;&gt;# and/or industry are missing
&lt;/span&gt;        &lt;span class=&quot;n&quot;&gt;cursor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;execute&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;INSERT INTO security (exchange_id, 
        ticker, name) VALUES (%s, %s, %s)&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;row&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;conn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;commit&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;h3 id=&quot;adding-price-data&quot;&gt;Adding price data&lt;/h3&gt;

&lt;p&gt;Before we add price data, we must manually add a data vendor:&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;sql&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;INSERT INTO data_vendor (name, website_url) VALUES &quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; \
      &lt;span class=&quot;s&quot;&gt;&quot;('YahooFinance', 'https://finance.yahoo.com')&quot;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;cursor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;execute&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sql&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;conn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;commit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;YAHOO_VENDOR_ID&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;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Next, we list the securities that have been added, so that we know the tickers for which we should download data.&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;all_tickers&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;n&quot;&gt;read_sql&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;SELECT ticker, id FROM security&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;conn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;ticker_index&lt;/span&gt; &lt;span class=&quot;o&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;n&quot;&gt;all_tickers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;to_dict&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'split'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'data'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;tickers&lt;/span&gt; &lt;span class=&quot;o&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;n&quot;&gt;ticker_index&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;keys&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;In principle, now all we have to do is iterate over the list of tickers, download data from pandas-datareader, then write to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;daily_price&lt;/code&gt;. This can be implemented very simply 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;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ticker&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tickers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# Download data
&lt;/span&gt;    &lt;span class=&quot;n&quot;&gt;df&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pdr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get_data_yahoo&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_date&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# Write to daily_price
&lt;/span&gt;    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;row&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;df&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;itertuples&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;values&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;YAHOO_VENDOR_ID&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ticker_index&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;o&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;n&quot;&gt;row&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;cursor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;execute&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;INSERT INTO daily_price (data_vendor_id, 
        ticker_id, price_date, open_price, high_price, low_price, 
        close_price, adj_close_price, volume) VALUES 
        (%s, %s, %s, %s, %s, %s, %s, %s, %s)&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;nb&quot;&gt;tuple&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;values&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;In practice, however, we need to add some fault tolerance. For example, I have found that pandas-datareader inconsistently throttles. One solution is to split the download into chunks, and keep track of failed downloads. Throwing a couple of try/except statements in there too, we get the following two methods:&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;download_data_chunk&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;start_idx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;end_idx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tickerlist&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;bp&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&quot;&quot;&quot;
    Download stock data using pandas-datareader
    :param start_idx: start index
    :param end_idx: end index
    :param tickerlist: which tickers are meant to be downloaded
    :param start_date: the starting date for each ticker
    :return: writes data to mysql database
    &quot;&quot;&quot;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;ms_tickers&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;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ticker&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tickerlist&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;start_idx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;end_idx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]:&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;df&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pdr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get_data_yahoo&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_date&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;df&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;empty&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;k&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;s&quot;&gt;&quot;df is empty 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;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;ms_tickers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;append&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;time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sleep&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;continue&lt;/span&gt;

        &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;row&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;df&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;itertuples&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;values&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;YAHOO_VENDOR_ID&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ticker_index&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;o&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;n&quot;&gt;row&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;sql&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;INSERT INTO daily_price (data_vendor_id, 
                        ticker_id, price_date, open_price,
                        high_price, low_price, close_price, 
                        adj_close_price, volume) 
                        VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)&quot;&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;cursor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;execute&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sql&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;tuple&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;values&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;except&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Exception&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;print&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;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;conn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;commit&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;ms_tickers&lt;/span&gt;


&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;download_all_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tickerlist&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;chunk_size&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;n&quot;&gt;start_date&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;c1&quot;&gt;# Hacky snippet to get the ceiling
&lt;/span&gt;    &lt;span class=&quot;n&quot;&gt;n_chunks&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&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;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tickerlist&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;chunk_size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;ms_tickers&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;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;range&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;n&quot;&gt;n_chunks&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;chunk_size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;# This will download data from the earliest possible date
&lt;/span&gt;        &lt;span class=&quot;n&quot;&gt;ms_from_chunk&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;download_data_chunk&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;chunk_size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; 
                                            &lt;span class=&quot;n&quot;&gt;tickerlist&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;ms_tickers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ms_from_chunk&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        
        &lt;span class=&quot;c1&quot;&gt;# Check for possible throttling
&lt;/span&gt;        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ms_from_chunk&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;mi&quot;&gt;40&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sleep&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;120&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;n&quot;&gt;time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sleep&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;10&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;ms_tickers&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;After this, you can do a second pass over the missing tickers to try to get as much data as possible.&lt;/p&gt;

&lt;h3 id=&quot;updating-data&quot;&gt;Updating data&lt;/h3&gt;

&lt;p&gt;At specified intervals (perhaps via a cron job), you may want to update the prices in the database. Here’s a quick script to do that:&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;update_prices&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# Get present tickers
&lt;/span&gt;    &lt;span class=&quot;n&quot;&gt;present_ticker_ids&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;n&quot;&gt;read_sql&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;SELECT DISTINCT ticker_id 
                                    FROM daily_price&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; 
                                    &lt;span class=&quot;n&quot;&gt;conn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;index_ticker&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;v&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;k&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;k&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;v&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ticker_index&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;items&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()}&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;present_tickers&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;index_ticker&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&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;i&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&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;n&quot;&gt;present_ticker_ids&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'ticker_id'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])]&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# Get last date
&lt;/span&gt;    &lt;span class=&quot;n&quot;&gt;sql&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;SELECT price_date FROM daily_price WHERE ticker_id=1&quot;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;dates&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;n&quot;&gt;read_sql&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sql&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;conn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;last_date&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dates&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;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;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;download_all_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;present_tickers&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;last_date&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;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;I hope this post has given you an idea of how you might go about creating your own securities database. In future, I’m quite keen to add to this database new tables for stock &lt;em&gt;fundamental&lt;/em&gt; data as well, but this would be much more complicated unless I’m only interested in a specific list of features. Otherwise, NoSQL might be a better option.&lt;/p&gt;

&lt;p&gt;But for now, I’m happy with the price database. Looking back, I think it was a very worthwhile time investment – I have already made quite significant use of the database and am enjoying the convenience. I can now prototype algorithms much faster, without the hassle of having to re-download data every time or find the csv files somewhere on my filesystem. Not to mention the fact that I won’t be as heavily affected by the whims of some Yahoo Finance team-leader who decides to change the UI again.&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>Learning Machine Learning</title>
   <link href="https://reasonabledeviations.com/2017/12/28/learning-ml/"/>
   <updated>2017-12-28T00:00:00+00:00</updated>
   <id>https://reasonabledeviations.com/2017/12/28/learning-ml</id>
   <content type="html">&lt;p&gt;Two years ago I was an absolute novice at machine learning: I had read around the subject a little bit, could probably rattle off a few of the buzzwords, and had some appreciation of the general idea, but there was no way I could have developed a predictive model beyond linear regression.&lt;!--more--&gt; I was somewhere near the peak of Mount Stupid (from a great chart by &lt;a href=&quot;https://www.smbc-comics.com/?id=2475&quot;&gt;SMBC&lt;/a&gt;):&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images//mt_stupid.png&quot; style=&quot;width:400px;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;Fast forward to the present day – I’m just about escaping the local minimum on the other side. Although I definitely wouldn’t claim anything close to mastery of the subject, a few months ago I took a step back to look at my progress and realised that I was definitely not a beginner any longer: I had built a couple of extensive projects, created some educational resources, and started applying machine learning models to my professional work. I therefore think that I am in a reasonable place to share the mistakes I made when learning ML, so that other people don’t have to make them!&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;Edit as of Jan 2022&lt;/strong&gt;: in retrospect, this post was probably written near the Dunning-Kruger peak. I will happily say that I am in one of the valleys, though I’m not sure which one. That said, I still stand by the advice in this post!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;It is worth noting that two years is a very long time; I was far too occupied (with the army, among other things) to commit fully to machine learning, but I think that most people could achieve near-mastery in 6 months if they so pleased.&lt;/p&gt;

&lt;p&gt;In any case, from the perspective of a fellow learner who has had a small head start, and without any guarantees of it being the most efficient way to learn the subject, I’m going to present some advice. The rough overview is as follows:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;a href=&quot;#learn-to-code&quot;&gt;Learn to code&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#learn-the-basics-of-ml&quot;&gt;Learn the basics of ML&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#pick-a-subject-you-are-interested-in&quot;&gt;Pick a subject you are interested in&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#pick-an-interesting-algorithm-and-dive-deep&quot;&gt;Pick an interesting algorithm, and dive deep&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#review-your-achievements&quot;&gt;Review your achievements&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For each subsection, I’ll try to provide some resource recommendations, and some tips.&lt;/p&gt;

&lt;h2 id=&quot;learn-to-code&quot;&gt;Learn to code&lt;/h2&gt;

&lt;p&gt;For better or worse, programming is basically a prerequisite for machine learning. It is true that there are some GUI environments, most notably &lt;a href=&quot;https://www.cs.waikato.ac.nz/ml/weka/&quot;&gt;Weka&lt;/a&gt; and the &lt;a href=&quot;https://studio.azureml.net/&quot;&gt;Azure ML studio&lt;/a&gt;, but users of these tools are definitely in the minority. Programming gives you far more control over the whole machine learning process. I know from experience that ‘control’ is often the last thing a beginner wants, because with great power comes the responsibility to actually write functional code, but it’s honestly hard to appreciate ML without programming. The one exception I would cautiously make is if you have experience with statistical analysis (e.g. in Excel), in which case Azure might not be a bad place to start.&lt;/p&gt;

&lt;p&gt;For those new to programming, a natural question is what language to use. This is a big decision, but spending too long deciding will hold you back. I really recommend just picking &lt;a href=&quot;https://www.python.org/&quot;&gt;python 3&lt;/a&gt; and sticking with it: it has a very mature environment for machine learning, with simple syntax and an abundance of tutorials online. It has the added benefit of being a general-purpose language, so you can really do anything you want with it.&lt;/p&gt;

&lt;p&gt;I can’t remember exactly how I learnt python (it was a long time ago): I recall it being a dark and uncomfortable time, probably because I tried to navigate my own way through. I suggest you choose one of the many tutorials available. Here are three suggestions:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://automatetheboringstuff.com/&quot;&gt;Automate the Boring Stuff with Python&lt;/a&gt;, by Al Sweigart. Very highly regarded, although it doesn’t directly address data science. It teaches the syntax, and just how useful python can be. Even if you know for a fact that you’re only interested in data science, the first 12 chapters are critical.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://python.swaroopch.com/&quot;&gt;A Byte of Python&lt;/a&gt;. A great online book which teachers you quite a lot of the language. I used this a lot while I was learning.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.coursera.org/learn/interactive-python-1&quot;&gt;Python course from Rice University&lt;/a&gt;. Comprehensive and beginner-friendly, but perhaps with an excessive focus on GUI programming.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Some tips:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;You don’t need to know all that much about writing classes in python, at least for ML. Understanding the basic language features, along with pandas dataframes, is probably sufficient.&lt;/li&gt;
  &lt;li&gt;Please use python 3.&lt;/li&gt;
  &lt;li&gt;I’d recommend against trying to learn about ML and learning the python language at the same time.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;learn-the-basics-of-ml&quot;&gt;Learn the basics of ML&lt;/h2&gt;

&lt;p&gt;Machine learning is a very mathematical subject, and though it’s better if you can learn it rigorously (with all the maths), it’s not necessary. But be aware that you will need a firm grasp of your high school maths if you want to progress further.&lt;/p&gt;

&lt;p&gt;One of the seemingly unanimous recommendations is Andrew Ng’s &lt;a href=&quot;https://www.coursera.org/learn/machine-learning&quot;&gt;Coursera course&lt;/a&gt;. My honest opinion is that this is likely to put you off ML unless you are a very quick learner (and good at maths). I didn’t finish the course, and after giving up, I avoided ML for a long time. One of the problems is that he uses Octave/Matlab as a programming language, which nobody uses. That being said, the course does give amazing insight (provided you can follow the maths), and he covers many relevant algorithms. If you feel up to it, it may be worth following along with the videos but doing all of the assignments in python. Then you can compare your work with other people’s &lt;a href=&quot;https://github.com/JWarmenhoven/Coursera-Machine-Learning&quot;&gt;solutions on GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;My personal recommendation is &lt;a href=&quot;https://www.udacity.com/course/machine-learning--ud262?utm_medium=referral&amp;amp;utm_campaign=api&quot;&gt;Georgia Tech Machine Learning&lt;/a&gt;. This course spends a lot more time explaining the intuition behind the algorithms before going into the maths or the code, which is invaluable as a learner.&lt;/p&gt;

&lt;p&gt;I also bought the &lt;a href=&quot;https://www.udemy.com/machinelearning/&quot;&gt;Udemy Machine Learning&lt;/a&gt; course for $10. While Udemy has a pretty bad rep nowadays, this course is actually decent: it gives you a basic understanding of a wide range of algorithms. There was a time when this lack of depth frustrated me, but looking back, I realise how useful it is to be given a practical tour across the whole ML scene. To be fair, you can probably learn this directly from the &lt;a href=&quot;http://scikit-learn.org/&quot;&gt;scikit-learn documentation &lt;/a&gt;, but it does help to have video explanations. I’ve put my code for this course onto &lt;a href=&quot;https://github.com/robertmartin8/udemyML&quot;&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Tips:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;If the maths is too hard, don’t worry about it. At this stage, it’s far more useful to understand intuitively how the algorithm works, and more importantly how to apply the algorithm in python. If you truly appreciate how useful ML can be, and become excited by it, you will be much more inclined to learn the maths.&lt;/li&gt;
  &lt;li&gt;Ensure that you pick up the necessary skills with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;numpy&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pandas&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sklearn&lt;/code&gt;. These libraries are fast, and knowing the common usage patterns will save you from reinventing the wheel.&lt;/li&gt;
  &lt;li&gt;Keep a folder with lots of different python files, each containing an example application of a particular machine learning algorithm to a dataset (dummy or real).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;pick-a-subject-you-are-interested-in&quot;&gt;Pick a subject you are interested in&lt;/h2&gt;

&lt;p&gt;After acquiring the basic skills, it is critical to find a subject that you are interested in and to build a project. Machine learning is a widely applicable tool, and I’d wager that there’s an application of ML in whatever field you’re interested in.&lt;/p&gt;

&lt;p&gt;Once you decide, it’s probably a good idea to look for a tutorial series on that particular topic. I was most interested in finance, so I followed an excellent youtube playlist from Sentdex, called &lt;a href=&quot;https://www.youtube.com/playlist?list=PLQVvvaa0QuDd0flgGphKCej-9jp-QdzZ3&quot;&gt;Machine Learning for Investing&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;At this stage, your goal is to build a fully working project, so that you come into contact with the whole machine learning stack, all the way from raw data to eventual predictions (and preferably some visualisations too). Performance isn’t the most important thing right now, though it would be nice if you can do better than random guessing :)&lt;/p&gt;

&lt;p&gt;Tips:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Try not to copy and paste code, at the very least type it out. You’ll learn faster this way.&lt;/li&gt;
  &lt;li&gt;Don’t spend too long trying to figure out what project to do: indecision is the worst form of procrastination when it comes to learning new subjects. If you’re really struggling to think of something, try &lt;a href=&quot;https://www.kaggle.com/competitions&quot;&gt;kaggle.com&lt;/a&gt;. Don’t be put off by the fact that it’s a ‘competition’ – it’s also a good place to learn, and lots of people share notebooks showing what they’ve tried. Seeing all these different approaches is useful for beginners, as you can quickly try experimenting with existing solutions to see what works and what doesn’t.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;pick-an-interesting-algorithm-and-dive-deep&quot;&gt;Pick an interesting algorithm, and dive deep&lt;/h2&gt;

&lt;p&gt;If you’ve followed the steps thus far, you should have a crude, but functional, project in a field that you are interested in. Now is the time to ascend to the next level, by really trying to understand what you are doing and improving your machine learning performance.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Learn about cross validation, particularly about the very important &lt;strong&gt;bias-variance tradeoff&lt;/strong&gt;. Improve your project’s methodology.&lt;/li&gt;
  &lt;li&gt;Examine the different methods of sampling to make training, validation, and test sets.&lt;/li&gt;
  &lt;li&gt;Explore different performance metrics, for example the &lt;a href=&quot;https://en.wikipedia.org/wiki/Receiver_operating_characteristic&quot;&gt;ROC curve&lt;/a&gt;. Which one is the most relevant for your problem? Learn about what the F-score is, and why it’s commonly used instead of accuracy.&lt;/li&gt;
  &lt;li&gt;Evaluate your feature importance, and maybe even try some feature selection/engineering.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After you’ve done all of this, it may be that your performance has actually gotten worse (for example, if you’ve changed to a more relevant metric). This was the case for me, as I changed from accuracy to precision – precision is much more pertinent to stock predictions because it’s the false positives that lose you money. But at least I had a logically sound methodology off of which to base future endeavours.&lt;/p&gt;

&lt;p&gt;Now, you should do a quick search on Google Scholar to see what algorithms are most successful in your field. For example, if you chose to deal with computer vision, you’d find that Convolutional Neural Networks are the de facto standard. Then, really hit the books – try to understand the algorithm in detail, to the point where you could recreate it from scratch. This naturally involves a lot of maths, but hopefully at this stage you’re excited enough about ML to weather it. You may find &lt;a href=&quot;https://web.stanford.edu/~hastie/Papers/ESLII.pdf&quot;&gt;The Elements of Statistical Learning&lt;/a&gt; to be useful: it is a legendary textbook that basically defines the field of machine learning. Supplement this with alternative explanations and tutorials from youtube or stackexchange, and you will be in a good place. In particular, you should look out for the actual meaning of all of the different metaparameters. This is very important, because it will guide your future search for optimal metaparameters to increase your performance.&lt;/p&gt;

&lt;p&gt;In my case, SVMs and neural networks were common in the literature on stock prediction, but I wasn’t too keen to proceed with them. I found in a more recent study that &lt;strong&gt;boosted decision trees&lt;/strong&gt; were having quite a lot of success in a number of different fields, so I was interested to see how they would do in stock prediction. I’ve written a &lt;a href=&quot;/2017/10/10/gradient-tree-boosting/&quot;&gt;post&lt;/a&gt; on my investigation into boosted trees, so that you can get an idea about how deep I went into the subject. I also found the seminal academic papers to be illuminating, but you should probably leave them for last.&lt;/p&gt;

&lt;p&gt;Once you understand the algorithm, you should be able to go back to your original project with fresh eyes, applying your new knowledge to increase performance. But even if your performance won’t increase, you’ll have a much better idea as to why.&lt;/p&gt;

&lt;h2 id=&quot;review-your-achievements&quot;&gt;Review your achievements&lt;/h2&gt;

&lt;p&gt;Although it is true that there is always more to learn about a subject, sometimes you do need to take a step back and acknowledge that you’ve come a long way. Look over your projects, your notes, and take some pride in what you have learnt. I’m most proud of an extensive tutorial I made, &lt;a href=&quot;https://github.com/robertmartin8/MachineLearningStocks&quot;&gt;MachineLearningStocks&lt;/a&gt;, which is meant to help beginners get started with applying ML to stock prediction.&lt;/p&gt;

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

&lt;p&gt;Machine Learning is a subject that is not hard to pick up, but has a learning curve that steepens the further you get into it. Just being able to &lt;em&gt;apply&lt;/em&gt; machine learning puts you ahead of most of the world. It is constantly evolving, with a startling amount of research released daily. But don’t let this put you off: with concerted effort and the right resources, it’s definitely feasible for you to contribute to that ever-expanding corpus.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Gradient tree boosting and XGBoost</title>
   <link href="https://reasonabledeviations.com/2017/10/10/gradient-tree-boosting/"/>
   <updated>2017-10-10T00:00:00+00:00</updated>
   <id>https://reasonabledeviations.com/2017/10/10/gradient-tree-boosting</id>
   <content type="html">&lt;p&gt;Decision trees make for pretty vanilla classifiers: they do an unspectacular job with most machine learning tasks, and you’d be forgiven for overlooking them when deciding on a classification algorithm. But decision trees happen to be the cornerstone of a powerful class of learning algorithms: &lt;em&gt;gradient tree boosting&lt;/em&gt; methods. I will try to elucidate the (short) history of gradient tree boosting, starting with the pioneering implementation of boosted trees and ending with the state-of-the-art. I’m afraid there will be a hearty amount of mathematics, but it is really a very intuitive topic at its core.
&lt;!--more--&gt;&lt;/p&gt;

&lt;p&gt;Note that I will &lt;em&gt;not&lt;/em&gt; be covering the actual functioning of decision trees; for that, you may either refer to &lt;a href=&quot;https://reasonabledeviations.com/notes/papers/induction_decision_trees&quot;&gt;my notes&lt;/a&gt; on the original paper, or to the excellent &lt;a href=&quot;https://en.wikipedia.org/wiki/Decision_tree_learning&quot;&gt;Wikipedia page&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;weak-learners-and-boosting&quot;&gt;Weak learners and boosting&lt;/h2&gt;

&lt;p&gt;As I have mentioned, I don’t think decision trees on their own are especially useful. You might even say that they are ‘weak classifiers’, or generally, ‘weak learners’. This, in fact, is a technical term which refers to predictive models which only do a little bit better than random guessing.&lt;/p&gt;

&lt;p&gt;However, Robert Schapire (currently a Princeton professor) discovered in his classic paper &lt;a href=&quot;https://www.cs.princeton.edu/~schapire/papers/strengthofweak.pdf&quot;&gt;&lt;em&gt;The Strength of Weak Learnability&lt;/em&gt; (1990)&lt;/a&gt;, that it is possible to combine a number of weak learners into a strong learning algorithm – a process called &lt;strong&gt;boosting&lt;/strong&gt;. The first tangible algorithm for doing so came in the form of Adaptive Boosting, or AdaBoost, invented by Freund and Schapire in 1997 (their &lt;a href=&quot;http://www.face-rec.org/algorithms/Boosting-Ensemble/decision-theoretic_generalization.pdf&quot;&gt;paper&lt;/a&gt; now has about 15,000 citations). Essentially, the idea behind AdaBoost is that you can build a strong algorithm by making weak learners learn from their mistakes. This is done by sequentially fitting new classifiers, each one giving more weight to the datapoints for which their predecessors made incorrect predictions. In the end, you take the weighted ‘vote’ of all the weak classifiers, &lt;em&gt;et voilà&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;I won’t go into the details of AdaBoost, because it is slightly tangential to the goal of this post, but Chris McCormick has an excellent exposition of the algorithm &lt;a href=&quot;http://mccormickml.com/2013/12/13/adaboost-tutorial/&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;additive-models&quot;&gt;Additive models&lt;/h2&gt;

&lt;p&gt;Around the same time, a lot of research was going on into &lt;strong&gt;additive expansions&lt;/strong&gt;, which are linear combinations of &lt;strong&gt;basis functions&lt;/strong&gt; $b(x;\gamma)$ with coefficients $\beta$:&lt;/p&gt;

\[f(x) = \sum_{m=0}^{M} \beta_m b(x;\gamma_m)\]

&lt;p&gt;Here, $x$ is an input vector (e.g. training data), and $\gamma_m$ denotes the parameters of the &lt;em&gt;m&lt;/em&gt;th basis function.&lt;/p&gt;

&lt;p&gt;What is interesting is that additive expansions are fundamentally equivalent to boosting methods, as shown by &lt;a href=&quot;https://web.stanford.edu/~hastie/Papers/AdditiveLogisticRegression/alr.pdf&quot;&gt;Friedman, Hastie and Tibshirani (2000)&lt;/a&gt;. If you take $b(x; \gamma)$ to be a weak learner, then it is clear that $f(x)$ presents a weighted vote of the weak learners, just as in AdaBoost. So now instead of dealing with the somewhat vague concept of boosting, all we have to do is figure out how to fit an additive expansion $f(x)$ to some training data, denoted by $\{(y_i, x_i)\}_1^N$.&lt;/p&gt;

&lt;p&gt;A first attempt to do this is to minimise some loss function $L(y_i, f(x_i))$
for each training example, such that the parameters are:&lt;/p&gt;

\[\arg\min_{\{\beta_m, \gamma_m \}_1^M} \sum_{i=1}^N L \left( y_i, \sum_{m=1}^M \beta_m b(x_i;\gamma_m)\right)\]

&lt;p&gt;That looks to be quite a monstrous summation, but it is only the cumbersome notation: if you carefully look at the expression, you will see that it is really just choosing $\beta_m$ and $\gamma_m$ values that will minimise the loss of $f(x)= \sum_{m=1}^M \beta_m b(x_i;\gamma_m)$ over all training examples.&lt;/p&gt;

&lt;p&gt;While the above optimisation may not be conceptually difficult, it is &lt;em&gt;computationally&lt;/em&gt; difficult. Fortunately, additive expansions invite a &lt;strong&gt;forward stagewise&lt;/strong&gt; approximation. Instead of trying to optimise the parameters all at once, we do it in stages, slowly building up an approximately optimal model. At each iteration $m$ we find
an optimal $\beta_m$ and $\gamma_m$, and add the new term to the existing predictor function $f_{m-1}(x)$.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Forward Stagewise Additive Modelling (FSAM) algorithm&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;Set&lt;/strong&gt; $~f_0(x) = 0$&lt;/p&gt;

  &lt;p&gt;&lt;strong&gt;For&lt;/strong&gt; $~m=1,\ldots, M$&lt;/p&gt;
  &lt;ul&gt;
    &lt;li&gt;&lt;strong&gt;Compute&lt;/strong&gt;:&lt;/li&gt;
  &lt;/ul&gt;

\[(\beta_m, \gamma_m) = \arg \min_{\beta, \gamma} \sum_{i=1}^N L(y_i, f_{m-1}(x_i)+\beta b(x_i;\gamma))\]

  &lt;ul&gt;
    &lt;li&gt;&lt;strong&gt;Set&lt;/strong&gt; $~f_m(x)=f_{m-1}(x)+\beta_m b(x;\gamma_m)$&lt;/li&gt;
  &lt;/ul&gt;

  &lt;p&gt;&lt;strong&gt;Return&lt;/strong&gt; $~f_M(x)$&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3 id=&quot;gradient-tree-boosting&quot;&gt;Gradient tree boosting&lt;/h3&gt;

&lt;p&gt;Unfortunately, the difficulty with the FSAM algorithm is the actual optimisation step:&lt;/p&gt;

\[(\beta_m, \gamma_m) = \arg \min_{\beta, \gamma} \sum_{i=1}^N L(y_i, f_{m-1}(x_i)+\beta b(x_i;\gamma))\]

&lt;p&gt;For all but the simplest loss functions, this presents a prohibitively difficult optimisation problem. One solution is gradient boosting, as described by Friedman in his 2001 Paper &lt;a href=&quot;https://statweb.stanford.edu/~jhf/ftp/trebst.pdf&quot;&gt;&lt;em&gt;Greedy Function Approximation: a Gradient Boosting Machine&lt;/em&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;It is worth mentioning at this point that although we can use a large variety of models for $b(x;\gamma)$, a decision tree is a common choice. We will adopt the notation conventions from &lt;a href=&quot;https://web.stanford.edu/~hastie/Papers/ESLII.pdf&quot;&gt;&lt;em&gt;Elements of Statistical Learning&lt;/em&gt;&lt;/a&gt;. A tree with $J$ &lt;strong&gt;terminal nodes&lt;/strong&gt; (or ‘leaves’) is represented as follows:&lt;/p&gt;

\[T(x; \Theta) = \sum_{j=1}^J \gamma_j I(x \in R_j), \qquad \Theta = \{R_j, \gamma_j \}_1^J\]

&lt;p&gt;Such a tree partitions the feature space into $J$ disjoint regions/leaves, each having an associated constant $\gamma_j$ which acts as a weight on that leaf. $I$ is the &lt;em&gt;indicator function&lt;/em&gt;, returning 1 if its argument is true, and 0 if false. $\Theta$ encodes both the leaf weights and the ‘structure’ of the tree.&lt;/p&gt;

&lt;p&gt;Thus the optimisation step in the FSAM algorithm becomes:&lt;/p&gt;

\[\Theta_m = \arg \min_{\Theta} \sum_{i=1}^N L(y_i, f_{m-1}(x_i) + T(x_i; \Theta))\]

&lt;p&gt;Note that we no longer need to consider the constants $\beta_m$, as they are effectively absorbed into $\gamma_{jm}$.&lt;/p&gt;

&lt;p&gt;One method of optimising the above is to borrow the idea of classical gradient descent, a wonderfully intuitive method which realises that the negative gradient of the loss function points in the direction of fastest decreasing loss, so updating parameters in the direction of the gradient must also decrease loss! Of course, in the case of boosting we are optimising in &lt;em&gt;function space&lt;/em&gt; rather than &lt;em&gt;parameter space&lt;/em&gt; – we are trying to find the &lt;em&gt;optimal tree&lt;/em&gt; to add to $f_{m-1}(x)$. The components of the gradient vector $\mathbf{g}_m \in \mathbb{R}^N$ are thus given by:&lt;/p&gt;

\[g_{im} = \frac{\partial L(y_i, f_{m-1}(x_i))}{\partial f_{m-1}(x_i)}\]

&lt;p&gt;Again, because $-\mathbf{g}_m$ points in the direction of fastest decreasing loss, we can simply fit the tree to the negative gradient instead of trying to optimise with respect to some loss function – in both cases, the tree will be one that decreases loss, but in the former case we have the computational benefits of fitting with squared error:&lt;/p&gt;

\[\Theta_m = \arg \min_{\Theta} \sum_{i=1}^N (-g_{im} - T(x_i;\Theta))^2\]

&lt;p&gt;I think it is definitely worth reading over the last few paragraphs again; it is a beautifully subtle piece of reasoning. The full algorithm is presented below: we start by initialising the model with the best constant weights. Then, in each round, we fit a tree to the negative gradient before choosing optimal leaf weights.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Gradient tree boosting algorithm&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;Set&lt;/strong&gt; $~f_0(x) = \arg \min_\gamma \sum_{i=1}^N L(y_i, \gamma)$&lt;/p&gt;

  &lt;p&gt;&lt;strong&gt;For&lt;/strong&gt; $~m=1,\ldots, M$&lt;/p&gt;
  &lt;ul&gt;
    &lt;li&gt;&lt;strong&gt;For&lt;/strong&gt; $~i=1, \ldots, N$
      &lt;ul&gt;
        &lt;li&gt;&lt;strong&gt;Compute&lt;/strong&gt;:&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/li&gt;
  &lt;/ul&gt;

\[g_{im} = -\frac{\partial L(y_i, f_{m-1}(x_i))}{\partial f_{m-1}(x_i)}\]

  &lt;ul&gt;
    &lt;li&gt;&lt;strong&gt;Fit&lt;/strong&gt; tree to targets $-g_{im}$ giving:&lt;/li&gt;
  &lt;/ul&gt;

\[R_{jm}, \qquad j = 1,2,\ldots,J_m\]

  &lt;ul&gt;
    &lt;li&gt;&lt;strong&gt;For&lt;/strong&gt; $~j=1,\ldots,J_m$:
      &lt;ul&gt;
        &lt;li&gt;&lt;strong&gt;Compute:&lt;/strong&gt;&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/li&gt;
  &lt;/ul&gt;

\[\gamma_{jm} = \arg \min_\gamma \sum_{x_i \in R_{jm}} L(y_i, f_{m-1}(x_i) + \gamma)\]

  &lt;ul&gt;
    &lt;li&gt;&lt;strong&gt;Set&lt;/strong&gt; $~f_m(x)=f_{m-1}(x)+ \sum_{j=1}^{J_m} \gamma_{jm} I(x \in R_{jm})$&lt;/li&gt;
  &lt;/ul&gt;

  &lt;p&gt;&lt;strong&gt;Return&lt;/strong&gt; $~f_M(x)$&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;regularisation&quot;&gt;Regularisation&lt;/h2&gt;

&lt;p&gt;A popular method of regularising boosted tree models, proposed in the original paper by Friedman, is &lt;em&gt;shrinkage&lt;/em&gt;, wherein additional trees are multiplied by a small shrinkage parameter $\nu$ before being added to the existing model. This would change the update step in the gradient tree boosting algorithm to:&lt;/p&gt;

\[f_m(x) = f_{m-1}(x) + \nu \sum_{j=1}^{J_m} \gamma_{jm} I(x\in R_{jm})\]

&lt;p&gt;Note that we cannot absorb $\nu$ into $\gamma_{jm}$: even though the latter are the &lt;em&gt;best&lt;/em&gt; parameters (having the least loss on the training data), we must shrink their contribution to the final model to reduce overfitting. The meta-parameter $\nu$ is intuitively the same as the learning rate and is often referred to as such: smaller values (in the order of $0.1$) lead to slower learning, but improved generalisation ability.&lt;/p&gt;

&lt;p&gt;Another method that has previously been employed with success is
&lt;em&gt;subsampling&lt;/em&gt;, in which a certain subset of the training data is
randomly selected (without replacement) at each boosting round.
&lt;em&gt;Stochastic gradient boosting&lt;/em&gt; subsamples by row; not
all available training data is used in a given round. These methods have been found to give computational benefits and improved generalisation.&lt;/p&gt;

&lt;h2 id=&quot;xgboost&quot;&gt;XGBoost&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/dmlc/xgboost&quot;&gt;XGBoost&lt;/a&gt; (‘eXtreme Gradient Boosting’) is as an open-source
gradient boosting software package that won renown for simply being &lt;em&gt;the&lt;/em&gt; algorithm you used if you wanted to win &lt;a href=&quot;https://www.kaggle.com&quot;&gt;Kaggle&lt;/a&gt; competitions.&lt;/p&gt;

&lt;p&gt;XGBoost provides an alternative perspective on the optimisation problem in gradient tree boosting (reproduced below):&lt;/p&gt;

\[\Theta_m = \arg \min_{\Theta} \sum_{i=1}^N L(y_i, f_{m-1}(x_i) + T(x_i; \Theta))\]

&lt;p&gt;Instead of making the ‘logical leap’ of fitting to gradients, we simply find the second order Taylor expansion of the loss function:&lt;/p&gt;

\[L(y_i, f_{m-1}(x_i) + T(x_i; \Theta)) = L(y_i, f_{m-1}(x_i)) + g_{im} T(x_i; \Theta) + \frac 1 2 h_{im}T(x_i; \Theta)^2\]

&lt;p&gt;where&lt;/p&gt;

\[\begin{aligned}
    g_{im} &amp;amp;= \frac{\partial L(y_i, f_{m-1}(x_i))}{\partial f_{m-1}(x_i)} \\
    h_{im} &amp;amp;= \frac{\partial^2 L(y_i, f_{m-1}(x_i))}{\partial (f_{m-1}(x_i))^2}
    \end{aligned}\]

&lt;p&gt;It looks pretty nasty, but it is really just a standard Taylor expansion of the form:&lt;/p&gt;

\[f(x+ k) \approx f(x) + kf'(x) + \frac{k^2}{2}f''(x)\]

&lt;p&gt;Following on from this, XGBoost’s &lt;em&gt;learning objective&lt;/em&gt;, or &lt;em&gt;cost
function&lt;/em&gt;, in boosting round $m$ is given by&lt;/p&gt;

\[\begin{gathered}
    C_m = \sum_{i=1}^N \left[L(y_i, f_{m-1}(x_i)) + g_{im} T(x_i; \Theta_m) + \frac 1 2 h_{im}T(x_i; \Theta_j)^2 \right] \\
    +~\Omega(T(x; \Theta_m))
\end{gathered}\]

&lt;p&gt;The ultimate goal is to minimise this cost with respect to $\Theta_m$. The first term is simply the sum over all training examples of the loss approximation. The second term is a &lt;em&gt;complexity penalty&lt;/em&gt; on the $m$th tree. Many different functions could work here, but the following is an intuitive choice which results in an elegant algorithm (we will subsequently drop the $m$ subscripts for clarity):&lt;/p&gt;

\[\Omega(T(x; \Theta)) = \alpha J + \frac 1 2 \lambda \sum_{j=1}^J \gamma_j^2\]

&lt;p&gt;Recall that $J$ is the number of terminal nodes on the tree, each having weight $\gamma_j$. This complexity penalty punishes having more leaves (controlled by $\alpha$), as well as leaves with greater weight (controlled by $\lambda$). If we substitute this back into the cost function, and at the same time remove terms that are constant in $\Theta$, we get:&lt;/p&gt;

\[C = \sum_{i=1}^N \left[g_{i} T(x_i; \Theta) + \frac 1 2 h_{i}T(x_i; \Theta_j)^2 \right] + \alpha J + \frac 1 2 \lambda \sum_{j=1}^J \gamma_j^2\]

&lt;p&gt;At this stage, one must recall the the mathematical defition of a tree:  $T(x; \Theta) = \sum_{j=1}^J \gamma_j I(x \in R_j)$. By substituting this into the above, and regrouping the terms, we can rewrite the cost function:&lt;/p&gt;

\[C = \sum_{j=1}^J \biggl[ \underbrace{\biggl( \sum_{x_i \in R_j} g_i \biggr)}_{G_j} \gamma_j + \frac 1 2 \biggl(\underbrace{\sum_{x_i \in R_j} h_i}_{H_j} + \lambda \biggr) \gamma_j^2 \biggr] + \alpha J\]

&lt;p&gt;Essentially, instead of counting the contributions to the cost over each
training example, we count over each leaf on the tree, summing the
weighted gradient statistics for all training examples in that leaf. New variables $G_j = \sum_{x_i \in R_j} g_i$ and
$H_j = \sum_{x_i \in R_j} h_i$ are defined to simplify the notation.&lt;/p&gt;

&lt;p&gt;Remembering that we are trying to minimise the cost with respect to the
parameters $\Theta = \{R_j, \gamma_j \}_1^J$, we can first find the
optimal weights $\gamma_j$ by noting that the cost function above is a sum of independent quadratics in $\gamma_j$. The
optimal $x$ in a quadratic $ax^2 + bx + c$ is given by $x = -b/2a$. Likewise, the optimal weights are:&lt;/p&gt;

\[\gamma_j^* = -\frac{G_j}{H_j+\lambda}\]

&lt;p&gt;Our final cost function is then:&lt;/p&gt;

\[C = -\frac 1 2 \sum_{j=1}^J \frac{G_j^2}{H_j + \lambda} + \alpha J\]

&lt;p&gt;We can use this cost function to greedily grow trees. To evaluate a potential split into ‘left’ and ‘right’ nodes, we consider the &lt;em&gt;gain&lt;/em&gt;, the amount by which a given split would decrease the cost:&lt;/p&gt;

\[\text{gain} = \frac 1 2 \left[ \frac{G_L^2}{H_L + \lambda} + \frac{G_R^2}{H_R + \lambda} - \frac{(G_L+G_R)^2}{H_L + H_R + \lambda} \right] - \alpha\]

&lt;p&gt;Then, we do a linear scan over the training examples for each feature to decide on the split. What is amazing about XGBoost is really just its cleverness: none of the above mathematics is &lt;em&gt;difficult&lt;/em&gt; – it is mostly algebraic sleight-of-hand. However, these ‘tricks’ do really improve the computational efficiency, which is partially why XGBoost is such a popular learning algorithm.&lt;/p&gt;

&lt;h3 id=&quot;regularistion-in-xgboost&quot;&gt;Regularistion in XGBoost&lt;/h3&gt;

&lt;p&gt;Apart from the inbuilt complexity penalty, XGBoost offers shrinkage and row subsampling, but also &lt;em&gt;column subsampling&lt;/em&gt; – the regularisation method of choice for Random Forests. In column subsampling, each boosting round neglects a certain portion of the features. This increases the overall model’s robustness by reducing the emphasis placed on one or two key variables, giving other variables ‘a chance to speak’, so to speak.&lt;/p&gt;

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

&lt;p&gt;This post turned out to be more detailed than I originally intended. While the logic behind the mathematics is not hard to follow, I concede that the notation is often quite arcane: there are many different indexing values to keep track of. However, it’s very rewarding to be able to understand just how clever XGBoost is, even more so because with it, you can achieve amazing performance on many learning tasks.&lt;/p&gt;

&lt;p&gt;I did a fair bit of research into the theory of gradient tree boosting while writing a paper about using XGBoost to classify potentially outperforming stocks (more on that another time, perhaps). For me, the biggest issue was synthesising a clear and consistent notation from all the conflicting sources (if you think the notation in this post is confusing, try three different sets of notation!). Nevertheless, some resources I found useful are as follows:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;The &lt;em&gt;Elements of Statistical Learning&lt;/em&gt;, which is often the gold-standard reference for machine learning theory. I made notes on the relevant chapters, which I have put up &lt;a href=&quot;https://reasonabledeviations.com/notes/el_stat_learn&quot;&gt;here&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;If ESL is too rigorous for your liking, there is a slightly easier (though obviously less in-depth) version by the same authors, called &lt;em&gt;Introduction to Statistical Learning&lt;/em&gt;. I also have &lt;a href=&quot;https://reasonabledeviations.com/notes/intro_stat_learn&quot;&gt;notes&lt;/a&gt; on the Tree-Based methods chapter.&lt;/li&gt;
  &lt;li&gt;The original XGBoost paper, detailing the theory and implementation. My notes on the paper are here: &lt;a href=&quot;/assets/images/../notes/xgboost.pdf&quot;&gt;XGBoost: A Scalable Tree Boosting System&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;The &lt;a href=&quot;https://xgboost.readthedocs.io/en/latest/model.html&quot;&gt;XGBoost docs&lt;/a&gt; offer a nice high-level overview of the theory, and are definitely a must-read when it comes to implementations and APIs.&lt;/li&gt;
  &lt;li&gt;There is a great &lt;a href=&quot;https://www.youtube.com/watch?v=Vly8xGnNiWs&quot;&gt;youtube video&lt;/a&gt; by Tianqi Chen explaining his algorithm.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I personally can never go back to the ‘standard’ Machine Learning algorithms such as SVM, kNN etc, because in my experience they are just crushed by XGBoost. However, even XGBoost may no longer be the state-of-the-art. Competing software packages such as &lt;a href=&quot;https://github.com/Microsoft/LightGBM&quot;&gt;LightGBM&lt;/a&gt; and &lt;a href=&quot;https://github.com/catboost/catboost&quot;&gt;CatBoost&lt;/a&gt;; after playing with those libraries, even XGBoost seems slow.&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>8-bit Julia set art in python</title>
   <link href="https://reasonabledeviations.com/2017/09/03/python-julia-set/"/>
   <updated>2017-09-03T00:00:00+00:00</updated>
   <id>https://reasonabledeviations.com/2017/09/03/python-julia-set</id>
   <content type="html">&lt;p&gt;You may have heard a mathematician or physicist (or more likely your maths teacher) describe mathematics as &lt;em&gt;beautiful&lt;/em&gt;. What could they mean by this? There is just something mysteriously attractive about the purity, complexity, interconnectedness, and underlying truth of it all (“Beauty is truth, 
truth beauty” - Keats). &lt;!--more--&gt;I can’t really say more than that, so I will leave you with a quote from the ‘man who loved only numbers’:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;It’s like asking why is Ludwig van Beethoven’s Ninth Symphony beautiful. If you don’t see why, someone can’t tell you. I know numbers are beautiful. If they aren’t beautiful, nothing is. - Paul Erdős&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But occasionally, mathematics can be beautiful in the traditional sense of the word.&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/juliaset/juliademo.png&quot; style=&quot;width:400px;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;What is this image, and how did I make it?&lt;/p&gt;

&lt;h2 id=&quot;the-julia-set&quot;&gt;The Julia set&lt;/h2&gt;

&lt;p&gt;The Julia set is a type of fractal, a complex shape with a certain kind of evolving symmetry. What 
is interesting about fractals is that they are often based on very simple rules, which really begs the question as to where the dazzling intricacy comes into the picture.&lt;/p&gt;

&lt;p&gt;While the Julia set comes in many flavours, we will consign ourselves to examine the easiest case, which is more than sufficient to produce pretty images; this is the Julia set defined by:&lt;/p&gt;

\[f_c(z) = z^2 + c\]

&lt;p&gt;where $c$ and $z$ are complex numbers. To get from this equation to an image, we follow a relatively simple procedure&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Choose a complex $c$ as the ‘seed’. This value of $c$ will determine the final picture, so choose wisely.&lt;/li&gt;
  &lt;li&gt;Draw a blank Argand diagram, i.e a set of axes with vertical being imaginary and horizontal being real.&lt;/li&gt;
  &lt;li&gt;Each point on this diagram is a complex number $z$. Repeatedly apply the above formula.&lt;/li&gt;
  &lt;li&gt;For each pixel, count the number of iterations it takes for the absolute value of the resulting complex number to exceed a certain limit. If it hasn’t exceeded this limit after a specific maximum iteration value, we terminate at this maximum.&lt;/li&gt;
  &lt;li&gt;Colour the pixel based on the number of steps it took.&lt;/li&gt;
  &lt;li&gt;Repeat this for all the points on the diagram.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Perhaps I have botched the explanation, but in essence, we count how many iterations of the above 
formula are required to increase $|z|$ beyond a certain bound, and colour accordingly. This is a remarkably simple procedure, and is rather easy to implement in python.&lt;/p&gt;

&lt;h2 id=&quot;python-produces-art&quot;&gt;Python produces art&lt;/h2&gt;

&lt;p&gt;We will start by importing the necessary libraries. We need &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;numpy&lt;/code&gt; for array manipulations, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;matplotlib&lt;/code&gt; to produce our graphs (or art, if you prefer).&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;nn&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;nn&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;nn&quot;&gt;matplotlib.cm&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cm&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In addition to deciding the resolution and the size of the plot, we must
also settle the mathematical parameters. Firstly, we need a suitable $|z|$ to terminate the iterations; secondly, a value for the maximum number of iterations to compute. Quick experiments 
have suggested that values of 10 and 1000 respectively produce decent results.&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;c1&quot;&gt;# Resolution and plot size
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x_res&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y_res&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;200&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;200&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;xmin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;xmax&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;1.5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;1.5&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;width&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;xmax&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;xmin&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;ymin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ymax&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;1.5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;1.5&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;height&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ymax&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ymin&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# The mathematical parameters.
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;z_abs_max&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;max_iter&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1000&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You should definitely play around with some of the above parameters. In general, I left everything untouched except the resolution – I will demonstrate the effects of adjusting this later on.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;x_res&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;y_res&lt;/code&gt; are the number of &lt;em&gt;x&lt;/em&gt; and &lt;em&gt;y&lt;/em&gt; pixels for which we will iterate the complex function. At some stage, we will have to loop over all of these pixels. But let’s first examine what happens for one pixel with coordinates (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ix&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;iy&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;We first map the pixel position to a point on the complex plane. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ix&lt;/code&gt; ranges from 0 to 200, but our real axis ranges from -1.5 to 1.5. We thus have to ‘scale’ the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ix&lt;/code&gt; range onto our Argand diagram.&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;real_part&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ix&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x_res&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;width&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;xmin&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;imaginary_part&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;iy&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y_res&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;height&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ymin&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;z&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;complex&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;real_part&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;imaginary_part&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;This encodes the logic that we want to loop over all values between &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;xmin&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;xmax&lt;/code&gt;, with increments of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;width/x_res&lt;/code&gt;. The same reasoning applies to the imaginary parts and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;iy&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Now that we have our $z$ and a $c$ (assuming the latter has already been chosen), we can proceed 
with the iterations. What I love about python is that the code practically speaks for itself –
continue iterating as long as $|z|$ is smaller than our chosen limit, if we haven’t yet exceeded 
the maximum number of iterations.&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;iteration&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;abs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;z&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;z_abs_max&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;and&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;iteration&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;max_iter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;z&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;z&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;iteration&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;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;So for each (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ix&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;iy&lt;/code&gt;), we will have a certain value of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;iteration&lt;/code&gt; which should determine the colour. It will be easiest to implement the colouring if we first map all of the iterations to the domain [0, 1]. One shortcut for doing this is to just write:&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;iteration_ratio&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;iteration&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;max_iter&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Because &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;iteration&lt;/code&gt; is always greater than zero but less than &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;max_iter&lt;/code&gt;, this value will be between zero and one, and will be proportional to the number of iterations 
it took to escape the $|z|$ circle.&lt;/p&gt;

&lt;h2 id=&quot;all-together-now&quot;&gt;All together now…&lt;/h2&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;nn&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;nn&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;nn&quot;&gt;matplotlib.cm&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cm&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Parameters
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x_res&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y_res&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;300&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;300&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;xmin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;xmax&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;1.5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;1.5&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;width&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;xmax&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;xmin&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;ymin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ymax&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;1.5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;1.5&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;height&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ymax&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ymin&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;z_abs_max&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;max_iter&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1000&lt;/span&gt;


&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;julia_set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# Initialise an empty array (corresponding to pixels)
&lt;/span&gt;    &lt;span class=&quot;n&quot;&gt;julia&lt;/span&gt; &lt;span class=&quot;o&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;zeros&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x_res&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y_res&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# Loop over each pixel
&lt;/span&gt;    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ix&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x_res&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;iy&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;y_res&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;# Map pixel position to a point in the complex plane
&lt;/span&gt;            &lt;span class=&quot;n&quot;&gt;z&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;complex&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ix&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x_res&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;width&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;xmin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                        &lt;span class=&quot;n&quot;&gt;iy&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y_res&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;height&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ymin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;# Iterate
&lt;/span&gt;            &lt;span class=&quot;n&quot;&gt;iteration&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;abs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;z&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;z_abs_max&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;and&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;iteration&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;max_iter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;z&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;z&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;iteration&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;n&quot;&gt;iteration_ratio&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;iteration&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;max_iter&lt;/span&gt;    
            &lt;span class=&quot;c1&quot;&gt;# Set the pixel value to be equal to the iteration_ratio
&lt;/span&gt;            &lt;span class=&quot;n&quot;&gt;julia&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ix&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;iy&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;iteration_ratio&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# Plot the array using matplotlib's imshow
&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;n&quot;&gt;subplots&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;n&quot;&gt;imshow&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;julia&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;interpolation&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'nearest'&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;n&quot;&gt;cm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;gnuplot2&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;n&quot;&gt;axis&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'off'&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;n&quot;&gt;show&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;savefig&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'julia_set.png'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dpi&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;500&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;Running the code for $c = -0.1 - 0.65i$ gives the image at the top of this post.&lt;/p&gt;

&lt;h2 id=&quot;your-turn&quot;&gt;Your turn&lt;/h2&gt;

&lt;p&gt;It is rather fun to play around with the different $c$ values. Here is a quick animation I made, using an adaptation of the above code:&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/juliaset/juliagif.gif&quot; style=&quot;width:400px;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;Or, if this colourscheme isn’t your thing, try playing around with the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cmap&lt;/code&gt; parameter of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;imshow&lt;/code&gt;.
&lt;a href=&quot;https://matplotlib.org/examples/color/colormaps_reference.html&quot;&gt;Here&lt;/a&gt; is a list of the available colourmaps (for each one, you can append ‘_r’ to the cmap name to reverse the spectrum). I’ve shown a few different cmaps below:&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/juliaset/julia_cmaps.png&quot; style=&quot;width:700px;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;It is worth noting that the fractal we produced is actually infinitely complex. If you want to see some more of that complexity, try increasing the resolution. Be warned, though: the computation time grows as the square of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;x_res&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;y_res&lt;/code&gt;, so it might take some time to render. This is a render of the same initial image, except with the resolution set to 2000.&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/juliaset/julia_highres.png&quot; style=&quot;width:500px;&quot; /&gt;
&lt;/center&gt;

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

&lt;p&gt;Clearly, this post doesn’t present the most efficient computation of a Julia set. For speed, we would use array manipulation rather than iterating over each pixel. However, the code is very easy to understand and we can still get some very pretty results.&lt;/p&gt;

&lt;p&gt;There is a lot of room for personal preference: I have demonstrated the effects of choosing different &lt;em&gt;c&lt;/em&gt; values, colourmaps, resolutions. However, you can even do stuff like ‘zoom’ onto a certain area of the fractal by changing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;xmin&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;xmax&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ymin&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ymax&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;But my personal favourite is the image I showed you at the start of this post. That lovely rich purple combined with the 8-bit pixelation gives the image an other-worldly charm.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;em&gt;Play around with the code on &lt;a href=&quot;https://github.com/robertmartin8/RandomWalks/blob/master/julia_set.py&quot;&gt;GitHub&lt;/a&gt; to experiment some more. The code therein is very similar to what I’ve shown in this post, except I’ve also included the code to generate the animations and test different colourmaps&lt;/em&gt;.&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>Retrieving historical stock prices from Yahoo Finance with no API</title>
   <link href="https://reasonabledeviations.com/2017/07/30/yahoo-historical-prices/"/>
   <updated>2017-07-30T00:00:00+00:00</updated>
   <id>https://reasonabledeviations.com/2017/07/30/yahoo-historical-prices</id>
   <content type="html">&lt;p&gt;Yahoo Finance has long been an excellent free financial resource with a wealth of data and a convenient API, allowing open source programming libraries to access stock data. But not any more. As of May 2017, they have &lt;a href=&quot;https://forums.yahoo.net/t5/Yahoo-Finance-help/Is-Yahoo-Finance-API-broken/td-p/250503&quot;&gt;discontinued their API&lt;/a&gt;, probably as a result of Yahoo’s pending acquisition by Verizon. This means that excellent tools like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pandas-datareader&lt;/code&gt; are now broken, much to the dismay of many amateur algorithmic traders or analysts. It turns out that there is a rather hackish workaround which allows us to download the data as CSV (i.e spreadsheet) files, which of course can then be read into excel, pandas dataframes etc.
&lt;!--more--&gt;&lt;/p&gt;

&lt;p&gt;Just a legal disclaimer. This method involves making a large number of requests on Yahoo Finance, which may ‘look like’ a DDOS. Clearly, I am not trying to conduct a DDOS – I am merely trying to parse data for educational purposes. I am not responsible for how you use the methods demonstrated in this post.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;Update as of 10/2/18&lt;/strong&gt;: there is now a &lt;a href=&quot;https://github.com/ranaroussi/fix-yahoo-finance&quot;&gt;library on GitHub&lt;/a&gt; that puts this post to shame, with a direct pandas-datareader interface. I will leave this post up for legacy, but for any serious implementations, I wholeheartedly recommend the aforementioned tool.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;Update as of 20/5/18&lt;/strong&gt;: it seems that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fix-yahoo-finance&lt;/code&gt; is becoming very inconsistent. So I guess this post does have some utility after all!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;Update as of 9/1/20&lt;/strong&gt;: looking back on this post, I don’t think my solution is very good. A much better way would be to use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;requests&lt;/code&gt; directly and parse the csv bytes, instead of physically downloading files moving them.&lt;/em&gt;&lt;/p&gt;

&lt;h2 id=&quot;overview&quot;&gt;Overview&lt;/h2&gt;

&lt;p&gt;Although the API doesn’t work, you can still manually download CSV files containing historical price data for a given ticker. To do so, you go to &lt;a href=&quot;https://finance.yahoo.com/&quot;&gt;finance.yahoo.com&lt;/a&gt;, and enter a ticker in the search. I entered ‘AAPL’.&lt;/p&gt;

&lt;p&gt;After navigating to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Historical Data&lt;/code&gt; tab, you will end up with a screen like this:&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/yfinance_screenshot.png&quot; style=&quot;width:800px;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;After editing the time period and applying it, you can click the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Download Data&lt;/code&gt; button (which I emphasised in red above), and a CSV file will promptly 
start downloading on your browser. But to manually do this for 3000 stocks, as I would have to do for my MachineLearningStocks project, would be tantamount to water torture. I thus needed a good way 
of automating this process, and luckily I found one.&lt;/p&gt;

&lt;h2 id=&quot;the-method&quot;&gt;The method&lt;/h2&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Download Data&lt;/code&gt; button is actually a hyperlink, so I decided to have a quick check as to the url of this link. If you right click a link in chrome, there is an option to ‘Copy Link Address’. Doing so for AAPL with a time period of Jan 01 2001 – Aug 06 2017, the link is:&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;https://query1.finance.yahoo.com/v7/finance/download/AAPL?
period1=946656000&amp;amp;period2=1501948800
&amp;amp;interval=1d&amp;amp;events=history&amp;amp;crumb=BkT/GAawAXc
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Actually, the structure of this url is all that one needs to crack the problem of automation. I don’t know what time system yahoo finance uses internally, but it’s a pretty fair guess that 946656000 corresponds to Jan 01 2001, and likewise that 1501948800 corresponds to Aug 06 2017.&lt;/p&gt;

&lt;p&gt;I tried just changing ‘AAPL’ to ‘GOOG’ in the above url and entering it onto chrome, and suddenly &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GOOG.csv&lt;/code&gt; had downloaded onto my machine. I did some digging as to what the ‘crumb’ is, and to my knowledge it is some sort of API token / cookie which will most likely be different for you.&lt;/p&gt;

&lt;p&gt;In any case, I figured that I had basically solved the issue at this point, so the rest was a matter of coding it up in python.&lt;/p&gt;

&lt;h2 id=&quot;implementing-the-solution-in-python&quot;&gt;Implementing the solution in python&lt;/h2&gt;

&lt;p&gt;To interact with my browser (Google Chrome), I used the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;webbrowser&lt;/code&gt; library in python. It is extremely intuitive to use: opening a URl is as simple as&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;webbrowser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'https://www.google.com'&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;I wanted to download a list of tickers that I had saved in a text file called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;russell3000.txt&lt;/code&gt;. To read this into a python list, we can use an elegant list comprehension:&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;russell_tickers&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;line&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;split&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'&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;n&quot;&gt;lower&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;line&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'russell3000.txt'&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;For each ticker, we will want to open the URL as specified earlier. The time period will be the same in all of them – Jan 01 2001 to Aug 06 2017. The code for this is 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;nn&quot;&gt;webbrowser&lt;/span&gt; 

&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ticker&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tickers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;link&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;s&quot;&gt;&quot;https://query1.finance.yahoo.com/v7/finance/download&quot;&lt;/span&gt;
            &lt;span class=&quot;s&quot;&gt;&quot;/{}?period1=946656000&amp;amp;period2=1501948800&amp;amp;interval=1d&quot;&lt;/span&gt;
            &lt;span class=&quot;s&quot;&gt;&quot;&amp;amp;events=history&amp;amp;crumb=BkT/GAawAXc&quot;&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;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;webbrowser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;link&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;And that’s it. This will download all of the CSV files straight to your downloads folder.&lt;/p&gt;

&lt;p&gt;Or will it? I have found the hard way that web-related code often runs into errors regarding the web queries – often, pages that you expect to exist have been taken down or corrupted etc. When webbrowser tries to open this URL, it will encounter an error, but instead of continuing with the rest of the tickers, it’ll just give up. The way around this is to use python’s exception handling:&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;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ticker&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tickers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;link&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;s&quot;&gt;&quot;https://query1.finance.yahoo.com/v7/finance/&quot;&lt;/span&gt;
                &lt;span class=&quot;s&quot;&gt;&quot;download/{}?period1=946656000&amp;amp;&quot;&lt;/span&gt;
                &lt;span class=&quot;s&quot;&gt;&quot;period2=1501948800&amp;amp;interval=1d&amp;amp;events=history&quot;&lt;/span&gt;
                &lt;span class=&quot;s&quot;&gt;&quot;&amp;amp;crumb=BkT/GAawAXc&quot;&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;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;webbrowser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;link&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;except&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Exception&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;print&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;e&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;This way, if there is ever an error, the code will just print the ticker and the error, then carry on. In principle, we are finished. However, it is a bit annoying that all of these CSVs are in the downloads folder, rather than the directory for my project. I wrote a few lines of python to fix 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;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;os&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Change these as appropriate
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;project_path&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;/Users/Robert/Project/&quot;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;download_path&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'/Users/Robert/Downloads/'&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Look at the contents of your download folder
&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;item&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&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;listdir&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;download_path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;# If the file is a csv, move it to the project folder
&lt;/span&gt;        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;item&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;4&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;s&quot;&gt;'.csv'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;download_path&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;project_path&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;p&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;rename&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;download_path&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;project_path&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;item&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;For AAPL.csv, the code would print:&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;/Users/Robert/Downloads/AAPL.csv
/Users/Robert/Project/AAPL.csv
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;and the file would be moved from the former location to the latter. Note that it will also move any other CSVs, but I didn’t need to fix this as I don’t normally have CSVs in my downloads.&lt;/p&gt;

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

&lt;p&gt;It is always a nice feeling when you can use some python to save you hours of manual labour, and this was certainly the case here. Using this code, one can automate the downloading of a large number of datafiles which contain historical stock prices. This was a very important aspect of my MachineLearningStocks project, as it is meaningless to try to learn from data without having the data.&lt;/p&gt;

&lt;p&gt;As is often the case, the code I’ve showed here just scratches the surface. You will find that some of the CSVs didn’t actually download, or they are empty. Thus, to increase the quality of your training data, some troubleshooting is in order. Some of the improvements I’ve made:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;troubleshoot non-alphabet characters in the ticker names&lt;/li&gt;
  &lt;li&gt;use &lt;a href=&quot;https://www.quandl.com&quot;&gt;Quandl&lt;/a&gt;, and its python API, to download data that Yahoo Finance doesn’t have&lt;/li&gt;
  &lt;li&gt;use &lt;a href=&quot;https://pandas-datareader.readthedocs.io&quot;&gt;pandas-datareader&lt;/a&gt; to download data from Google Finance, if both Yahoo and Quandl don’t have the data&lt;/li&gt;
&lt;/ul&gt;

</content>
 </entry>
 
 <entry>
   <title>The Leibniz integral rule in electrodynamics</title>
   <link href="https://reasonabledeviations.com/2017/07/25/leibniz-electrodynamics/"/>
   <updated>2017-07-25T00:00:00+00:00</updated>
   <id>https://reasonabledeviations.com/2017/07/25/leibniz-electrodynamics</id>
   <content type="html">&lt;p&gt;I have been slowly working through David Griffith’s widely-used textbook, &lt;em&gt;Introduction to Electrodynamics&lt;/em&gt;. It is known for having a large number of reasonably difficult exercises, which are instrumental in conveying some concepts not directly addressed in the main text. I found an elegant shortcut to one of the questions, which was not noted in the solution manual. The shortcut involves (ab)using a very versatile, if slightly overlooked theorem in calculus – the &lt;strong&gt;Leibniz integral rule&lt;/strong&gt;. I will briefly discuss this, before working through the problem. &lt;!--more--&gt;&lt;/p&gt;

&lt;h2 id=&quot;the-leibniz-integral-rule&quot;&gt;The Leibniz integral rule&lt;/h2&gt;

&lt;p&gt;The Leibniz rule concerns derivatives of integrals; the most basic form is as follows:&lt;/p&gt;

\[\frac{d}{dx} \left( \int_a^b f(x,t) dt  \right) = \int_a^b \frac{\partial}{\partial x} f(x,t) dt\]

&lt;p&gt;This can be proven by letting $I(x) = \int_a^b f(x,t) dt$, then using the definition of the derivative to evaluate $I’(x)$, which will be left as an exercise for the reader.  However I hope you can get an intuitive feel for this: the integral measures the total variation with respect to $t$, whereas the derivative measures the rate of change with respect to $x$, so it makes sense that the order of operation doesn’t really matter.&lt;/p&gt;

&lt;p&gt;A slightly more general version concerns the case where the limits of the integral are variable:&lt;/p&gt;

\[\frac{d}{dx} \left( \int_{a(x)}^{b(x)} f(x,t) dt  \right) =  f\left(x, b(x)\right)\frac{d b(x)}{dx} - f(x, a(x))\frac{d a(x)}{dx} + \int_{a(x)}^{b(x)}\frac{\partial}{\partial x} f(x,t) dt\]

&lt;p&gt;Effectively, it is the same as what we had before, plus some additions to take into account of the variable limits. This formula can be explained in terms of the fundamental theorem of calculus, but the derivation is besides the point. For now, I would urge you to just accept the above rule (it is surprisingly useful). Let’s go through an example to evaluate the following expression:&lt;/p&gt;

\[\frac{d}{dx} \int_{\sin x}^{\tan x} \sqrt{1+ t^3}dt\]

&lt;p&gt;Hint: do &lt;em&gt;not&lt;/em&gt; do it by evaluating the indefinite integral, substituting in the limits, then differentiating. Applying the Leibniz rule, we see that:&lt;/p&gt;

\[\begin{aligned}
\frac{d}{dx} \int_{\sin x}^{\tan x} \sqrt{1+ t^3}dt &amp;amp;= \sqrt{1+ \tan^3 x}\frac{d}{dx}\tan x - \sqrt{1 + \sin^3 x} \frac{d}{dx} \sin x\\
&amp;amp;= \sec^2x\sqrt{1+ \tan^3 x} - \cos x \sqrt{1 + \sin^3 x}  
\end{aligned}\]

&lt;h2 id=&quot;the-electrodynamics-problem&quot;&gt;The electrodynamics problem&lt;/h2&gt;

&lt;p&gt;I say ‘electrodynamics’, but really it is an electrostatics problem. Below I have reproduced the question from Chapter 2 (Electrostatics) of Griffith’s &lt;em&gt;Introduction to Electrodynamics&lt;/em&gt;. It has been edited slightly.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;Problem 27&lt;/strong&gt; Find the electric field on the axis of a uniformly charged solid cylinder, at a distance &lt;em&gt;z&lt;/em&gt; from the centre. The length of the cylinder is &lt;em&gt;L&lt;/em&gt;, its radius is &lt;em&gt;R&lt;/em&gt;, and the charge density is $\rho$.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Our general strategy will be to first find the potential (or rather, an &lt;em&gt;expression&lt;/em&gt; for the potential), by decomposing the cylinder into simpler shapes. Then we will find the field using $\mathbf{E} = - \nabla V$.&lt;/p&gt;

&lt;p&gt;A cylinder can be thought of as many discs; a disc can be thought of as many rings; a ring can be thought of as many point charges. We will thus build a cylinder.&lt;/p&gt;

&lt;h3 id=&quot;1-a-single-point-charge&quot;&gt;1. A single point charge&lt;/h3&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/edyn/point_charge.png&quot; style=&quot;width:300px; margin:10px 5px;&quot; class=&quot;right&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Recall the formula for the potential at a point $P$:&lt;/p&gt;

\[V = \frac{1}{4\pi \epsilon_0} \frac{q}{r}\]

&lt;p&gt;In this formula, $r$ is the displacement between the charge and the test point. Applying Pythagoras’ theorem to the diagram, we see that $r^2 = R^2 + z^2$.&lt;/p&gt;

&lt;p&gt;Plugging values into the formula:&lt;/p&gt;

\[V = \frac{1}{4\pi \epsilon_0} \frac{q}{\sqrt{R^2 + z^2}}\]

&lt;h3 id=&quot;2-a-ring-of-charge&quot;&gt;2. A ring of charge&lt;/h3&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/edyn/ring_charge.png&quot; style=&quot;width:300px; margin:30px 20px;&quot; class=&quot;right&quot; /&gt;&lt;/p&gt;

&lt;p&gt;In order to find the potential for a ring of charge given the potential of a point charge, we consider a charge element $\lambda dl$. But the length of a segment on a circle is given by $R \theta$, so we can say that $dl = R d\theta$.&lt;/p&gt;

&lt;p&gt;Instead of a positive point charge $q$, we now have a charge element $\lambda R d\theta$. Using our previous result, the potential at &lt;em&gt;P&lt;/em&gt; from one of these charge elements is:&lt;/p&gt;

\[dV = \frac{1}{4\pi \epsilon_0} \frac{\lambda R d\theta}{\sqrt{R^2 + z^2}}\]

&lt;p&gt;The total potential for the ring is the sum of the contributions from each of the charge elements.&lt;/p&gt;

\[V = \int_0^{2\pi} \frac{1}{4\pi \epsilon_0} \frac{\lambda R d\theta}{\sqrt{R^2 + z^2}} = \frac{1}{4\pi \epsilon_0} \frac{\lambda R }{\sqrt{R^2 + z^2}} \int_0^{2\pi} d\theta\]

\[\therefore V = \frac{1}{4\pi \epsilon_0} \frac{2 \pi R \lambda}{\sqrt{R^2 + z^2}}\]

&lt;h3 id=&quot;3-a-disc-of-charge&quot;&gt;3. A disc of charge&lt;/h3&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/edyn/disc_charge.png&quot; style=&quot;width:300px;margin:40px 20px;&quot; class=&quot;left&quot; /&gt;&lt;/p&gt;

&lt;p&gt;We can build up a &lt;em&gt;disc&lt;/em&gt; of charge with radius R from an infinite number of &lt;em&gt;rings&lt;/em&gt; of charge of radius &lt;em&gt;r&lt;/em&gt;. Note that this &lt;em&gt;r&lt;/em&gt; is different to the one briefly mentioned earlier, which was the separation between &lt;em&gt;P&lt;/em&gt; and the charge.&lt;/p&gt;

&lt;p&gt;Because we are going from 1D to 2D, we must let $\lambda = \sigma dr$, where $\sigma$ is the surface charge. Thus the potential from one ring is:&lt;/p&gt;

\[dV = \frac{1}{4\pi \epsilon_0} \frac{2 \pi r \sigma dr}{\sqrt{r^2 + z^2}}\]

&lt;p&gt;We could arrive at this otherwise, by noting that the total charge in the ring is&lt;/p&gt;

\[\sigma \cdot \text{area} = \sigma \cdot 2\pi r dr\]

&lt;p&gt;Anyway, let us continue.&lt;/p&gt;

\[V = \frac{\sigma}{2 \epsilon_0}  \int_0^R \frac{r}{\sqrt{r^2 + z^2}} dr\]

&lt;p&gt;As is rarely the case in electrodynamics, this integral is not at all difficult to evaluate. We use the ‘reverse chain rule’ to see that:&lt;/p&gt;

\[\int_0^R \frac{r}{\sqrt{r^2 + z^2}} dr = \int_0^R r \left( r^2 + z^2\right)^{-1/2} dr =\left[ (r^2+z^2)^{1/2} \right]_0^R\]

&lt;p&gt;The total potential is then easily found.&lt;/p&gt;

\[V = \frac{\sigma}{2\epsilon_0} \left(\sqrt{R^2+z^2} - z \right)\]

&lt;h3 id=&quot;4-a-cylinder-of-charge&quot;&gt;4. A cylinder of charge&lt;/h3&gt;

&lt;p&gt;We are now ready to tackle the original problem. Here it is again:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Find the electric field on the axis of a uniformly charged solid cylinder, at a distance &lt;em&gt;z&lt;/em&gt; from the centre. The length of the cylinder is &lt;em&gt;L&lt;/em&gt;, its radius is &lt;em&gt;R&lt;/em&gt;, and the charge density is $\rho$.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I’ll set up the solution in the same way Griffiths does – this image is taken from the solution manual.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/edyn/cylinder_charge.png&quot; style=&quot;width:300px;margin:20px 19px;&quot; class=&quot;right&quot; /&gt;&lt;/p&gt;

&lt;p&gt;A cylinder is made up of an infinite number of discs. We want an expression for the potential at the test point, a distance &lt;em&gt;z&lt;/em&gt; from the centre. We will let &lt;em&gt;x&lt;/em&gt; denote the distance between the test point and a given disc. The potential from one such disc is given by the formula we found earlier, with the substitution $z \rightarrow x$ and $\sigma \rightarrow \rho dx$:&lt;/p&gt;

\[dV = \frac{\rho dx}{2 \epsilon_0} \left( \sqrt{R^2 + x^2}  - x \right)\]

&lt;p&gt;Since &lt;em&gt;L&lt;/em&gt; is the length of the cylinder, and &lt;em&gt;z&lt;/em&gt; is the distance from the centre of the cylinder, it is clear that there are discs all the way from $x = z - \frac L 2$ to $x = z+ \frac L 2$. The total potential at our test point is just the integral over all of these discs:&lt;/p&gt;

\[V = \frac{\rho}{2\epsilon_0} \int_{z-L/2}^{z+L/2}(\sqrt{R^2 + x^2} - x) dx\]

&lt;p&gt;Despite the simple-looking expression, this integral will soon become a nightmare to evaluate. And don’t forget – we are trying to find the &lt;em&gt;electric field&lt;/em&gt;, so we will then have to take the gradient of the whole mess.&lt;/p&gt;

&lt;p&gt;To be clear, because of the cylinder’s symmetry, ‘taking the gradient’ here just means differentiating with respect to &lt;em&gt;z&lt;/em&gt;,
but nevertheless this will not be a fun calculation.&lt;/p&gt;

\[\mathbf{E} = - \nabla V = - \mathbf{\hat{z}} \frac{\partial V}{\partial z}\]

&lt;h2 id=&quot;leibniz-to-the-rescue&quot;&gt;Leibniz to the rescue&lt;/h2&gt;

&lt;p&gt;Instead of immediately evaluating the integral to calculate the potential, then differentiating, why don’t 
we write it in one expression first:&lt;/p&gt;

\[\mathbf{E} = - \mathbf{\hat{z}}  \frac{\rho}{2\epsilon_0} \frac{\partial}{\partial z} \int_{z-L/2}^{z+L/2}\left( \sqrt{R^2 + x^2} - x \right) dx\]

&lt;p&gt;This might &lt;em&gt;look&lt;/em&gt; more complicated, but in fact it makes life much easier. Because we are taking the 
derivative of an integral, we can simply apply the Leibniz integral rule. The limits are not constant, but the integrand is independent of &lt;em&gt;z&lt;/em&gt;, so the rule tells us that:&lt;/p&gt;

\[\begin{align*}
    \mathbf{E} = - \mathbf{\hat{z}}  \frac{\rho}{2\epsilon_0}  &amp;amp;\bigg[ \left( \sqrt{R^2 + 
    (z+L/2)^2} - (z+L/2) \right) \frac{\partial}{\partial z}(z+L/2)\\
    &amp;amp; - \left( \sqrt{R^2 + (z-L/2)^2} - (z-L/2) \right) \frac{\partial}{\partial z}(z-L/2) \bigg]
\end{align*}\]

&lt;p&gt;Intuitively, there is some kind of ‘cancellation’ between the differentiation and integration, so we simply 
substitute the limits into the integrand (and multiply by the derivative of the limit since it is not constant), then subtract. In this case it is even simpler because the derivatives of the limits are unity:&lt;/p&gt;

\[\mathbf{E} = \frac{\rho}{2\epsilon_0} \left[L + \sqrt{R^2 + (z-L/2)^2} - \sqrt{R^2 + 
(z+L/2)^2} \right] \mathbf{\hat{z}}\]

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

&lt;p&gt;In many electrodynamics problems, one finds the electric field by first calculating the potential, 
then taking the gradient. I hope I’ve given you a reasonable motivation to &lt;em&gt;not&lt;/em&gt; just do this mechanically –
keep an eye out for shortcuts that will save you time, and reduce the chances of you making a minor
algebraic error.&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>Intuiting the gamma function, part 3</title>
   <link href="https://reasonabledeviations.com/2017/07/21/gamma-intuition-3/"/>
   <updated>2017-07-21T00:00:00+00:00</updated>
   <id>https://reasonabledeviations.com/2017/07/21/gamma-intuition-3</id>
   <content type="html">&lt;p&gt;We ended &lt;a href=&quot;/2017/07/16/gamma-intuition-2/&quot;&gt;Part 2&lt;/a&gt; with the stunning result that&lt;/p&gt;

\[x! = \int_0^\infty t^x e^{-t}dt\]

&lt;p&gt;Of course, this is not exactly the same as the gamma function, because the gamma function is defined as:&lt;/p&gt;

\[\Gamma (x) = \int_0^\infty t^{x-1}e^{-t} dt\]

&lt;p&gt;A direct observation of the above leads to the conclusion that:&lt;/p&gt;

\[\Gamma (x) = (x-1)! \qquad \text{or alternatively} \qquad x! = \Gamma (x+1)\]

&lt;p&gt;This ‘shift’ is a minor annoyance and is largely a historical artifact, but don’t let it distract you from the fact that &lt;strong&gt;the gamma function is a superset of the factorial&lt;/strong&gt;. That is quite a bold statement (no pun intended), so it is worth proving. To show it, we must simply demonstrate that the gamma function can satisfy the definition of the factorial which we discussed in &lt;a href=&quot;/2017/07/11/gamma-function-intuition/&quot;&gt;Part 1&lt;/a&gt;, reproduced here:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;$1! = 1$&lt;/li&gt;
  &lt;li&gt;$n! = n(n-1)!$&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In terms of the gamma function, we thus need to show that&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;$\Gamma (2) = 1$, the &lt;strong&gt;boundary condition&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;$\Gamma (x+1) = x \Gamma (x)$, the &lt;strong&gt;recursion condition&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;the-boundary-condition&quot;&gt;The boundary condition&lt;/h2&gt;

&lt;p&gt;To evaluate $\Gamma(2)$, we can start by substituting $x=2$ into the definition of the gamma function:&lt;/p&gt;

\[\Gamma(2) = \int_0^\infty te^{-t}\]

&lt;p&gt;We can use standard integration by parts (IBP) to evaluate this:&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/gamma/ttable.png&quot; style=&quot;width:150px;&quot; /&gt;
&lt;/center&gt;

\[\begin{align*}

    \therefore \int_0^\infty t e^{-t} dt &amp;amp;= \left[ -te^{-t} - e^{-t} \right]_0^\infty \\
    &amp;amp;= \left[ \lim_{t \rightarrow \infty} \left( -\frac{t}{e^t}\right) \right] + 1
 \end{align*}\]

&lt;p&gt;I can tell you very quickly that this limit is equal to zero – look at what happens to the expression within the limit as $t$ grows. The $e^t$ on the denominator clearly grows much faster than the $t$ on the numerator, so as $t \rightarrow \infty$, this expression will tend to zero. In fact this is the intuition behind L’Hopital’s rule, which we could apply by differentiating the top and the bottom of the fraction.&lt;/p&gt;

&lt;p&gt;In any case, we can conclude that&lt;/p&gt;

\[\Gamma(2) = \int_0^\infty t e^{-t} dt =  \left[ \lim_{t \rightarrow \infty} \left( -\frac{t}{e^t}\right) \right] + 1 = 1 \qquad QED\]

&lt;h2 id=&quot;the-recursion-condition&quot;&gt;The recursion condition&lt;/h2&gt;

&lt;p&gt;As a quick reminder, we need to show that:&lt;/p&gt;

\[\Gamma(x+1) = \int_0^\infty t^x e^{-t}dt\]

&lt;p&gt;This proof is really rather elegant, so I hope you’ll be as impressed with it as I was when I first understood it.&lt;/p&gt;

&lt;p&gt;We will start by evaluating the left hand side (LHS) using IBP, though we will do it without our table method. Choosing an $f$ and a $g’$ then substituting them into the IBP formula:&lt;/p&gt;

\[\begin{aligned}
    f = t^x \implies f' = xt^{x-1}\\
    g' = e^{-t} \implies g = -e^{-t}
\end{aligned}\]

\[\therefore \int_0^\infty t^x e^{-t} dt = \left[ -t^xe^{-t} + x\int e^{-t} t^{x-1} dt\right]_0^\infty\]

&lt;p&gt;To evaluate the RHS, we can split up the expression (noting that &lt;em&gt;x&lt;/em&gt; can be treated as a constant)&lt;/p&gt;

\[\begin{aligned}
    \left[ -t^xe^{-t} + x\int e^{-t} t^{x-1} dt\right]_0^\infty &amp;amp;= \left[ -t^xe^{-t}\right]_0^\infty + x \left[\int e^{-t} t^{x-1} dt\right]_0^\infty\\
    &amp;amp;= \left[ -t^xe^{-t}\right]_0^\infty + x\int_0^\infty e^{-t} t^{x-1} dt\\
\end{aligned}\]

&lt;p&gt;Let us evaluate the first term (in square brackets):&lt;/p&gt;

\[\left[ -t^xe^{-t}\right]_0^\infty = \lim_{t \rightarrow \infty} \left( -\frac{t^x}{e^t} \right)\]

&lt;p&gt;I could just apply the same intuition as before to find this limit, but in this case it’s not immediately obvious that $e^t$ grows faster than $t^x$. To show this, we just repeated differentiate the top and bottom as per L’Hopital’s rule, until we can ‘substitute’ $t = \infty$ with sensible results (please don’t show that statement to any mathematicians):&lt;/p&gt;

\[\lim_{t \rightarrow \infty} \left( -\frac{t^x}{e^t} \right) = \lim_{t \rightarrow \infty} \left( -\frac{xt^{x-1}}{e^t} \right) =  \lim_{t \rightarrow \infty} \left( -\frac{x(x-1)t^{x-2}}{e^t} \right) = \ldots = \lim_{t \rightarrow \infty} \left( -\frac{x!}{e^t} \right) = 0\]

&lt;p&gt;To summarise:&lt;/p&gt;

\[\begin{aligned}
    \Gamma(x+1) &amp;amp;= \int_0^\infty t^x e^{-t} dt\\
    &amp;amp;= \left[ -t^xe^{-t}\right]_0^\infty + x\int_0^\infty e^{-t} t^{x-1} dt\\
    &amp;amp;= 0 + x\Gamma(x)\\
    &amp;amp;=x\Gamma(x) \qquad \qquad  QED
\end{aligned}\]

&lt;p&gt;We have successfully shown that the gamma function satisfies both the boundary condition and the recursion condition of the factorial function. This means that it will &lt;em&gt;always&lt;/em&gt; give the same result as the traditional factorial (ignoring the shift). For example, we know for sure that $1729! = \Gamma(1730)$ without having to calculate either quantity.&lt;/p&gt;

&lt;h2 id=&quot;extending-the-factorial&quot;&gt;Extending the factorial&lt;/h2&gt;

&lt;p&gt;It is a fact that $\Gamma(x)$ is defined for all complex numbers except the negative integers (and zero), but unfortunately, its values are rarely ‘nice’ and can often only be evaluated with a computer. However, I’d like to show you one example that &lt;em&gt;does&lt;/em&gt; turn out nicely: $\Gamma(\frac 1 2)$.&lt;/p&gt;

&lt;p&gt;We start by substituting 1/2 into the definition of the gamma function:&lt;/p&gt;

\[\Gamma (\frac 1 2) = \int_0^\infty t^{-1/2}e^{-t}dt\]

&lt;p&gt;To evaluate the RHS, we make a substitution:&lt;/p&gt;

\[u = t^{1/2} \implies \frac{du}{dt} = \frac{1}{2\sqrt{t}} = \frac{1}{2u}\]

\[\begin{aligned}
    \therefore \int_0^\infty t^{-1/2}e^{-t}dt &amp;amp;= \int_0^\infty \frac 1 u e^{-u^2} \cdot 2u~du\\
    &amp;amp;= 2\int_0^\infty e^{-u^2}du
\end{aligned}\]

&lt;p&gt;You may recognise this very special integral, which is called the &lt;em&gt;Gaussian integral&lt;/em&gt;. Although the indefinite integral has no elementary antiderivative, this &lt;em&gt;definite&lt;/em&gt; integral has a beautifully simple result (the derivation of which I won’t show):&lt;/p&gt;

\[\Gamma(\frac 1 2) = 2\int_0^\infty e^{-u^2} du = \sqrt \pi\]

&lt;p&gt;I hope it’s clear just how unexpected and &lt;em&gt;weird&lt;/em&gt; this result is. We are saying that $\left( -\frac 1 2 \right)! = \sqrt \pi$, which is when you should realise that this whole series of posts has been a practical joke…&lt;/p&gt;

&lt;p&gt;Not really.&lt;/p&gt;

&lt;p&gt;Anyway, note how we can use this value to find $\frac 1 2 !$ We see that $\frac 1 2 ! = \Gamma(\frac 3 2)$, and we can easily write this in terms of $\Gamma(\frac 1 2)$ using the recursion condition.&lt;/p&gt;

\[\Gamma(\frac 3 2) = \frac 1 2 \Gamma(\frac 1 2) = \frac{\sqrt{\pi}}{2}\]

&lt;p&gt;This is a result with which I like to surprise people at parties, because it’s slightly more believable than the result of $(- \frac 1 2)!$. Maybe this is why nobody invites me to parties.&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;We have reached the end of the series on understanding the intuition behind the gamma function. I hope you now see that the weird integral really does ‘contain’ the traditional factorial, which you can verify at any time by doing repeated integration by parts.&lt;/p&gt;

&lt;p&gt;Unfortunately, despite my efforts, I can offer no intuition regarding the freaky results like $(1/2)! = \sqrt{\pi}/2$. But fortunately, you don’t have to take these results on faith – you can prove them to be true (as we have done).&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Intuiting the gamma function, part 2</title>
   <link href="https://reasonabledeviations.com/2017/07/16/gamma-intuition-2/"/>
   <updated>2017-07-16T00:00:00+00:00</updated>
   <id>https://reasonabledeviations.com/2017/07/16/gamma-intuition-2</id>
   <content type="html">&lt;p&gt;In &lt;a href=&quot;/2017/07/11/gamma-function-intuition/&quot;&gt;Part 1&lt;/a&gt;, we showed that repeated differentiation gives rise to a factorial. In this second post of &lt;em&gt;Intuiting the gamma function&lt;/em&gt;, we are going to show that integration by parts can also produce a factorial – an instrumental step in generalising the idea of a factorial to non-integers.&lt;!--more--&gt;&lt;/p&gt;

&lt;p&gt;Just a couple of minor points regarding the presentation of this post. I will abbreviate ‘integration by parts’ to IBP, since we will be mentioning it rather often. I will also consistently exclude the constant of integration, because it is just clutter.&lt;/p&gt;

&lt;h2 id=&quot;a-quick-ibp-refresher&quot;&gt;A quick IBP refresher&lt;/h2&gt;

&lt;p&gt;If we are integrating a function $f(t)g’(t)$, the IBP formula tells us:&lt;/p&gt;

\[\int fg'dt = fg - \int f'g~dt\]

&lt;p&gt;Notice some things:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;There is both differentiation and integration involved. $f(t)$ is differentiated, while $g’(t)$ is integrated.&lt;/li&gt;
  &lt;li&gt;There is a minus sign, which will later be very important.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As we saw in the previous post, in order to end up with a factorial, we will have to repeatedly differentiate something like $x^n$. As such, I am going to let $f(t) = t^n$. This is because $f(t)$ is always differentiated in the IBP formula.&lt;/p&gt;

&lt;p&gt;We also need to choose a $g(t)$, which will be always be integrated. I will choose the exponential function because it stays the same and makes everything easier. Maybe you think that I’m just choosing it because I know for a fact that the gamma function includes an exponential, but even if you think so, I urge you to read on as this doesn’t affect the intuition behind the gamma function – it just makes this post an &lt;em&gt;explanation&lt;/em&gt; instead of a &lt;em&gt;derivation&lt;/em&gt;.&lt;/p&gt;

&lt;h2 id=&quot;a-shortcut-for-repeated-ibp&quot;&gt;A shortcut for repeated IBP&lt;/h2&gt;

&lt;p&gt;It may not be clear what I am trying to say at first, but it will make the eventual moment of understanding that much better. Follow along with the steps for now, and hopefully I will be able to make it all come together.&lt;/p&gt;

&lt;p&gt;Consider the integral $\int t e^t dt$. We set up the parts required:&lt;/p&gt;

\[\begin{aligned}
    f = t \implies f' = 1\\
    g' = e^t \implies g = e^t
\end{aligned}\]

&lt;p&gt;Now we apply the IBP formula:&lt;/p&gt;

\[\begin{aligned}
    \therefore \int t e^t dt &amp;amp;= te^t - \int e^t dt\\
    &amp;amp;= te^t - e^t
\end{aligned}\]

&lt;p&gt;What about $\int t^2 e^t dt~$? We can still use IBP, though we have to do it twice. The first time:&lt;/p&gt;

\[\begin{aligned}
    f_1 = t^2 \implies f_1' = 2t\\
    g_1' = e^t \implies g_1 = e^t\\
    \therefore \int t^2 e^t dt = t^2e^t - \int 2te^t dt.
\end{aligned}\]

&lt;p&gt;We already know what $\int te^tdt$ is, so we conclude that:&lt;/p&gt;

\[\int t^2 e^t dt = t^2e^t - 2te^t + 2e^t\]

&lt;p&gt;Look carefully at the structure of the above answer. What can you notice? First, there is an alternating sign between each term. Secondly, each term is made up of a differential and an integral. For example, in $t^2e^t - 2te^t + 2e^t$, notice how the first part of each term is the derivative of the previous term’s first part ($t^2 \rightarrow 2t \rightarrow 2$).&lt;/p&gt;

&lt;p&gt;These two facts aren’t just coincidences, they are a direct result of the IBP formula: the $f(t)$ that we choose is &lt;em&gt;always&lt;/em&gt; differentiated and the $g(t)$ is &lt;em&gt;always&lt;/em&gt; integrated. The alternating sign between the terms is explained by the fact that there is a minus sign in the IBP formula, so when we apply the IBP formula multiple times, some of the negative signs cancel to become positives, while some stay negative.&lt;/p&gt;

&lt;p&gt;In fact, we can derive a useful shortcut from this. Let us now try to evaluate $\int t^4 e^t dt$. This would normally be very tedious, as we would have to apply IBP four times! But let’s be clever about it. The integrand has two parts, $t^4$ and $e^t$. As we saw, the former will always be differentiated while the latter will always be integrated. Not forgetting the alternating signs, we can set up a small table:&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/gamma/t4table.png&quot; style=&quot;width:150px;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;The &lt;em&gt;D&lt;/em&gt; denotes the part of the integrand which we will always differentiate, i.e the &lt;em&gt;f&lt;/em&gt; that we choose for the IBP formula. Every term in the &lt;em&gt;D&lt;/em&gt; column is the derivative of the term above it.&lt;/p&gt;

&lt;p&gt;The &lt;em&gt;I&lt;/em&gt; denotes the part of the integrand that we will always integrate, i.e the $g’$ that we choose. Every term in the &lt;em&gt;I&lt;/em&gt; column is the integral of the term above it (this is why we chose $g’(t) = e^t$; it is easy to integrate).&lt;/p&gt;

&lt;p&gt;The +/- column represents the alternating sign.&lt;/p&gt;

&lt;p&gt;Combining these facts provides an easy shortcut to integrate by parts. We simply lattice multiply according to the arrows, and include the sign.&lt;/p&gt;

\[\therefore \int t^4 e^t dt = t^4e^t - 4t^3e^t -24te^t + 24e^t\]

&lt;p&gt;This is reminiscent of the Maclaurin expansion in the sense that we are differentiating a power of &lt;em&gt;t&lt;/em&gt; many times. When we did that $n$ times, the result was a factorial. Could we apply the same logic here? Why don’t we just go ahead and evaluate $\int t^n e^tdt$?&lt;/p&gt;

&lt;h2 id=&quot;a-minor-problem&quot;&gt;A minor problem&lt;/h2&gt;

&lt;p&gt;Due to the alternating signs in the first column of our table, we will not be able to properly evaluate the integral $\int t^n e^tdt$ – how would we know whether the &lt;em&gt;n&lt;/em&gt;th term is positive or negative? However, there is an elegant workaround: we change our $g’(t)$ from $e^t$ to $e^{-t}$. Successive integrations of $e^{-t}$ have alternating sings, so when we multiply out with the alternating signs in the first column of our table, they will all cancel so that everything will be negative!&lt;/p&gt;

&lt;p&gt;To show this clearly, consider the table to evaluate $\int t^n e^{-t}dt$:&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/gamma/tntable.png&quot; style=&quot;width:300px;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;Lattice multiplying the first few terms gives&lt;/p&gt;

\[t^n(-e^{-t}) - nt^{n-1}e^{-t} + n(n-1)t^{n-2}(-e^{-t})-\ldots\]

\[= -t^ne^{-t} - nt^{n-1}e^{-t}-n(n-1)t^{n-2}e^{-t}-\ldots\]

&lt;p&gt;Every term has a negative sign, because we have two sets of alternating signs that cancel to a negative! Because we know that all the terms are negative, we can proceed.&lt;/p&gt;

&lt;h2 id=&quot;integrating-by-parts-n-times&quot;&gt;Integrating by parts &lt;em&gt;n&lt;/em&gt; times&lt;/h2&gt;

&lt;p&gt;We will now evaluate $\int t^ne^{-t}dt$:&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/gamma/tncomplete.png&quot; style=&quot;width:300px;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;Thus we see that:&lt;/p&gt;

\[\begin{multline*}
    \int t^n e^{-t}dt = -t^ne^{-t} - nt^{n-1}e^{-t}-n(n-1)t^{n-2}e^{-t}-\ldots \\ - n(n-1)(n-2)(\cdots)(3)(2)te^{-t} - n!e^{-t}
\end{multline*}\]

&lt;p&gt;Note very carefully the last term – we have managed to produce an $n!$ (I would put another exclamation mark there, but…)&lt;/p&gt;

&lt;p&gt;This is good news, but we now need to isolate the $n!$ by removing all of the other terms. The easiest way to do this is to cleverly choose limits on our integral such that all of the other terms, as well as the $e^{-t}$ in $-n!e^{-t}$ disappear to leave us with solely $n!$&lt;/p&gt;

&lt;p&gt;To do this, we can first notice that if the lower limit of the integral were zero:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;all of the terms that contain powers of &lt;em&gt;x&lt;/em&gt; will disappear (since anything multiplied by zero is zero)&lt;/li&gt;
  &lt;li&gt;the last term would reduce to $n!$ because $e^0=1$.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Hence choosing a lower limit of 0 produces $n!$ and nothing more. Wouldn’t it be great if we could now choose a suitable upper limit that will make all other terms disappear? This can be done. Note that as &lt;em&gt;t&lt;/em&gt; grows, $e^{-t}$ shrinks. In particular, as $t \rightarrow \infty$, $e^{-t} \rightarrow 0$. Thus if our upper limit where $\infty$, every single term would equate to zero. Just what we need, since our lower bound has already produced $n!$ Adding these limits to the integral:&lt;/p&gt;

\[\begin{aligned}

 \int_0^\infty t^n e^{-t}dt = \left[ -t^ne^{-t} - nt^{n-1}e^{-t} - \ldots - n!te^{-t} - n!e^{-t} \right]_0^\infty\\
 = [0] - [-n!] = n!
 \end{aligned}\]

&lt;p&gt;By using our well-chosen limits, we have produced a factorial (and nothing more). To reiterate:&lt;/p&gt;

\[\int_0^\infty t^n e^{-t}dt = n!\]

&lt;p&gt;This is a hugely significant result, as we have shown that the factorial can be written as the result of an integral. But integrals don’t have to have integer components – we have managed to de-couple factorials from integers, which is what we set out to do! We shall therefore redefine the factorial, and change the variable from &lt;em&gt;n&lt;/em&gt; to &lt;em&gt;x&lt;/em&gt; to reflect the fact that we don’t just have to deal with integers any more:&lt;/p&gt;

\[x! = \int_0^\infty t^x e^{-t}dt\]

&lt;p&gt;Doesn’t this look familiar?&lt;/p&gt;

&lt;h2 id=&quot;conclusion-for-part-2&quot;&gt;Conclusion for part 2&lt;/h2&gt;

&lt;p&gt;We have shown that repeated integration by parts can produce a factorial, and in fact that&lt;/p&gt;

\[x! = \int_0^\infty t^x e^{-t}dt\]

&lt;p&gt;Compare this to the gamma function, and you will see that there is only a slight difference:&lt;/p&gt;

\[\Gamma (x) = \int_0^\infty t^{x-1}e^{-t} dt\]

&lt;p&gt;The first integral has an $x$ in the exponent, while the second has an $x-1$. You can connect the dots here on your own, but in &lt;a href=&quot;/2017/07/21/gamma-intuition-3/&quot;&gt;Part 3&lt;/a&gt; I will explicitly show how the gamma function related to the factorial.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Intuiting the gamma function, part 1</title>
   <link href="https://reasonabledeviations.com/2017/07/11/gamma-function-intuition/"/>
   <updated>2017-07-11T00:00:00+00:00</updated>
   <id>https://reasonabledeviations.com/2017/07/11/gamma-function-intuition</id>
   <content type="html">&lt;p&gt;What is the factorial of a half? This series of posts builds up from middle-school algebra to the enigmatic gamma function in an attempt to answer this simple question.&lt;!--more--&gt;&lt;/p&gt;

&lt;p&gt;When high-school students study the Binomial Theorem, a classic problem is the expansion of a binomial with a fractional power, such as $(1+x)^{1/2}$. Speaking from experience, a student’s first attempt is often to naïvely substitute values into the formula for the Binomial Theorem, leading to&lt;/p&gt;

\[(1+x)^{1/2} = 1 +  \binom{0.5}{1}x + \binom{0.5}{2}x^2 +\ldots+x^n\]

&lt;p&gt;The problem arises when trying to evaluate $\binom{n}{r}$. We could just try to use the standard formula:&lt;/p&gt;

\[\binom{n}{r} = \frac{n!}{r!(n-r)!}\]

&lt;p&gt;but the student will soon encounter the troubling question of how to evaluate $0.5!.$ Clearly, the traditional definition of the factorial does not make any provision for non-integers.&lt;/p&gt;

&lt;p&gt;However, it turns out that there &lt;em&gt;is&lt;/em&gt; a natural extension of the factorial to all complex numbers excluding the nonpositive integers called the &lt;strong&gt;gamma function&lt;/strong&gt;, $\Gamma (x)$, which is defined using an integral:&lt;/p&gt;

\[\Gamma (x) = \int_0^\infty t^{x-1}e^{-t} dt, \qquad x \in \mathbb{C}| x \not\in \left \{ \{0\} \cup \mathbb{Z}^- \right \}\]

&lt;p&gt;Don’t let the &lt;em&gt;t&lt;/em&gt; distract you: it is just a dummy variable. The gamma function is a function of &lt;em&gt;x&lt;/em&gt;. But the question is, &lt;em&gt;what on earth has this got to do with factorials?&lt;/em&gt; As it happens, there is a wonderful intuitive reason for the form of the gamma function, and how it relates to $x!$, which I aim to explain. Following along will require the reader to have a reasonable understanding of differentiation and integration, but I will try to assume little else.&lt;/p&gt;

&lt;p&gt;Just a disclaimer: the maths used here isn’t going to be formally rigorous, but it will definitely be valid and hopefully intuitive.&lt;/p&gt;

&lt;h2 id=&quot;the-factorial&quot;&gt;The Factorial&lt;/h2&gt;

&lt;p&gt;For $\forall n \in \mathbb{N}$ (this means ‘for all natural numbers’), the factorial, which will henceforth be
referred to as the ‘traditional factorial’, is defined by the following two conditions:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;$1! = 1$&lt;/li&gt;
  &lt;li&gt;$n! = n(n-1)!$, the &lt;strong&gt;recursion condition&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These two conditions alone are sufficient to calculate any integer factorials.&lt;/p&gt;

&lt;p&gt;For example, if we needed to find $4!$, we would simply apply the recursion condition multiple times until we see a $1!$, at which point we apply condition 1:&lt;/p&gt;

\[4! = 4 \cdot 3! = 4 \cdot 3 \cdot 2! = 4 \cdot 3 \cdot 2 \cdot 1! = 4 \cdot 3 \cdot 2 \cdot 1 = 24\]

&lt;p&gt;Now the problem is that there is no obvious way to apply these two conditions to non-integers. We &lt;em&gt;could&lt;/em&gt; conceivably apply the recursion condition&lt;/p&gt;

\[2.5! = 2.5 \cdot 1.5! = 2.5 \cdot 1.5 \cdot 0.5!\]

&lt;p&gt;But we are now left with the conundrum of working out $0.5!$. Nevertheless, it is important to discuss this, because any function that claims to extend the factorial must also meet these conditions. We shall eventually prove that the gamma function &lt;em&gt;does&lt;/em&gt; have these two properties, but first we must understand its shape.&lt;/p&gt;

&lt;h2 id=&quot;the-gamma-function&quot;&gt;The Gamma Function&lt;/h2&gt;

&lt;p&gt;Just a reminder that our final goal is to explain:&lt;/p&gt;

\[\Gamma(x) = \int_0 ^\infty t^{x-1}e^{-t}dt\]

&lt;p&gt;We will approach this in a slightly round-about way. To begin with, we need to find an area of mathematics in which the factorial is used then see if we can tweak the method to work with non-integers. The most common use of the factorial is in permutations and combinations, but these deal with integers so are unlikely to be of use. However, another area in which the factorial appears is in Taylor’s theorem. Consider the Maclaurin series (a simpler special case of the Taylor series) for $e^x$:&lt;/p&gt;

\[e^x = 1 + x + \frac{1}{2!}x^2 + \frac{1}{3!}x^3 + \ldots + \frac{1}{n!}x^n + \ldots\]

&lt;p&gt;Actually, this just begs the question: why is there an $n!$ in the Maclaurin series? This is interesting enough to warrant a tangent, though it will soon become apparent that it is not so much a tangent as a key to understanding the gamma function.&lt;/p&gt;

&lt;h2 id=&quot;deriving-the-maclaurin-series&quot;&gt;Deriving the Maclaurin series&lt;/h2&gt;

&lt;p&gt;We will assume that a function $f(x)$ can be written as a polynomial of degree &lt;em&gt;n&lt;/em&gt; plus some remainder term $R(x)$ (which we will ignore):&lt;/p&gt;

\[f(x) = a_0 + a_1x + a_2x^2 + \cdots + a_nx^n + R(x)\]

&lt;p&gt;This is a reasonable assumption, because you’ve probably seen that polynomials can often be made to look like other functions. Here is a graph on which I’ve plotted $y = \sin x$ as well as $y = x - \frac{x^3}{3!} + \frac{x^5}{5!}$, which are the first three terms of the Maclaurin series for $\sin x$.&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/gamma/maclaurin.png&quot; style=&quot;width:400px;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;But I digress. Going back to our expression for a general function $f(x)$, all we have to do is find the values of our coefficients $a_0, a_1, a_2, \ldots, a_n$, and we will have our approximation. Well for starters, we can easily find $a_0$ because it is the constant term. If we substitute in $x = 0$, everything except that constant will disappear, so we can see that:&lt;/p&gt;

\[f(0) = a_0\]

&lt;p&gt;A nice trick is that we can actually also use similar reasoning to find &lt;em&gt;every other coefficient&lt;/em&gt;. How? By differentiating. Notice that:&lt;/p&gt;

\[f'(x) = a_1 + 2a_2x + \cdots + na_n x^{n-1}\]

&lt;p&gt;Now, substituting $x=0$ will kill all the terms except $a_1$. Hence:&lt;/p&gt;

\[f'(0)=a_1\]

&lt;p&gt;To find $a_2$, there is a slight difference. Note that $a_2$ was initially the coefficient of the $x^2$ term, so when we differentiate $a_2x^2$ twice we arrive at $2a_2$.&lt;/p&gt;

\[f''(0)=2a_2 \implies a_2 = \frac{f''(0)}{2}\]

&lt;p&gt;We can repeat this process, differentiating as we go, to find $a_n$. Just to make this clear, let us examine do this step by step (but only consider the last term):&lt;/p&gt;

\[\begin{gather*}
    f(x) = [ \ldots ] + a_n x^n \\
    f'(x) = [ \ldots ] + na_n x^{n-1} \\
    f''(x) = [ \ldots ] + n(n-1)a_n x^{n-2} \\
    f'''(x) = [ \ldots ] + n(n-1)(n-2)a_n x^{n-3} \\
    \cdots \\
    f^{(n)}(x) = n(n-1)(n-2)\ldots(3)(2)(1)a_n = n!a_n\\
\end{gather*}\]

&lt;p&gt;So for our polynomial, we conclude that:&lt;/p&gt;

\[f^{(n)}(0) = n!a_n \implies a_n = \frac{f^{(n)}(0)}{n!}\]

&lt;p&gt;Substituting these coefficients back into $f(x)$:&lt;/p&gt;

\[f(x) = f(0) + f'(0)x + \frac{f''(0)}{2!}x^2 + \ldots + \frac{f^{(n)}(0)}{n!}x^n\]

&lt;p&gt;which completes our derivation of the Maclaurin series. If you want to find the Maclaurin series for a particular function, you just have to compute it’s &lt;em&gt;n&lt;/em&gt;th derivative and substitute in $x=0$.&lt;/p&gt;

&lt;h2 id=&quot;conclusion-for-part-1&quot;&gt;Conclusion for Part 1&lt;/h2&gt;

&lt;p&gt;I know that this is an abrupt end, and that I haven’t even started to explain the form of the gamma function, but in this post I showed that factorials have a natural relation to calculus, when one repeatedly differentiates a power of &lt;em&gt;x&lt;/em&gt;. I trust that because of this, the reader may be getting a sneaking suspicion that there might actually be a link between factorials and &lt;em&gt;integrals&lt;/em&gt;, which would explain why the gamma function looks the way it does.&lt;/p&gt;

&lt;p&gt;In fact, there is a certain type of integration that &lt;em&gt;does&lt;/em&gt; involve repeated differentiation. If you did A-level mathematics or an equivalent, you will almost certainly have come across it, though you may not see right now how it relates to factorials. I am referring to &lt;em&gt;Integration by Parts&lt;/em&gt;. Head to &lt;a href=&quot;/2017/07/16/gamma-intuition-2/&quot;&gt;Part 2&lt;/a&gt; to find out more.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Combinatorial optimisation with a pseudo-genetic algorithm</title>
   <link href="https://reasonabledeviations.com/2017/07/05/social-seating/"/>
   <updated>2017-07-05T00:00:00+00:00</updated>
   <id>https://reasonabledeviations.com/2017/07/05/social-seating</id>
   <content type="html">&lt;p&gt;&lt;strong&gt;a python approach to XKCD’s Social Seating problem&lt;/strong&gt;&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/socialseating/movie_seating.png&quot; style=&quot;width:400px;&quot; /&gt;
&lt;/center&gt;
&lt;!--more--&gt;

&lt;p&gt;Social groups are remarkably complex affairs, and on many occasions this complexity can lead to awkward situations. Randall Munroe humorously portrays one such example in XKCD #173. This comic naturally begs the question – how &lt;em&gt;do&lt;/em&gt; we find the optimal linear seating arrangement for a given social group?&lt;/p&gt;

&lt;p&gt;If you’re not a fan of people trying to logically dissect non-mathematical things, now would be a good time to cut your losses (at least you got to see the XKCD comic!). Otherwise, don your cringe-protection glasses and we can move on.&lt;/p&gt;

&lt;p&gt;The precise problem here is that the nonlinear ‘social graph’ has to be compressed onto a linear arrangement, which, as the comic points out, is computationally hard. Nevertheless, we will attempt to find a good arrangement (not necessarily the optimal one) by using a genetic algorithm of sorts. But before that, we must firstly quantify the various parameters in this problem.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;Edit as of Jan 2020&lt;/strong&gt;: reading back on some of my old posts, I typically realise that many of my solutions are excessively convoluted or poorly-explained. This post is no exception. One particular highlight is my rediscovery of adjacency matrices!&lt;/em&gt;&lt;/p&gt;

&lt;h2 id=&quot;contents&quot;&gt;Contents&lt;/h2&gt;

&lt;!-- TOC depthFrom:2 depthTo:6 withLinks:1 updateOnSave:0 orderedList:0 --&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;#1-developing-the-initial-model&quot;&gt;1. Developing the initial model&lt;/a&gt;
    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#examining-a-social-network&quot;&gt;Examining a social network&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#a-quick-example&quot;&gt;A quick example&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#2-refining-the-model&quot;&gt;2. Refining the model&lt;/a&gt;
    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#the-total-enjoyment-for-a-general-arrangement&quot;&gt;The total enjoyment for a general arrangement&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#back-to-the-xkcd-network&quot;&gt;Back to the XKCD network&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#3-optimising-the-arrangement&quot;&gt;3. Optimising the arrangement&lt;/a&gt;
    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#genetic-algorithms&quot;&gt;Genetic algorithms&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#4-implementing-the-pseudo-genetic-algorithm-in-python&quot;&gt;4. Implementing the (pseudo-)genetic algorithm in python&lt;/a&gt;
    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#generating-the-population&quot;&gt;Generating the population&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#evaluating-a-phenotypes-fitness&quot;&gt;Evaluating a phenotype’s fitness&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#survival-of-the-fittest-and-reproduction&quot;&gt;Survival of the fittest and reproduction.&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#mutation&quot;&gt;Mutation&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#iteration&quot;&gt;Iteration&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#application-to-the-xkcd-network&quot;&gt;Application to the XKCD network&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#conclusion&quot;&gt;Conclusion&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;!-- /TOC --&gt;

&lt;h2 id=&quot;1-developing-the-initial-model&quot;&gt;1. Developing the initial model&lt;/h2&gt;

&lt;p&gt;What does it mean for a seating arrangement to be optimal? I am going to take a disgustingly utilitarian approach, by simply calculating the enjoyment per individual for a certain arrangement then summing over all individuals to find the &lt;em&gt;total enjoyment&lt;/em&gt;. The optimal arrangement is then just the one with the highest total enjoyment (duh).  Before going further, we must first decide on a way of describing social networks which will allow us to do this analysis.&lt;/p&gt;

&lt;h3 id=&quot;examining-a-social-network&quot;&gt;Examining a social network&lt;/h3&gt;

&lt;p&gt;Following the XKCD comic, each individual be denoted by a circular node. We will consider four types of relationships between individuals: friends, one-way ‘crushes’, romantic relationships, and acquaintances. I’m going to slightly modify the notation of the comic though:&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/socialseating/relationship_types.png&quot; style=&quot;width:400px;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;The major advantage of this notation is that it encourages us to only consider the &lt;em&gt;outgoing&lt;/em&gt; connections from a particular individual, which will be a big simplification when we come to considering an individual’s enjoyment. But it is far from perfect. For example, this notation implies that a romantic relationship is the same as a mutual crush, i.e. that an individual would get as much enjoyment from sitting next to their crush as they would with their partner.&lt;/p&gt;

&lt;p&gt;Proceeding anyway, we assign each type of outgoing arrow a weighting, corresponding to how much enjoyment an individual experiences when sitting next to a person of that relation. Here is one proposed weighting:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Crush (arrowed line): weight = 2&lt;/li&gt;
  &lt;li&gt;Friend (solid line): weight = 1&lt;/li&gt;
  &lt;li&gt;Acquaintance (dotted line): weight = 0.5&lt;/li&gt;
  &lt;li&gt;Stranger (no line): weight = 0&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Of course, the actual values are quite arbitrary – what matters is their relative ordering. I think it is fair to say that an individual’s seating preferences follow the above list in descending order. That is, given free choice, you would enjoy most to sit next to your crush, and least enjoy sitting next to a stranger. Choosing precise weightings would require you to quantify, for example, exactly how much more you would prefer to sit with a crush than with a friend – good luck with that!&lt;/p&gt;

&lt;h3 id=&quot;a-quick-example&quot;&gt;A quick example&lt;/h3&gt;

&lt;p&gt;Now that we can &lt;em&gt;describe&lt;/em&gt; a social network, let’s try to calculate the total enjoyment for a seating arrangement.
We may as well use the network given in the comic. Here it is reproduced with our new notation (labelling each individual with a letter of the alphabet):&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/socialseating/xkcdgraphletters.png&quot; style=&quot;width:400px;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;If this group has to sit down in a line, they will form an arrangement
that will be denoted by a sequence of letters. For example, the
arrangement &lt;em&gt;BFGHECDA&lt;/em&gt; represents &lt;em&gt;F&lt;/em&gt; sitting to
the right of &lt;em&gt;B&lt;/em&gt;, &lt;em&gt;G&lt;/em&gt; to the right of &lt;em&gt;F&lt;/em&gt;, etc. For each of the
8! possible arrangements, we could calculate a total enjoyment, denoted
by $\epsilon_{\text{total}}$, which is done as follows. Consider the arrangement $ABCDEFGH$:&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/socialseating/xkcdline.png&quot; style=&quot;width:400px;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;Assuming that an individual can only be affected by the individuals immediately to their left or right, each individual can have up to two outgoing connections,  which can assume values of 2, 1, 0.5, or 0 depending on the relationship. For each individual, we add up these values, then to find $\epsilon_\text{total}$ we just sum over all the individuals.&lt;/p&gt;

&lt;p&gt;This is easy to do with just one arrangement, but we need a robust way of finding $\epsilon_{\text{total}}$ for &lt;em&gt;any&lt;/em&gt; arrangement.&lt;/p&gt;

&lt;h2 id=&quot;2-refining-the-model&quot;&gt;2. Refining the model&lt;/h2&gt;

&lt;p&gt;We will define the system as follows:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Let the number of individuals in a network be denoted by $n$. In the XKCD network, there are 8 individuals, so $n=8$.&lt;/li&gt;
  &lt;li&gt;Each individual will be arbitrarily labelled $1, 2, 3, \ldots , n$, rather than using letters of the alphabet.&lt;/li&gt;
  &lt;li&gt;In order to define an individual’s relationships with other members of the group, we will associate a vector $x_i$ with each individual in the group, where $x_i \in \mathbb{R}^n$.
    &lt;ul&gt;
      &lt;li&gt;This vector contains one element for each member in the network (including itself).&lt;/li&gt;
      &lt;li&gt;The $m$th entry of the vector $x_i$ encodes the enjoyment produced for individual $i$ by individual $m$.&lt;/li&gt;
      &lt;li&gt;We will denote the $m$th row of the vector $x_i$ as $[i, m]$.&lt;/li&gt;
      &lt;li&gt;Let me reiterate that $x_i$ does &lt;em&gt;not&lt;/em&gt; mean the &lt;em&gt;i&lt;/em&gt; th element of a vector $x$. $\mathbf{x_i}$ &lt;strong&gt;is the vector!&lt;/strong&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is a lot to take in, but hopefully this notation will become clearer with an example. Here is the XKCD network, with the letter labels replaced by numerals.&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/socialseating/xkcdgraphnumbers.png&quot; style=&quot;width:400px;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;Corresponding to the sixth individual, we have: $ x_6 = \langle 0,0,0,2,1,0,0.5,0 \rangle $. Let’s break this down to make sure we are on the same page. Recalling our relationship weightings from earlier on:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;The first three entries (and the final one) are equal to zero,
because individual 6 has no relationship with individuals 1, 2, 3,
and 8 (i.e they are strangers to him). We could also write $[6,1] = [6,2] = [6,3] = [6,8] = 0$.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;The fourth entry is equal to two, because individual 6 has a crush
on individual 4. We could also write $[6,4] = 2$.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;The fifth entry is equal to one, because individual 6 is friends
with individual 5. We could also write $[6,5] = 1$.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;The seventh entry is equal to a half, because individual 6 is an
acquaintance of individual 7. We could also write $[6,7] = 0.5$.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You may notice that I neglected to mention the sixth entry of $x_6$. $[6, 6]$ represents the enjoyment produced for the sixth individual by the sixth individual. I suppose that some people do enjoy being alone, but seeing as we are dealing with a group of friends at a cinema, I will set this entry to zero. That is to say, if $i=m, [i,m] = 0$. In our model, individuals get no enjoyment from sitting next to themselves.&lt;/p&gt;

&lt;p&gt;We can write a vector $x_i$ for every individual in our network. After doing this, &lt;em&gt;we do not need the graph any more&lt;/em&gt; – our network is fully defined by these &lt;em&gt;n&lt;/em&gt; vectors!&lt;/p&gt;

&lt;h3 id=&quot;the-total-enjoyment-for-a-general-arrangement&quot;&gt;The total enjoyment for a general arrangement&lt;/h3&gt;

&lt;p&gt;We will denote a general linear arrangement of individuals as
\(u_1, u_2, u_3, \ldots, u_n\). (earlier we had &lt;em&gt;ABCDEFGH&lt;/em&gt;). You may be wondering why I have decided to add &lt;em&gt;yet another&lt;/em&gt; set of variables – am I just trying to be novel in my notation? As it happens, there is a very important distinction between the individual’s location $\left(u\right)$ and the &lt;em&gt;label&lt;/em&gt; of the individual ($i$). &lt;strong&gt;$\mathbf{u_1}$ is not the same as individual 1&lt;/strong&gt;. $u_1$ could be any of the people in the network – but what matters is that this individual is in the first position of the arrangement. &lt;strong&gt;The &lt;em&gt;u&lt;/em&gt; terms are a way of representing the order of people in their seats&lt;/strong&gt;. Strictly speaking, I should therefore be saying things like “the individual &lt;em&gt;at&lt;/em&gt; $u_1$”, but I will often just say “the individual $u_1$” or just “$u_1$”. This is an important point, so I’ll give you a concrete example:&lt;/p&gt;

\[\begin{align*}
    u_1 &amp;amp;= 3 \\
    u_2 &amp;amp;= 7 \\
    u_3 &amp;amp;= 1 \\
    u_4 &amp;amp;= 2 \\
    u_5 &amp;amp;= 5  \\
    u_6 &amp;amp;= 8 \\
    u_7 &amp;amp;= 6  \\
    u_8 &amp;amp;= 4
\end{align*}\]

&lt;p&gt;corresponds to a seating arrangement 3, 7, 1, 2, 5, 8, 6, 4. Remember, each numeral is the label of a person, so this could alternatively be thought of as &lt;em&gt;CGABEHFD&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;In order to calculate $\epsilon_\text{total}$, we need to calculate the enjoyment for each $u$ term then take the sum of all these. Because of our simplification that an individual is only affected by the
people immediately to their left or right, a neat trick is to group
the arrangement into pairs. So we will think of the arrangement as:&lt;/p&gt;

\[(u_1, u_2), (u_2,u_3), (u_3, u_4), \ldots, (u_{n-2}, u_{n-1}),(u_{n-1}, u_n),\]

&lt;p&gt;where each pair of parentheses encloses the relationship between two individuals.&lt;/p&gt;

&lt;p&gt;It is at this stage that we need to recall the previously-defined vector notation. Just as $x_6$ represents the relationship of individual 6 to the network, the vector representing individual $u_1$’s relationship to the network is $x_{u_1}$.&lt;/p&gt;

&lt;p&gt;If we wanted to find how much individual 4 contributes to the enjoyment of individual 6, we would look in the 4th entry of $x_6$. Likewise, the enjoyment produced in individual $u_1$ by individual $u_2$ can be
found by looking in the $u_2$th entry of the vector $x_{u_1}$. This subscript-within-subscript notation is very confusing, which is why we defined an alternative notation, the square-bracket notation wherein $[i, m]$ represents the $m$th row of the vector $x_i$.&lt;/p&gt;

&lt;p&gt;With this notation, the $u_2$th entry of the vector $x_{u_1}$ is simply written as $[u_1, u_2]$.
However, note that in the pair $(u_1, u_2)$, individual $u_1$ also
produces some enjoyment in $u_2$. Thus the &lt;em&gt;total&lt;/em&gt; enjoyment produced
within the pair $(u_1, u_2)$ is equal to:&lt;/p&gt;

\[[u_1, u_2] + [u_2, u_1]\]

&lt;p&gt;Now all we need to do is sum over all the $n-1$ pairs. Therefore:&lt;/p&gt;

\[\epsilon_\text{total} = \sum_{r = 1}^{n-1} \left( [u_r, u_{r+1}] + [u_{r+1}, u_r] \right),\]

&lt;p&gt;where $r$ is a dummy variable.&lt;/p&gt;

&lt;h3 id=&quot;back-to-the-xkcd-network&quot;&gt;Back to the XKCD network&lt;/h3&gt;

&lt;p&gt;Let’s calculate the $\epsilon_\text{total}$ of a random arrangement. I’ll choose this with my custom arrangement-randomiser:&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;random_arrangement&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt;
	&lt;span class=&quot;c1&quot;&gt;# please note that this is a joke
&lt;/span&gt;    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&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;5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;4&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;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;7&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;To begin with, we need to know the vectors $x_i$. These unfortunately have to be hard-coded based on the network. Earlier we showed the full form of $x_6$ and how we got there. Let’s repeat the same process to find $x_i, i = 1,2,3,…,8$
(because $n=8$).&lt;/p&gt;

\[\begin{align*}
    x_1 &amp;amp;= \langle 0,2,0,0,0,0,0,0 \rangle \\
    x_2 &amp;amp;= \langle 2,0,1,0,0,0,0,1 \rangle \\
    x_3 &amp;amp;= \langle 0,1,0,1,0,0,0,0.5 \rangle \\
    x_4 &amp;amp;= \langle 0,0,1,0,0.5,2,0,0 \rangle \\
    x_5 &amp;amp;= \langle 0,0,0,0.5,0,1,0,0 \rangle \\
    x_6 &amp;amp;= \langle 0,0,0,2,1,0,0.5,0 \rangle \\
    x_7 &amp;amp;= \langle 0,0,0,0,0,0.5,0,2 \rangle \\
    x_8 &amp;amp;= \langle 0,,0.5,0,0,0,1,0 \rangle
\end{align*}\]

&lt;p&gt;In order to calculate $\epsilon_\text{total}$, we just need to apply our formula:&lt;/p&gt;

\[\begin{align*}
    \epsilon_\text{total} &amp;amp;= \sum_{r = 1}^{n-1} \left( [u_r, u_{r+1}] + [u_{r+1}, u_r] \right) \\
    &amp;amp;= [3, 2] + [2, 3] + [2, 8] + [8, 2] + \ldots + [1, 7] + [7, 1]  \\
    &amp;amp;= 1+1+1+1+0+0+0.5+0.5+2+2+0+0+0+0 \\
    &amp;amp;= 9
\end{align*}\]

&lt;p&gt;It is worth commenting on the fact that all of the above values come in identical pairs (that is, $[u_r, u_{r+1}] = [u_{r+1}, u_r]$), which is &lt;strong&gt;not true&lt;/strong&gt; generally. However, for this particular network, there is only one asymmetrical relationship, the one-way crush between individuals 7 and 8
– and in our particular arrangement, 7 is not next to 8.&lt;/p&gt;

&lt;p&gt;We could have found $\epsilon_\text{total}$ without our vector notation, and simply with arithmetic by looking at the linkages on the graph. However, the process that we have described in this section does not require actually looking at the networks, meaning that we can write code to do it for us.&lt;/p&gt;

&lt;h2 id=&quot;3-optimising-the-arrangement&quot;&gt;3. Optimising the arrangement&lt;/h2&gt;

&lt;p&gt;Now that we can effectively calculate $\epsilon_\text{total}$ for any given arrangement. We are ready to find the optimal arrangement.&lt;/p&gt;

&lt;p&gt;A brute force search (i.e, calculating
$\epsilon_\text{total}$ for every possible arrangement and returning the maximum), is not feasible due to the computational complexity, which is $\Theta(n!)$. For those not familiar with Big O notation and it’s variants – this is equivalent to saying that it’ll take one hell of a long time.&lt;/p&gt;

&lt;p&gt;So we need an alternative. The problem is that many optimisation algorithms rely on calculus, which requires a continuous independent variable. Our independent variable is the arrangement, which is certainly not continuous,
meaning that we cannot use many of these algorithms. How, then, do we approach this problem?&lt;/p&gt;

&lt;h3 id=&quot;genetic-algorithms&quot;&gt;Genetic algorithms&lt;/h3&gt;

&lt;p&gt;A genetic algorithm is an optimisation heuristic (shortcut) that mimics Darwinian natural selection.&lt;/p&gt;

&lt;p&gt;A brief summary of Darwin’s theory is as follows:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;There is a population of &lt;strong&gt;phenotypes&lt;/strong&gt;, individual members
with different genetics and hence different characteristics.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;This variety means that certain individuals are better suited to
their environment.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;The ‘fitter’ individuals survive and reproduce, passing on their
genetic information to the next generation.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;During the process of reproduction, random mutations may occur,
resulting in slight changes to the child phenotype.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;The process repeats, and over time, the species gets increasingly
better adapted to the environment.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A genetic algorithm follows the same steps, but in computer science terms:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;There is a ‘population’ of different possible arrangements.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Some of these arrangements have a higher $\epsilon_\text{total}$.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;These phenotypes survive, and some of the survivors ‘reproduce’ – their arrangements are somehow combined. In computer science, this step is known as the &lt;strong&gt;crossover&lt;/strong&gt;.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Random mutations occur, i.e. some arrangements may be changed slightly.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;The process repeats, and over time, the mean $\epsilon_\text{total}$ of the population should increase.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;4-implementing-the-pseudo-genetic-algorithm-in-python&quot;&gt;4. Implementing the (pseudo-)genetic algorithm in python&lt;/h2&gt;

&lt;p&gt;In this section we will write python code to find the optimum arrangement for the XKCD network, though of course we could use it for any other network.&lt;/p&gt;

&lt;p&gt;Let us represent our network as a list of lists, where each sublist corresponds to a vector $x_i$.&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;network&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;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&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;mi&quot;&gt;0&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;mi&quot;&gt;0&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;mi&quot;&gt;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;mi&quot;&gt;2&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;mi&quot;&gt;1&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;mi&quot;&gt;0&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;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&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;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;mi&quot;&gt;1&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;mi&quot;&gt;1&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;mi&quot;&gt;0&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;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;mi&quot;&gt;0&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;mi&quot;&gt;1&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;mf&quot;&gt;0.5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&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;mi&quot;&gt;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;mi&quot;&gt;0&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;mi&quot;&gt;0&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;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&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;mi&quot;&gt;0&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;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;mi&quot;&gt;0&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;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&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;mi&quot;&gt;0&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;mi&quot;&gt;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;mi&quot;&gt;0&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;mi&quot;&gt;0&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;mi&quot;&gt;0&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;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&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;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&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;mf&quot;&gt;0.5&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;mi&quot;&gt;0&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;mi&quot;&gt;1&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;n&quot;&gt;network_size&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;network&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;population_size&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;16&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;However, don’t forget that python indexing starts from zero. So to find the enjoyment that individual 5 produces in individual 3, we look at &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;network[2][4]&lt;/code&gt;, &lt;strong&gt;not&lt;/strong&gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;network[3][5]&lt;/code&gt;. We will also define two global variables: the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;network_size&lt;/code&gt; (number of individuals in the network) and the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;population_size&lt;/code&gt; (number of arrangements in the population).&lt;/p&gt;

&lt;h3 id=&quot;generating-the-population&quot;&gt;Generating the population&lt;/h3&gt;

&lt;p&gt;Creating our population of arrangements is a simple matter of shuffling a list of numbers.&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;nn&quot;&gt;random&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;generate_population&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;network_size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;population_size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&quot;&quot;&quot;
    :param network_size: the number of individuals in the network
    :param population_size: the number of different arrangements
    &quot;&quot;&quot;&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# Empty list which will contain the arrangements
&lt;/span&gt;    &lt;span class=&quot;n&quot;&gt;population&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;# Produce the simplest arrangement, which is just the
&lt;/span&gt;    &lt;span class=&quot;c1&quot;&gt;# individuals in order of their index, i.e [1,2,3,4,5,6,7,8].
&lt;/span&gt;    &lt;span class=&quot;n&quot;&gt;test_phenotype&lt;/span&gt; &lt;span class=&quot;o&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;range&lt;/span&gt;&lt;span class=&quot;p&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;n&quot;&gt;network_size&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;c1&quot;&gt;# Shuffle the test_phenotype to generate the population
&lt;/span&gt;    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;population_size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;random&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;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;test_phenotype&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;population&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;append&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;n&quot;&gt;test_phenotype&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;population&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;generate_population&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;network_size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;population_size&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;This function will return the following:&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;[[3, 4, 5, 6, 8, 2, 1, 7],
 [1, 5, 2, 3, 7, 8, 6, 4],
 [1, 5, 6, 3, 2, 8, 4, 7],
 [7, 2, 6, 4, 3, 1, 5, 8],
 [1, 5, 2, 4, 6, 8, 3, 7],
 [5, 6, 3, 1, 4, 7, 8, 2],
 [4, 5, 2, 1, 3, 8, 6, 7],
 [5, 8, 4, 1, 7, 6, 2, 3],
 [7, 2, 5, 1, 3, 8, 4, 6],
 [5, 3, 1, 2, 6, 7, 4, 8],
 [7, 1, 2, 4, 6, 8, 3, 5],
 [7, 8, 2, 4, 6, 1, 3, 5],
 [2, 4, 3, 7, 1, 8, 6, 5],
 [1, 8, 7, 5, 3, 6, 4, 2],
 [3, 8, 5, 7, 2, 1, 6, 4],
 [3, 1, 6, 8, 7, 4, 5, 2]]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;evaluating-a-phenotypes-fitness&quot;&gt;Evaluating a phenotype’s fitness&lt;/h3&gt;

&lt;p&gt;We now need to implement a function to calculate the $\epsilon_\text{total}$ of a particular arrangement. We do this by initially setting $\epsilon_\text{total} = 0$, then cumulatively adding the contributions from each pair.&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;evaluate_etotal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;arrangement&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;etotal&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;r&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;network_size&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;c1&quot;&gt;# u_r1 is meant to represent u_{r+1}
&lt;/span&gt;        &lt;span class=&quot;c1&quot;&gt;# When defining u_r and u_r1, we subtract 1,
&lt;/span&gt;        &lt;span class=&quot;c1&quot;&gt;# because indices in python start from zero.
&lt;/span&gt;        &lt;span class=&quot;n&quot;&gt;u_r&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;arrangement&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;r&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;n&quot;&gt;u_r1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;arrangement&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;r&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;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;

        &lt;span class=&quot;c1&quot;&gt;# network[u_r][u_r1] is the same as [u_r, u_r1]
&lt;/span&gt;        &lt;span class=&quot;c1&quot;&gt;# epair is the contribution from each pair
&lt;/span&gt;        &lt;span class=&quot;n&quot;&gt;epair&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;network&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;u_r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;u_r1&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;network&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;u_r1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;u_r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;etotal&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;epair&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;etotal&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;We can test this function on the random arrangement that we examined earlier, by running: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;evaluate_etotal([3, 2, 8, 5, 4, 6, 1, 7])&lt;/code&gt;. This returns a value of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;9.0&lt;/code&gt;, which agrees with our calculation.&lt;/p&gt;

&lt;h3 id=&quot;survival-of-the-fittest-and-reproduction&quot;&gt;Survival of the fittest and reproduction.&lt;/h3&gt;

&lt;p&gt;Now that we have a function to measure the fitness of an arrangement, we need to find a way to remove the ‘weak’ individuals, leaving only survivors. Then, we need to let these survivors reproduce. But what does this mean in terms of arrangements and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;etotal&lt;/code&gt;s? The first part is simple: we will consider an arrangement to be &lt;em&gt;weak&lt;/em&gt; if it’s $\epsilon_\text{total}$ is less than the median $\epsilon_\text{total}$ (which I think is reasonable).&lt;/p&gt;

&lt;p&gt;However, once we have the survivors, how can we get them to ‘reproduce’ to form new arrangements? The important aspect of reproduction, when it comes to genetic
algorithms, is that the offspring receives features from both parents (who are themselves ‘fit’) – there is a crossover of genetic material. However, the individuals of our population are &lt;em&gt;arrangements&lt;/em&gt; we cannot just take the first half of one and combine it with the second half of another.&lt;/p&gt;

&lt;p&gt;I thought for a long time about this, and came up with a rather unsatisfactory solution. Instead of mixing genetics from two parents, I decided to define a child as half of a parent’s arrangement, plus the shuffle of the other half.&lt;/p&gt;

&lt;p&gt;Yes, I know. It’s hardly reproduction – more like a glorified form of mutation, but this is why I have titled the post as a &lt;em&gt;pseudo&lt;/em&gt;-genetic algorithm. If anyone can come up with a better idea for what reproduction could mean in the context of seating arrangements, I’m all ears.&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;nn&quot;&gt;statistics&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;survive_and_reproduce&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;population&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# We will make a new list that stores the 'fitness' of each
&lt;/span&gt;    &lt;span class=&quot;c1&quot;&gt;# phenotype in the population, where 'fitness'
&lt;/span&gt;    &lt;span class=&quot;c1&quot;&gt;# depends on the e_total.
&lt;/span&gt;    &lt;span class=&quot;n&quot;&gt;population_fitness&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;evaluate_etotal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;phenotype&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;phenotype&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;population&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# If a phenotype has an etotal lower than the median etotal,
&lt;/span&gt;    &lt;span class=&quot;c1&quot;&gt;# it is WEAK.
&lt;/span&gt;    &lt;span class=&quot;n&quot;&gt;median&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;statistics&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;median&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;population_fitness&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;weaklings&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;i&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;population&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;population_fitness&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;median&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# Destroy the weak arrangements by excluding them
&lt;/span&gt;    &lt;span class=&quot;c1&quot;&gt;# from the survivor list
&lt;/span&gt;    &lt;span class=&quot;n&quot;&gt;survivors&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;population&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&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;i&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;population&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;i&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;weaklings&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;number_removed&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;population&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;nb&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;survivors&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# Now, the survivors 'breed'. This for loop maintains
&lt;/span&gt;    &lt;span class=&quot;c1&quot;&gt;# the population_size.
&lt;/span&gt;    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;number_removed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;# Pick a parent from the survivors
&lt;/span&gt;        &lt;span class=&quot;n&quot;&gt;parent&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;random&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;choice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;survivors&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

        &lt;span class=&quot;c1&quot;&gt;# A child is the first half of a parent,
&lt;/span&gt;        &lt;span class=&quot;c1&quot;&gt;# plus the shuffled second half (estranged_father).
&lt;/span&gt;        &lt;span class=&quot;n&quot;&gt;estranged_father&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;parent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;network_size&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;network_size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;random&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;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;estranged_father&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;child&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;parent&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;nb&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;network_size&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&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;estranged_father&lt;/span&gt;

        &lt;span class=&quot;c1&quot;&gt;# The child is now part of the new generation
&lt;/span&gt;        &lt;span class=&quot;n&quot;&gt;survivors&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;child&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;survivors&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;mutation&quot;&gt;Mutation&lt;/h3&gt;

&lt;p&gt;Mutations are random changes to the genetic material. With regard to seating arrangements, I will consider a mutation to be the swapping of any two members within the arrangement.
A required parameter is the &lt;em&gt;mutation rate&lt;/em&gt;, which encodes how often mutations should occur. A value of 0.05 (which is what we shall use) means that, on average, 5 in every 100 arrangements will contain a swap.&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;mutate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;generation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mutation_rate&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;0.05&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# Each member of the population has a chance of being mutated
&lt;/span&gt;    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;generation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)):&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;# random.random() returns a float between 0 and 1, so 
&lt;/span&gt;        &lt;span class=&quot;c1&quot;&gt;# this is equivalent to a probability of the mutation_rate.
&lt;/span&gt;        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;random&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;random&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mutation_rate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;# A mutation entails two elements of
&lt;/span&gt;            &lt;span class=&quot;c1&quot;&gt;# the arrangement swapping with each other.
&lt;/span&gt;            &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;random&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sample&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;network_size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;population&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;population&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&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;population&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;population&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;b&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;h3 id=&quot;iteration&quot;&gt;Iteration&lt;/h3&gt;

&lt;p&gt;Evolution requires many generations to happen. In the context of our algorithm, this just means that we need to repeat the process of survival, crossover, and mutation over and over again.&lt;/p&gt;

&lt;p&gt;That being said, we also want to get a feel for how our algorithm is doing, so I will write a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;progress&lt;/code&gt; function to do that. The idea is that at every iteration, we will run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;progress(generation)&lt;/code&gt;, which will log the best $\epsilon_\text{total}$ of that generation, and the corresponding arrangement.&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;progress_list&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;hall_of_fame&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;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;progress&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;generation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;population_fitness&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;evaluate_etotal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;phenotype&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;phenotype&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt;
                          &lt;span class=&quot;n&quot;&gt;population&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;progress_list&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;max&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;population_fitness&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;max_location&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;population_fitness&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;index&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;max&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;population_fitness&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;hall_of_fame&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;population&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;max_location&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;h2 id=&quot;application-to-the-xkcd-network&quot;&gt;Application to the XKCD network&lt;/h2&gt;

&lt;p&gt;Now that we have everything ready, we are ready to find the optimal arrangement for the XKCD network.&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;network&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;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&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;mi&quot;&gt;0&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;mi&quot;&gt;0&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;mi&quot;&gt;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;mi&quot;&gt;2&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;mi&quot;&gt;1&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;mi&quot;&gt;0&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;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&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;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;mi&quot;&gt;1&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;mi&quot;&gt;1&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;mi&quot;&gt;0&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;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;mi&quot;&gt;0&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;mi&quot;&gt;1&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;mf&quot;&gt;0.5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&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;mi&quot;&gt;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;mi&quot;&gt;0&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;mi&quot;&gt;0&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;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&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;mi&quot;&gt;0&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;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;mi&quot;&gt;0&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;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&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;mi&quot;&gt;0&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;mi&quot;&gt;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;mi&quot;&gt;0&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;mi&quot;&gt;0&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;mi&quot;&gt;0&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;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&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;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&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;mf&quot;&gt;0.5&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;mi&quot;&gt;0&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;mi&quot;&gt;1&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;n&quot;&gt;network_size&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;network&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;population_size&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;16&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;population&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;generate_population&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;network_size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;population_size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;gen_number&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;500&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;gen_number&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;next_generation&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;survive_and_reproduce&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;population&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;progress&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;next_generation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;mutate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;next_generation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;population&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;next_generation&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;gen_number&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;n&quot;&gt;champion_location&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;progress_list&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;index&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
                    &lt;span class=&quot;nb&quot;&gt;max&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;progress_list&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;The optimal arrangement is {}, with an etotal of: {}&quot;&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;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;hall_of_fame&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;champion_location&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
                      &lt;span class=&quot;nb&quot;&gt;max&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;progress_list&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)))&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;gen_number&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;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&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;n&quot;&gt;plt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;plot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;progress_list&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;n&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'Evolution of etotal'&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;n&quot;&gt;xlabel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'Generation number'&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;n&quot;&gt;ylabel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sa&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'$\epsilon$ total'&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;n&quot;&gt;show&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;One example of running this gave the output:&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;The optimal arrangement is: [4, 2, 8, 7, 3, 1, 6, 5],
with an etotal of: 17
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/socialseating/etotal_ev.png&quot; style=&quot;width:400px;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;I ran this a number of times on my system, and plotted all the results at once.&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/socialseating/many_etotal.png&quot; style=&quot;width:400px;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;Notice how it is quite volatile: many generations have a &lt;em&gt;lower&lt;/em&gt; maximum $\epsilon_\text{total}$ than their parent’s generation. This is probably because our reproduction step is really just another form of mutation. However, because our code keeps track of best arrangement so far, all that matters is that our optimisation algorithm &lt;em&gt;hits&lt;/em&gt; the maximum – it doesn’t have to stay there.&lt;/p&gt;

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

&lt;p&gt;So there you have it. Using pseudo genetic algorithm, we have managed to find good linear seating arrangements given a friendship network.&lt;/p&gt;

&lt;p&gt;This problem of optimising seating arrangements actually belongs to the much broader (and actively researched) field of &lt;em&gt;combinatorial optimisation&lt;/em&gt; – so it is a cousin of the famous Travelling Salesman problem, as we could interpret our investigation as trying to find the optimal route through the social network. Perhaps we could initialise many salesmen and let them evolve! Food for thought.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;em&gt;I hope you enjoyed what turned out to be a rather long article. All the code is on my &lt;a href=&quot;https://github.com/robertmartin8/RandomWalks/blob/master/social_seating.py&quot;&gt;GitHub&lt;/a&gt;, feel free to fork it and try out your own networks&lt;/em&gt;.&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>Conway's Game of Life in python</title>
   <link href="https://reasonabledeviations.com/2017/06/10/conway-python/"/>
   <updated>2017-06-10T00:00:00+00:00</updated>
   <id>https://reasonabledeviations.com/2017/06/10/conway-python</id>
   <content type="html">&lt;p&gt;In this short post, I explain how to implement Conway’s Game of Life in python, using numpy arrays and matplotlib animations. I emphasise intuitive code than performance, so it could be a useful starting point for somebody to understand the logic before implementing a more efficient version.
&lt;!--more--&gt;&lt;/p&gt;

&lt;h2 id=&quot;cellular-automata&quot;&gt;Cellular automata&lt;/h2&gt;

&lt;p&gt;A cellular automaton (pl. automata) consists of a grid of &lt;strong&gt;cells&lt;/strong&gt;; each cell has a state — either alive or dead. I will refer to this grid as the &lt;strong&gt;universe&lt;/strong&gt;, though this is non-standard terminology. The automaton then iterates forward in time, with the new state of each cell being decided by a set of rules.&lt;/p&gt;

&lt;p&gt;One of the most widely-studied cellular automata is the Game of Life, sometimes just called ‘Life’, which was discovered (or ‘invented’ if you prefer) by John Conway. It is so named because its rules can be interpreted as a very simplistic model of living cells.&lt;/p&gt;

&lt;p&gt;Firstly, let us define the the eight grid squares around a cell as its &lt;strong&gt;neighbours&lt;/strong&gt;. The rules of Life are then as follows:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1.&lt;/strong&gt; A living cell will survive into the next generation by default, unless:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;it has fewer than two live neighbours (&lt;em&gt;underpopulation&lt;/em&gt;).&lt;/li&gt;
  &lt;li&gt;it has more than three live neighbours (&lt;em&gt;overpopulation&lt;/em&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;2.&lt;/strong&gt; A dead cell will spring to life if it has exactly three live neighbours (&lt;em&gt;reproduction&lt;/em&gt;).&lt;/p&gt;

&lt;p&gt;Clearly, no cells can come to life unless there are already some living cells in the universe (Aquinas’ &lt;em&gt;primum movens&lt;/em&gt;?). Thus, we have to provide the universe with a &lt;strong&gt;seed&lt;/strong&gt;, i.e a set of initial living cells. The massive variety and complexity of the results, often starting from simple seeds, is what makes cellular automata so interesting.&lt;/p&gt;

&lt;h2 id=&quot;a-python-implementation&quot;&gt;A python implementation&lt;/h2&gt;

&lt;p&gt;This is a rough overview of our plan of attack:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Initialise an empty universe&lt;/li&gt;
  &lt;li&gt;Set the seed in the universe&lt;/li&gt;
  &lt;li&gt;Determine if a given cell survives to the next iteration, based on its neighbours&lt;/li&gt;
  &lt;li&gt;Iterate this survival function over all the cells in the universe&lt;/li&gt;
  &lt;li&gt;Iterate steps 3–4 for the desired number of generations&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;1-theuniverse&quot;&gt;1. The universe&lt;/h3&gt;

&lt;p&gt;I am using numpy arrays to represent the universe, as they are easy to initialise and manipulate. Let us start with a small universe consisting of a six-by-six grid. In our universe, each cell will either be a 1 (alive) or 0 (dead). For now, they are all dead, so we initialise with np.zeros&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;nn&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;n&quot;&gt;universe&lt;/span&gt; &lt;span class=&quot;o&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;zeros&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;mi&quot;&gt;6&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;h3 id=&quot;2-theseed&quot;&gt;2. The seed&lt;/h3&gt;

&lt;p&gt;We must now decide on the seed. I will choose a simple oscillator, known as the &lt;em&gt;beacon&lt;/em&gt;, which evolves as follows:&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/conway/beacon.png&quot; style=&quot;width:700px;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;If you are struggling to see how this works, simply count the number of living neighbours for each cell then apply the rules of Life as presented earlier.&lt;/p&gt;

&lt;p&gt;In python, we will set the seed like so:&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;beacon&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;1&lt;/span&gt;&lt;span class=&quot;p&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;mi&quot;&gt;0&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;p&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;mi&quot;&gt;1&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;mi&quot;&gt;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;mi&quot;&gt;0&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;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&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;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;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&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;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]]&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;universe&lt;/span&gt;&lt;span class=&quot;p&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;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;p&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;mi&quot;&gt;5&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;beacon&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;It is worth discussing at this point how we plan on visualising our universe. The easiest solution I have come across is to use matplotlib’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;imshow&lt;/code&gt;, which outputs a grid of colours corresponding to the value of every entry in an array. Using the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;binary&lt;/code&gt; colourmap, we have:&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;nn&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;n&quot;&gt;plt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;imshow&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;universe&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;s&quot;&gt;'binary'&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;n&quot;&gt;show&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;center&gt;
&lt;img src=&quot;/assets/images/conway/imshow_example.png&quot; style=&quot;width:350px;&quot; /&gt;
&lt;/center&gt;

&lt;h3 id=&quot;3-survival&quot;&gt;3. Survival&lt;/h3&gt;

&lt;p&gt;We now need a function which will do the following:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Take as arguments the &lt;em&gt;x&lt;/em&gt; and &lt;em&gt;y&lt;/em&gt; coordinates of a cell&lt;/li&gt;
  &lt;li&gt;Examine the neighbours of this cell&lt;/li&gt;
  &lt;li&gt;Decide whether the cell survives&lt;/li&gt;
  &lt;li&gt;Write this into a new universe&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The last point is rather subtle — we cannot update the cell in place, because this will affect the surrounding cells &lt;em&gt;in the same iteration&lt;/em&gt;. Instead, we need to compute the survival of every cell based on a snapshot of the current universe, then simultaneously write all of these new cells into our universe. A cheap shortcut is to write the new values into a copy of the universe called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;new_universe&lt;/code&gt;, while leaving &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;universe&lt;/code&gt; as a read-only. Then, later on, we can set the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;universe&lt;/code&gt; to be equal to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;new_universe&lt;/code&gt;.&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;c1&quot;&gt;# Create an identical copy of the universe, which will be the next generation.
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;new_universe&lt;/span&gt; &lt;span class=&quot;o&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;copy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;universe&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;survival&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;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&quot;&quot;&quot;
    :param x: x coordinate of the cell
    :param y: y coordinate of the cell
    &quot;&quot;&quot;&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# Find the number of living neighbours by taking
&lt;/span&gt;    &lt;span class=&quot;c1&quot;&gt;# the sum of the 8 surrounding grid squares
&lt;/span&gt;    &lt;span class=&quot;n&quot;&gt;num_neighbours&lt;/span&gt; &lt;span class=&quot;o&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;nb&quot;&gt;sum&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;universe&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;mi&quot;&gt;1&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;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y&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;n&quot;&gt;y&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&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;universe&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;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# If the cell is alive
&lt;/span&gt;    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;universe&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;y&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;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;num_neighbours&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;or&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;num_neighbours&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;new_universe&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;y&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;0&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# Otherwise, the cell is dead...
&lt;/span&gt;    &lt;span class=&quot;k&quot;&gt;elif&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;universe&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;y&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;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;# ... but it will rise again
&lt;/span&gt;        &lt;span class=&quot;c1&quot;&gt;# IF there are three living neighbours
&lt;/span&gt;        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;num_neighbours&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;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;new_universe&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;y&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;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;I know that the if-else statements can be simplified drastically, like such:&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;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;universe&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;y&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;ow&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;num_neighbours&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;new_universe&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;y&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;0&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;elif&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;num_neighbours&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;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;new_universe&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;y&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;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;But it is much more intuitive to keep the logic in its ‘expanded’ form, wherein one can clearly see the rules of Life.&lt;/p&gt;

&lt;h3 id=&quot;4-compute-one-full-generation&quot;&gt;4. Compute one full generation&lt;/h3&gt;
&lt;p&gt;This is a simple matter of applying our survival function to every cell in the universe, then setting universe to be equal to new_universe.&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;generation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# We are inside a function, so we must specify
&lt;/span&gt;    &lt;span class=&quot;c1&quot;&gt;# that we are referring to the global variable 'universe'.
&lt;/span&gt;    &lt;span class=&quot;c1&quot;&gt;# Otherwise python will think that we are trying to
&lt;/span&gt;    &lt;span class=&quot;c1&quot;&gt;# create a new variable also called 'universe'.
&lt;/span&gt;    &lt;span class=&quot;k&quot;&gt;global&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;universe&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# Simple loop over every possible xy coordinate.
&lt;/span&gt;    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;universe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;shape&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;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;j&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;universe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;shape&lt;/span&gt;&lt;span class=&quot;p&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;n&quot;&gt;survival&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;j&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# Set universe to be equal to new_universe.
&lt;/span&gt;    &lt;span class=&quot;n&quot;&gt;universe&lt;/span&gt; &lt;span class=&quot;o&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;copy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;new_universe&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;h3 id=&quot;5-iterate-andanimate&quot;&gt;5. Iterate and animate&lt;/h3&gt;

&lt;p&gt;There is no point iterating unless we can visualise the changes. To do so, we can simply use matplotlib’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;animation&lt;/code&gt; module (see some examples &lt;a href=&quot;https://matplotlib.org/2.0.0/examples/animation/index.html&quot;&gt;here&lt;/a&gt;).&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;nn&quot;&gt;matplotlib.animation&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;animation&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;fig&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;n&quot;&gt;figure&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Remove the axes for aesthetics
&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;n&quot;&gt;axis&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'off'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;ims&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;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;30&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# Add a snapshot of the universe, then move to the next generation
&lt;/span&gt;    &lt;span class=&quot;n&quot;&gt;ims&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;append&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;n&quot;&gt;imshow&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;universe&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;s&quot;&gt;'Purples'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),))&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;generation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Create the animation
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;im_ani&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;animation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ArtistAnimation&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;ims&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;interval&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;700&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;repeat_delay&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1000&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;blit&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;# Optional to save the animation
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;im_ani&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;save&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'beacon.gif'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;writer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;imagemagick&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;p&gt;Putting it all together, the output is:&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/conway/beacon.gif&quot; style=&quot;width:400px;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;Yes, I changed the colour to purple. Get over it.&lt;/p&gt;

&lt;h3 id=&quot;where-here-to-go-fromhere&quot;&gt;Where here to go from here&lt;/h3&gt;

&lt;p&gt;In principle, we are done. Now it is up to you to experiment with different seeds! But I can’t resist one more demonstration, using the &lt;em&gt;R-pentomino&lt;/em&gt; seed:&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/conway/Rpentomino.png&quot; style=&quot;width:300px;&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;Perhaps you’d expect its behaviour to be vaguely similar to that of the beacon. But the wonderful thing about Life is the dazzling complexity that can result from a simple seed. See for yourself:&lt;/p&gt;

&lt;center&gt;
&lt;img src=&quot;/assets/images/conway/rpentomino.gif&quot; style=&quot;width:400px;&quot; /&gt;
&lt;/center&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;em&gt;Check out my &lt;a href=&quot;https://github.com/robertmartin8/PyGameofLife&quot;&gt;PyGameofLife repository&lt;/a&gt; on GitHub, which includes a slightly more professional refactoring with a number of built-in seeds allowing a user to generate gifs from the command line. The R-pentomino animation above can be created with:&lt;/em&gt;&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;python life.py -seed r_pentomino -n 500 -interval 50
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Feel free to submit a pull request if you’d like to add more seeds or functionality.&lt;/em&gt;&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>The Cambridge Natural Sciences Interview</title>
   <link href="https://reasonabledeviations.com/2017/05/01/cambridge-natsci-interview/"/>
   <updated>2017-05-01T00:00:00+00:00</updated>
   <id>https://reasonabledeviations.com/2017/05/01/cambridge-natsci-interview</id>
   <content type="html">&lt;p&gt;In December 2015, just after finishing my IB exams, I went to Cambridge for my (Physical) Natural Sciences Interview. The interview has a reputation for being incredibly demanding and intense, and you’ve probably seen on the internet some of the ‘crazy’ questions that people get asked. Although it is definitely stressful, the Cambridge interview is almost purely academic (and quite reasonable). So without further ado, these are the questions I was asked, along with my thoughts at the time and the interviewer’s reactions. &lt;!--more--&gt;&lt;/p&gt;

&lt;h2 id=&quot;interview-1--physics-and-chemistry&quot;&gt;Interview 1 – physics and chemistry&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;In your Personal Statement, you mentioned that you had done an experiment to determine the Planck constant. Tell us a bit about this.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Prior to the interview I had thought about this a lot, because I knew they were going to ask me about it. So I just described the experiment I did and what I learnt from it. The interviewers seemed very impressed by this, and they didn’t ask anything more about it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What does &lt;em&gt;F = ma&lt;/em&gt; mean&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Of course it’s one of Newton’s laws, so with abounding confidence I said “that’s Newton’s first law!”. Something about that sounded weird, but the damage was done and I will never forgive myself for failing to identify Newton’s 2nd. I was mortified, but the interviewer chuckled and said not to worry about it. I recovered this by giving a half-decent explanation in terms of inertia, i.e reluctance to move. I think this is one of the setup questions to test your fundamental understanding before moving onto harder stuff.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Imagine we have a box, hanging by a light inextensible rope, in an elevator. The box weighs 5kg. The lift is accelerating upwards at 4$ms^{-2}$. What is the tension in the rope?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;We will now connect another box, via a light inextensible rope, to the bottom of the box in the previous question. What is the tension in this new piece of rope?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I was familiar with what happens when you put weighing scales in lifts, and this problem was similar. But I got quite confused doing the question, especially with the signs of acceleration, force etc. Frustrating, because I know that if it were an exam I’d be able to do it easily. I now see that the whole question would have easily (and impressively) been done by considering the principle of equivalence. I think I did eke out the correct answer in the end, but it wasn’t pretty.&lt;/p&gt;

&lt;p&gt;For the second question, as soon as the question was asked, I made an intuitive observation regarding the system, and it turned out that this is exactly what he wanted. I can’t remember exactly what I said, but I think it was something regarding how we can consider the bottom of the second box as the new ‘roof’ of the elevator, so to speak.&lt;/p&gt;

&lt;p&gt;At this point, the second interviewer (who I believe specialised in chemistry) took over. I was really not looking forward to this part, because on my personal statement I said that I had read &lt;em&gt;Why Chemical Reactions Happen&lt;/em&gt; – I did read it, but it was a lot harder than I expected it to be, and I wasn’t too sure that I could answer questions about it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tell me about hydrogen bonding&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I explained it in terms of electron density. When a hydrogen atom moves near an oxygen atom (for example), the high positive charge density of the oxygen draws toward it the hydrogen electron cloud, leaving the hydrogen atom with a partial positive charge. I think you have to be careful not to rattle off textbook definitions, you have to actually understand what you’re saying.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If I mix some lithium ions in water, what happens?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt; I said something about coordinate complexes. Although lithium isn’t a transition metal, it has a very high charge density, so I suggested that it could form some kind of ligands. I drew a little diagram with curly arrows going from the lone pairs on $OH$ or $H_2O$ to the lithium ion.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If I mix some aluminium ions in water, what happens?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This one threw me off, because I would have thought that aluminium behaved the same way as lithium. So when they asked me about aluminium after asking me about lithium, I figured something was up. I tried to make up some stuff about Lewis acid-base character (when I say ‘make up’, I mean that I brought up facts that I knew were true, but of questionable relevance).&lt;/p&gt;

&lt;p&gt;In the end, after some fidgeting, I decided to be honest. I said “I don’t see how it’s different to the lithium case”. She then asked the next question (very closely related).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is the aluminium-ligand bond stronger or weaker than the lithium-ligand bond?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I first stated the relevant features. The ionic radius, and the charge. Aluminium obviously has greater charge, but then on the other hand I thought it had a larger radius (I was wrong). I didn’t know how to compare these opposing factors. I reasoned that, because of Coulomb’s law, the radius would be more important (because it is an $r^2$), but she informed me that the charge was much more significant.&lt;/p&gt;

&lt;p&gt;In hindsight, I could have worked that out. Aluminium has 3/2 times the charge. For the ionic radius to be more significant, aluminium’s radius would have to be at least $\sqrt{3/2}$ (about 1.2) times greater than that of lithium.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What happens when $Al_2Cl_6$ reacts with water?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I think a description would have sufficed, but I wrote the chemical equations&lt;/p&gt;

\[Al_2Cl_6 + 6H_2O \to [Al(H_2O)_6]^{3+} + 6Cl^{-}\]

\[[Al(H_2O)_6]^{3+} \to [Al(H_2O)_5OH]^{2+} + H^+\]

&lt;p&gt;I also mentioned something about the pH decreasing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In the above equilibrium, how would I produce more of the metal ion&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I had no idea what I was being asked. I clarified once, but still didn’t get it. But I figured that it was something to do with Le Chatelier’s principle, so I talked about how adding a strong acid or strong base would affect the equilibrium. This seemed to satisfy them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Any questions?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I asked “what do you think is the next upcoming field in your subject?”. Physics guy said ‘energy’, chemistry person agreed. Obviously I asked this question because everyone told me that you have to ask something good at the end. I figured I had nothing to lose by asking.&lt;/p&gt;

&lt;p&gt;All in all, I felt that the first interview was disastrous, especially regarding chemistry, even though I wasn’t asked anything about &lt;em&gt;Why Chemical Reactions Happen&lt;/em&gt;.&lt;/p&gt;

&lt;h2 id=&quot;interview-2--physics-and-maths&quot;&gt;Interview 2 – physics and maths&lt;/h2&gt;

&lt;p&gt;I felt that I was more prepared for this interview, because it didn’t involve chemistry.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Have you heard of Felix Baumgartner?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Yes, he jumped off some balloon.  I was informed that he had jumped from 40,000m.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tell me about the forces involved in his jump&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Obviously, the force of gravity, and the force of air resistance. I said that I couldn’t find his equation of motion, because I didn’t know about the air resistance. We came back to this shortly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What value of &lt;em&gt;g&lt;/em&gt; are you using?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I perceived that the question was about the variation of &lt;em&gt;g&lt;/em&gt;. I wrote down $g=GM/R^2$. I knew that the radius of the earth was about 6400km, so I did a quick mental calculation to note the difference in order of magnitude between 40,000m and 6400km. Therefore, I concluded that the variation of &lt;em&gt;g&lt;/em&gt; was not relevant. He agreed, but asked me to quantify it.&lt;/p&gt;

&lt;p&gt;I replaced $R$ with $R+h$, ($R$ is the Earth’s radius and $h$ is the height above the surface) and expanded, but didn’t know how to proceed. I think the key point was that the $h^2$ term vanishes in comparison to the $R^2$ and the $2Rh$.&lt;/p&gt;

\[g = \frac{GM}{(R+h)^2} =\frac{GM}{R^2 + 2Rh + h^2} \approx \frac{GM}{R^2 + 2Rh}\]

&lt;p&gt;&lt;strong&gt;How does air pressure vary as a function of height?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This was quite a frustrating question. Everyone knows that there is an inverse relationship, but I had never stopped to consider the exact function.I said that I could work probably work it out if I knew the distribution for pressure or density.&lt;/p&gt;

&lt;p&gt;It turns out that it can be derived using the ideal gas equation to find the density as a function of height and $dP$, then substituting this into $P = \rho g h$ to get a differential equation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Have you heard of the Boltzmann distribution?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Yes, it is a distribution for the velocities of particles in an ideal gas. I don’t know the precise form of the equation, except that it has an $\exp(\frac 1 T)$ in it.&lt;/p&gt;

&lt;p&gt;This was a weird question, and seemed a bit out of place. But it related to the previous question. Since I didn’t know the form of the Boltzmann distribution, the guy just said “assume that the pressure halves every 5000m”. As he said this I wrote down $\rho = \rho_0 (\frac{1}{2})^{h/5000}$, and he seemed satisfied.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;With the above in mind, could Felix have broken the sound barrier?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I thought that he wanted me to use the pressure function from the previous question to do the differential equation. But when I started, the interviewer got a bit impatient and told me that the pressure thing was a separate part of the question. I think that was a bit unfair of him, but never mind.&lt;/p&gt;

&lt;p&gt;What they wanted was much more simple. I had already shown that changes in &lt;em&gt;g&lt;/em&gt; were pretty much irrelevant. Likewise, because of the exponential relationship of air pressure, air resistance was also negligible. So it reduces to a simple SUVAT equation. I did it mentally and told him the answer, and he was surprised that I got it correct.&lt;/p&gt;

&lt;p&gt;At this point, the second interviewer took over. I was thankful to switch interviewers (the first had been a bit grumpy), but I knew that the second interviewer was the author of my IB maths textbook, which is a bit of a daunting prospect.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Integrate $f(x) = \arcsin(x)$&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Standard trick, use integration by parts with $u=\arcsin(x)$ and $dv = 1$.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Differentiate $f(x) = \arcsin (\cos x)$&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I was very happy to get some calculus, because I thought it was my strong suit.&lt;/p&gt;

&lt;p&gt;I started off by saying that I would simplify the function, but this is actually a mistake because it can’t be simplified – I got $f(x)$ confused with something similar, $\cos(\arcsin x)$ (which &lt;em&gt;can&lt;/em&gt; be simplified). So I had to backtrack and just differentiated it normally.&lt;/p&gt;

&lt;p&gt;Easy application of the chain rule, so as usual I skipped a few steps (which I later regretted).&lt;/p&gt;

\[\frac{d}{dx} \arcsin (\cos x) = -\frac{\sin x}{\sqrt{1-\cos^2 x}} = -1\]

&lt;p&gt;(Do you spot the mistake here? I didn’t)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Find $\int_0^1 f(x)dx$&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I had absolutely no clue how to integrate it (at first). I started off by suggesting substitutions, but knew that these wouldn’t work.Luckily I had a mini ‘eureka’ moment – the derivative is constant!. This means that the $f(x)$ ALWAYS has the same negative gradient – it’s just a straight line.&lt;/p&gt;

&lt;p&gt;So I knew that $f(x)$ could be rewritten as $–x + c$. I didn’t know how to proceed at first, but the interviewer suggested “how might you find &lt;em&gt;c&lt;/em&gt;?” and I worked it out. $c = f(0) = \pi/2$. The integral is then just the area of the triangle. The interviewer seemed very pleased that I had managed to spot this method. However, he then smiled and asked the next question.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;That’s a nice method. But the answer is wrong. What did you miss?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;As soon as he said I missed a step, I knew exactly where my error was - I felt that I had messed up the square root of $(1 - \cos^2 x)$ in the derivative. He agreed, but after this it took me a while to figure out exactly what it was. I suggested that I had forgot a +/-, but it wasn’t that. We then had a really sad discussion (sad and embarrassing for me) about square roots. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is the square root of 16?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;+/- 4, but the sqrt sign technically means only the positive root, so 4.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is the square root of 4?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;2.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is the square root of $( -2)^2$?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I got really flustered, said -2, then quickly corrected to 2.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is the square root of $x^2$?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Thank goodness I realised what he was getting to, the answer is clearly $|x|$. I’m ashamed it took me so long. Having figured this out, I quickly realised that the function would look like a sawtooth, and said so.&lt;/p&gt;

&lt;p&gt;Overall, I was pleased that I figured out the integral, but very humbled by the square root discussion. Of course, I missed an obvious trick to instantly figure out the integral:&lt;/p&gt;

\[\int_0^1 \arcsin (\cos x)dx = \int_0^1 \arcsin (\sin(x + \pi/2))dx\]

&lt;p&gt;&lt;strong&gt;What is the factorial of a half?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I smiled and said $\sqrt{\pi/2}$. (I mentioned the Gamma function in my personal statement).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In your maths exploration, how did you show that the gamma function worked as a factorial?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Repeated integration by parts. I asked if he wanted me to do it, he said no and seemed to be content with this answer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is the Gamma function the only function that can model a factorial?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I said that it wasn’t: given any (finite) set of coordinates, there are an infinite number of functions that can model it (polynomials).&lt;/p&gt;

&lt;p&gt;However, I said that I had proved that the gamma function satisfies the recursion principle, and that $\Gamma(1)=0$. This means that it is at worst, a superset of the factorial. I also said that I was aware that the Gamma function had an extra property which meant that it was the only function that satisfactorily extended the factorial to non-integers, but I was honest and said I didn’t know exactly what it was.&lt;/p&gt;

&lt;p&gt;He told me that the name of the property was analytic continuation. Thanks mate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why do you want to study natural sciences?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is a question that everyone should prepare for, but I mucked it up.  In my personal statement, I had already talked about the philosophical aspects – I love to find simple explanations, reductionism etc. I hinted at that in this answer, but ended up saying “these are where my strengths lie”. The grumpy interviewer said “so you’re basically saying that you’re good at this subject”. I gave a cautious “yeah”&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How are you going to keep your academics up to scratch prior to entering?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I said that I had already planned which books I would study, mentioning the Feynman lectures. I think this was a smart choice, because I know that physicists and mathematicians &lt;em&gt;love&lt;/em&gt; Feynman. They were happy, and said that it was a good book.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Any questions? You don’t have to try to impress us with good questions, we’re genuinely asking if you have any questions.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I asked something administrative about UCAS.&lt;/p&gt;

&lt;p&gt;THE END&lt;/p&gt;

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

&lt;p&gt;I really thought that I had screwed it up afterwards, but everyone had told me that that’s how you’re &lt;em&gt;meant&lt;/em&gt; to feel. The interviewers obviously know more than you, and they want to probe you until you don’t know something, so it makes sense that you will feel out of your depth. That being said, even thinking about objectively I knew that I had made a poor showing: getting Newton’s laws mixed up, and getting destroyed by some simple square roots.&lt;/p&gt;

&lt;p&gt;However, I decided not to dwell on it and to enjoy my Christmas break: it had been a rough couple of months of IB and interview prep. In the end, I did get in, so I guess some of my better answers must have made up for the failures.&lt;/p&gt;

</content>
 </entry>
 

</feed>
