├── .gitignore ├── 1Bayes ├── a_CoinsInABag.ipynb ├── b_CosmologicalModel.ipynb ├── figs │ ├── bayesian_updating.png │ ├── cosmological_posterior.png │ ├── cosmological_posterior_cf.png │ ├── flat_universe.png │ ├── guassian_prior.png │ ├── more_data.png │ ├── non_uniform.png │ ├── pmf_twocoins.png │ └── supernova_data.png └── supernova_data.csv ├── 2MCMC ├── a_MonteCarloPi.ipynb ├── b_IsingModel.ipynb └── figs │ ├── finite_size_scaling.png │ ├── ising_model.png │ ├── magnetisation.png │ ├── phase_transition.png │ └── pi_estimate.gif ├── 3GameTheory ├── a_RockPaperScissors.ipynb ├── b_InterviewGame.ipynb └── figs │ ├── numbers_game_nash.png │ └── rps_converging.png └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .ipynb_checkpoints 2 | */.ipynb_checkpoints/* 3 | .idea 4 | .DS_Store 5 | */.DS_Store/* 6 | -------------------------------------------------------------------------------- /1Bayes/a_CoinsInABag.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": { 6 | "collapsed": false, 7 | "editable": true, 8 | "jupyter": { 9 | "outputs_hidden": false 10 | }, 11 | "pycharm": { 12 | "is_executing": true 13 | }, 14 | "slideshow": { 15 | "slide_type": "" 16 | }, 17 | "tags": [] 18 | }, 19 | "source": [ 20 | "# Coins in a bag: Bayesian updating/inference\n", 21 | "\n", 22 | "### Aims of this notebook:\n", 23 | "1. Write out the mathematics for conditional probability and then elevating this to a Bayesian view of things\n", 24 | "2. Add in the likelihood to the `CoinFlip` class (is there a better way to define `nCr` (i.e. use an existing `scipy` function)\n", 25 | "3. Add in fucntionality to update multiple times and show that this gives the same results as before\n", 26 | "4. Show that we can use a non-uniform prior to show a belief and show how convergence time changes when we are sure and when we are unsure\n", 27 | "5. look at a cosomological model (or any other two parameter system)\n", 28 | "\n", 29 | "### Notebook structure:\n", 30 | "1. Conditional probabilities and Bayes' rule\n", 31 | "2. Elevating to probability distributions\n", 32 | "3. Generalising to a 100-coin bag\n", 33 | "4. Bayesian updating\n", 34 | "5. Non-uniform priors" 35 | ] 36 | }, 37 | { 38 | "cell_type": "code", 39 | "execution_count": 3, 40 | "metadata": { 41 | "editable": true, 42 | "slideshow": { 43 | "slide_type": "" 44 | }, 45 | "tags": [] 46 | }, 47 | "outputs": [], 48 | "source": [ 49 | "import numpy as np\n", 50 | "import math\n", 51 | "import matplotlib.pyplot as plt" 52 | ] 53 | }, 54 | { 55 | "cell_type": "markdown", 56 | "metadata": { 57 | "editable": true, 58 | "slideshow": { 59 | "slide_type": "" 60 | }, 61 | "tags": [] 62 | }, 63 | "source": [ 64 | "## 1. Conditional proabilities and Bayes' rule\n", 65 | "\n", 66 | "For this notebook, we assume that you are familiar with Bayes' rule for [conditional probabilities](https://en.wikipedia.org/wiki/Conditional_probability):\n", 67 | "\n", 68 | "$$P(A|B) = \\frac{P(B|A) P(A)}{P(B)} $$\n", 69 | "\n", 70 | "Lets apply this equation to an example where we have a bag with two coins, one is a fair coin and the other has a heads on both sides. We pick one coin out of the bag at random and flip it twice and get HH. Using conditional probabilities and Bayes' rule we can update our probabilities of which coin we might have (which before the flips is 50/50). Therefore Bayes' rule becomes:\n", 71 | "\n", 72 | "$$P(B|HH) = \\frac{P(HH|B) P(B)}{P(HH)} $$\n", 73 | "\n", 74 | "$P(HH|B) = 1$ as the doubled-headed coin can only flips heads\n", 75 | "\n", 76 | "$P(B) = 0.5$ before flipping we pick one of two coins with equal probability\n", 77 | "\n", 78 | "$P(HH) = 0.5 \\cdot 1^2 + 0.5 \\cdot 0.5^2 = 0.375$\n", 79 | "\n", 80 | "$$P(B|HH) = \\frac{P(HH|B) P(B)}{P(HH)} = \\frac{1 \\cdot 0.5}{0.625} = 0.8$$\n", 81 | "\n", 82 | "" 83 | ] 84 | }, 85 | { 86 | "cell_type": "markdown", 87 | "metadata": { 88 | "editable": true, 89 | "slideshow": { 90 | "slide_type": "" 91 | }, 92 | "tags": [] 93 | }, 94 | "source": [ 95 | "## 2. Elevating to probability distributions\n", 96 | "\n", 97 | "We can now try and look at the same problem through the lens of probability distributions, to do this we need to define the prior, likelihood and posterior. \n", 98 | "\n", 99 | "### Prior, $\\pi (\\theta)$\n", 100 | "The prior distribution is a probability distribution of our prior beliefs of the parameters of the system before we've seen any data. \n", 101 | "\n", 102 | "In the case of our two coins in a bag, the parameter of the model $\\theta$ is the *bias* of the coin we picked, $p$. Before actually flipping the coin, we believe there is an equal chance that we picked a coin with $p=0.5$ or $p=1$. This is known as a *uniform prior* as the probability distribution over the values of parameters in the model is uniform. We will look later at non-uniform priors and when to use them.\n", 103 | "\n", 104 | "### Likelihood, $\\mathcal L(D | \\theta)$\n", 105 | "The likelihood describes how likely we are to observe some data, given the parameters of the model.\n", 106 | "\n", 107 | "In our case where we are flipping coins we notice that each flip has just two outcomes and therefore many flips will follow a [binomial distribution](#), having the formula for this binomial distribution becomes necessary for more non-trivial cases (i.e. $>2$ flips).\n", 108 | "\n", 109 | "### Posterior, $P(\\theta | D)$\n", 110 | "The posterior is the probability distribution over the model paramters which we get after updating the prior using our data\n", 111 | "\n", 112 | "\n", 113 | "This gives us an analogous expression for Bayes' rule for distributions:\n", 114 | "\n", 115 | "$$P(\\theta | D) = \\frac{\\mathcal L(D | \\theta) \\cdot \\pi(\\theta) }{ P(D)}$$\n", 116 | "\n", 117 | "N.B all distributions in our case theta domain $[0,1]$ and therefore we can multiply two distributions together by multiplying elementwise (this how `numpy` works out of the box). Distributions are vectors and not continuous" 118 | ] 119 | }, 120 | { 121 | "cell_type": "markdown", 122 | "metadata": { 123 | "editable": true, 124 | "slideshow": { 125 | "slide_type": "" 126 | }, 127 | "tags": [] 128 | }, 129 | "source": [ 130 | "### Exercise: Constructing the Likelihood, $\\mathcal L(D | \\theta)$\n", 131 | "\n", 132 | "The likelihood, $\\mathcal L (D_N | p)$ for a coin with bias $p$ is the probability of seeing a given outcome of $N$ coin flips, this follows a binomial distribution. \n", 133 | "\n", 134 | "Fill in your code for the function definition below using the type hints of the inputs and outputs along with the form of `data` as given in the previous cell" 135 | ] 136 | }, 137 | { 138 | "cell_type": "code", 139 | "execution_count": 76, 140 | "metadata": { 141 | "editable": true, 142 | "slideshow": { 143 | "slide_type": "" 144 | }, 145 | "tags": [] 146 | }, 147 | "outputs": [], 148 | "source": [ 149 | "def likelihood(data: np.array(bool), p: np.array(float)) -> np.array(float):\n", 150 | " '''\n", 151 | " YOUR CODE HERE!\n", 152 | "\n", 153 | " Hint: use `math.comb()`\n", 154 | " '''" 155 | ] 156 | }, 157 | { 158 | "cell_type": "code", 159 | "execution_count": 60, 160 | "metadata": { 161 | "editable": true, 162 | "slideshow": { 163 | "slide_type": "" 164 | }, 165 | "tags": [] 166 | }, 167 | "outputs": [], 168 | "source": [ 169 | "assert likelihood(data=[True, True], p=0.5) == 0.25\n", 170 | "assert likelihood(data=[True, True, False], p=0.5) == 3/8\n", 171 | "assert likelihood(data=[True], p=0) == 0" 172 | ] 173 | }, 174 | { 175 | "cell_type": "markdown", 176 | "metadata": { 177 | "editable": true, 178 | "slideshow": { 179 | "slide_type": "" 180 | }, 181 | "tags": [] 182 | }, 183 | "source": [ 184 | "### Exercise: Constructing the Posterior, $P( \\theta | D)$\n", 185 | "\n", 186 | "Now use the your likelihood function in Bayes' rule to calculate the posterior. Note how we didn't define $P(D)$ earlier, this is becuase we use it as a normalising factor, though this term is known as the *model evidence* which has many subtleties that go beyond the scope of this tutorial. Therefore, for now, we always want our posteriors (and priors) to be normalised ($\\sum_i P(p_i | D) = 1$).\n", 187 | "\n", 188 | "When generating a posterior for a given coin flip data, we can run a couple of sense checks on the results. If we see a single TAILS in the data, then our probability of having the $p=1$ coin is 0. We should also check that the probabilities calculated by hand in (Ex.A) agree with the program.\n", 189 | "\n", 190 | "Fill out the function definitions below using the again using type hints\n", 191 | "\n", 192 | "if you construct a function using existing vectorised `numpy` functions, and the operations within that function are element-wise, your new function will also be vectorised (this is the likelihood)" 193 | ] 194 | }, 195 | { 196 | "cell_type": "code", 197 | "execution_count": 77, 198 | "metadata": { 199 | "editable": true, 200 | "slideshow": { 201 | "slide_type": "" 202 | }, 203 | "tags": [] 204 | }, 205 | "outputs": [], 206 | "source": [ 207 | "def posterior(data: np.array(bool), theta: float, prior: np.array(float)) -> np.array(float):\n", 208 | " '''\n", 209 | " YOUR CODE HERE!\n", 210 | "\n", 211 | " Hint: \n", 212 | " this is possible with `numpy` vectorisation (i.e. no loops) \n", 213 | "\n", 214 | " as `likelihood()` acts on single elements it is also vectorised\n", 215 | "\n", 216 | " if vectorisation is unfamiliar try with loops first\n", 217 | " '''" 218 | ] 219 | }, 220 | { 221 | "cell_type": "code", 222 | "execution_count": 62, 223 | "metadata": {}, 224 | "outputs": [], 225 | "source": [ 226 | "assert np.allclose(\n", 227 | " posterior(data=np.array([True, True]), theta=np.array([0.5,1]), prior=np.array([0.5,0.5])),\n", 228 | " np.array([0.2,0.8])\n", 229 | ")\n", 230 | "\n", 231 | "assert np.allclose(\n", 232 | " posterior(data=np.array([True, False]), theta=np.array([0,0.5, 1]), prior=np.array([1/3,1/3,1/3])), \n", 233 | " np.array([0,1,0])\n", 234 | ")\n", 235 | "\n", 236 | "assert np.allclose(\n", 237 | " posterior(data=np.array([True, True]), theta=np.array([0,0.25,0.5,0.75,1]), prior=np.array([0.2,0.2,0.2,0.2,0.2])), \n", 238 | " np.array([0. , 0.03333333, 0.13333333, 0.3 , 0.53333333])\n", 239 | ")" 240 | ] 241 | }, 242 | { 243 | "cell_type": "code", 244 | "execution_count": 63, 245 | "metadata": { 246 | "editable": true, 247 | "slideshow": { 248 | "slide_type": "" 249 | }, 250 | "tags": [] 251 | }, 252 | "outputs": [], 253 | "source": [ 254 | "# Intuition building w/ self generated data \n", 255 | "\n", 256 | "data = np.array([\n", 257 | " # YOUR SEQUENCE\n", 258 | "])\n", 259 | "\n", 260 | "# unit tests here" 261 | ] 262 | }, 263 | { 264 | "cell_type": "markdown", 265 | "metadata": { 266 | "editable": true, 267 | "slideshow": { 268 | "slide_type": "" 269 | }, 270 | "tags": [] 271 | }, 272 | "source": [ 273 | "## 3. Generalising for an 100-coin bag\n", 274 | "\n", 275 | "Using probability distributions quickly becomes a far better framework than the conditional probability case when we consider using many coins in a bag. For example, consider $100$ coins with biases, $ p = [0.00,0.01,0.02,\\cdots,0.99]$. Our functions above work with no modifications whereas the conditional case would require a lot of fiddly algebra. \n", 276 | "\n", 277 | "Now lets pick one coin from the bag at random and flip it 10 times. As we've picked the coin randomly our prior, $\\pi(p)$ is uniform over $p \\in [0,1]$. Now we flip the coin $N=10$ times and realise an outcome of H and T. For examples, we may get 7H 3T which in the order given by: \n", 278 | "\n", 279 | "```\n", 280 | "data = [True, False, True, True, True, False, True, False, True, True]\n", 281 | "```\n", 282 | "\n", 283 | "We use this data to calculate a posterior distribution. Intuitively, we expect the likelihood to be peaked at $p=0.7$, this peak will propagate into the posterior as we have a uniform prior. \n", 284 | "\n", 285 | "Intuitively, we expect the posterior to be peaked at $p=0.7$ and be zero for $p=0$ and $p=1$. We have used this sequence of 10 flips to inform us that the coin we picked from the bag likely has some bias towards heads." 286 | ] 287 | }, 288 | { 289 | "cell_type": "code", 290 | "execution_count": 78, 291 | "metadata": { 292 | "editable": true, 293 | "slideshow": { 294 | "slide_type": "" 295 | }, 296 | "tags": [] 297 | }, 298 | "outputs": [ 299 | { 300 | "data": { 301 | "text/plain": [ 302 | "'\\nDefine your coin bias parameters and prior vectors\\n'" 303 | ] 304 | }, 305 | "execution_count": 78, 306 | "metadata": {}, 307 | "output_type": "execute_result" 308 | } 309 | ], 310 | "source": [ 311 | "'''\n", 312 | "Define your coin bias parameters and prior vectors\n", 313 | "'''" 314 | ] 315 | }, 316 | { 317 | "cell_type": "markdown", 318 | "metadata": { 319 | "editable": true, 320 | "slideshow": { 321 | "slide_type": "" 322 | }, 323 | "tags": [] 324 | }, 325 | "source": [ 326 | "### Exercise: Creating data and flipping coins\n", 327 | "\n", 328 | "First, we need a way to create random data systematically to test our functions. This should produce a `numpy` array of boolean values, again, with `True` for H and `False` for T. \n", 329 | "\n", 330 | "Then you should plot the posterior assuming we picked a coin with, say `true_p = 0.2`, and see that the posterior will form a peak around this value which gets sharper as we increase this number of flips.\n", 331 | "\n", 332 | "The sharpness of the peak reflects our confidence. " 333 | ] 334 | }, 335 | { 336 | "cell_type": "code", 337 | "execution_count": 79, 338 | "metadata": { 339 | "editable": true, 340 | "slideshow": { 341 | "slide_type": "" 342 | }, 343 | "tags": [] 344 | }, 345 | "outputs": [], 346 | "source": [ 347 | "def create_data(true_p: float, N_flips: int) -> np.array(bool):\n", 348 | " '''\n", 349 | " YOUR CODE HERE!\n", 350 | "\n", 351 | " Hint: \n", 352 | " `np.random.random()`\n", 353 | " '''" 354 | ] 355 | }, 356 | { 357 | "cell_type": "code", 358 | "execution_count": 66, 359 | "metadata": { 360 | "editable": true, 361 | "slideshow": { 362 | "slide_type": "" 363 | }, 364 | "tags": [] 365 | }, 366 | "outputs": [], 367 | "source": [ 368 | "# mean and variance assertions? \n", 369 | "\n", 370 | "true_p = 0.2\n", 371 | "N_flips = 100000\n", 372 | "\n", 373 | "assert np.isclose(\n", 374 | " create_data(true_p=true_p, N_flips=N_flips).mean(), \n", 375 | " true_p,\n", 376 | " rtol=1e-2\n", 377 | ")\n", 378 | "\n", 379 | "# 3 std devs\n", 380 | "assert create_data(true_p=true_p, N_flips=N_flips).var() < 3 * true_p * (1 - true_p)" 381 | ] 382 | }, 383 | { 384 | "cell_type": "markdown", 385 | "metadata": {}, 386 | "source": [ 387 | "## 4. Bayesian updating\n", 388 | "\n", 389 | "In this section we explore Bayesian updating. The idea is to use the calculated posterior for some inital observed data, and then set this as our new prior when awaiting for further data. In this way, we use our initial observations to update our expecations for future data, assuming we are still sampling from the same distribution (i.e. flipping same coin with `true_p`).\n", 390 | "\n", 391 | "Assuming we start with a uniform initial prior, the updating process for updating data in batches $(D_1, D_2, \\cdots)$ looks schematically as\n", 392 | "$$ \\pi_0(\\theta) = \\text{Uniform}(0,1) $$\n", 393 | "$$\\pi_1(\\theta) \\leftarrow P (\\theta | D_1)$$\n", 394 | "$$\\pi_2(\\theta) \\leftarrow P (\\theta | D_2)$$\n", 395 | "\n", 396 | "We can explore this updating process by writing a function using `create_data()` and then splitting this in multiple sets of data i.e. a dataset. \n", 397 | "\n", 398 | "e.g. `data = [True, False, False, True] -> dataset = [[True, False], [False, True]]`" 399 | ] 400 | }, 401 | { 402 | "cell_type": "code", 403 | "execution_count": 80, 404 | "metadata": {}, 405 | "outputs": [], 406 | "source": [ 407 | "def create_dataset(true_p: float, N_flips: int, M_sets: int) -> list[np.array(bool)]:\n", 408 | " '''\n", 409 | " YOUR CODE HERE!\n", 410 | "\n", 411 | " maybe we call create_data in here? \n", 412 | " '''" 413 | ] 414 | }, 415 | { 416 | "cell_type": "markdown", 417 | "metadata": {}, 418 | "source": [ 419 | "Next, we want to see what the posterior looks like after every batch of new data, using our new prior (the calculated posterior after the old set of data). \n", 420 | "\n", 421 | "To neatly package this into one function, we pass the dataset as a list of $M$ sets of data, and return out $M$ posteriors: $P_1(p | D_1), P_2(p | D_1 \\cup D_2), \\cdots, P_M(p | D) $, where we are continuously updating the prior each time.\n" 422 | ] 423 | }, 424 | { 425 | "cell_type": "code", 426 | "execution_count": 81, 427 | "metadata": {}, 428 | "outputs": [], 429 | "source": [ 430 | "def bayes_update(dataset: list[np.array(bool)], theta: np.array(float), prior: np.array(float)) -> list[np.array(float)]:\n", 431 | " '''\n", 432 | " YOUR CODE HERE!\n", 433 | " '''" 434 | ] 435 | }, 436 | { 437 | "cell_type": "markdown", 438 | "metadata": {}, 439 | "source": [ 440 | "Let's now plot a graph of all the posteriors as the data streams in. Your graph should look something like this\n", 441 | "\n", 442 | "\n", 443 | "\n", 444 | "We also expect that repeatedly updating the posterior as new data comes in should still give the same final posterior as if we were to just perform a single inference on the whole dataset from the beginning.\n", 445 | "$$P_M(p | D_1 \\cup D_2 \\cdots \\cup D_M) = P(p | D)$$\n", 446 | "And we should verify this is the case for our example" 447 | ] 448 | }, 449 | { 450 | "cell_type": "code", 451 | "execution_count": 72, 452 | "metadata": {}, 453 | "outputs": [], 454 | "source": [ 455 | "dataset = create_dataset(true_p=0.2, N_flips=100, M_sets=5)\n", 456 | "data = np.hstack(dataset)\n", 457 | "\n", 458 | "assert np.allclose(\n", 459 | " posterior(data, p, prior).astype('float'),\n", 460 | " bayes_update(dataset, p, prior)[-1]\n", 461 | ")" 462 | ] 463 | }, 464 | { 465 | "cell_type": "markdown", 466 | "metadata": {}, 467 | "source": [ 468 | "## 5. Piecewise uniform priors\n", 469 | "\n", 470 | "Having developed a full workflow for tackling problems with bayesian inference let's explore non-uniform priors i.e. before any data is seen we have an opinion on what the bias may be. \n", 471 | "\n", 472 | "Consider the case where Bob, hands you a coin and says *'this coin is biased towards tails, but remember I lie x% of the time'* (i.e. $0 < p < 0.5$ x% of the time and $0.5
1 \\\\\n",
62 | "x, &\\Omega_{\\Lambda} + \\Omega_M = 1\\\\\n",
63 | "\\sinh(x), &\\Omega_{\\Lambda} + \\Omega_M < 1\n",
64 | "\\end{cases}\n",
65 | "$$\n",
66 | "Now these expressions looks very complicated at first glance, and they are. We dont fully understand them either. However, one of the skills that is important in quant finance is the ability to implement a model that you might not have time or expertise to fully understand. It is important to be able to abstract away the meaning behind equations and continue from there in a pragmatic workplace. \n",
67 | "\n",
68 | "The above expressions are really just some basic math functions, a piecewise function, and an integral. Implementing this in python shouldn't be too difficult. Take care to make sure all the units are correct and you dont forget to divide by $10\\text{pc}$ in your luminosity distance."
69 | ]
70 | },
71 | {
72 | "cell_type": "markdown",
73 | "id": "6580ddd3",
74 | "metadata": {
75 | "collapsed": false,
76 | "jupyter": {
77 | "outputs_hidden": false
78 | }
79 | },
80 | "source": [
81 | "\n",
82 | "*Supernova brightness data with two cosmological fits overlayed*\n",
83 | "\n",
84 | "Here is the raw supernova brightness data and two fits for different cosmological parameters. We can see how different cosmologies produce a better or worse fit."
85 | ]
86 | },
87 | {
88 | "cell_type": "markdown",
89 | "id": "99d6dfbd-8bff-40ef-81ea-525c6bf322f1",
90 | "metadata": {},
91 | "source": [
92 | "# Likelihood Function\n",
93 | "\n",
94 | "Now we have an expression for $m(z ; \\Omega_{\\Lambda}, \\Omega_M)$ we use the supernova dataset from [Astier et al., 2005.](https://www.aanda.org/articles/aa/pdf/2006/07/aa4185-05.pdf) that we have provided in `supernova_data.csv` along with a likelihood function to run a Bayesian analysis. \n",
95 | "\n",
96 | "The idea is to assume a value for $(\\Omega_{\\Lambda}, \\Omega_M)$ and then calculate the $\\chi^2$ of the fit $m(z)$ with our data $D$. The larger the $\\chi^2$, the worse the fit is and therefore the lower the likelihood. We can express this by treating the $\\chi^2$ value as a boltzmann factor and say our likelihood is proportional to\n",
97 | "$$\n",
98 | "\\mathcal{L}(\\Omega_{\\Lambda}, \\Omega_M) = e^{-\\chi^{2} (\\Omega_{\\Lambda}, \\Omega_M)}\n",
99 | "$$\n",
100 | "\n",
101 | "The $\\chi^2$ is simply the sum of all residuals\n",
102 | "$$\n",
103 | "\\chi^2(\\Omega_{\\Lambda}, \\Omega_M) = \\sum_i \\left( m_{\\text{data}}(z) - m_{\\text{model}}(z ; \\Omega_{\\Lambda}, \\Omega_M) \\right)^2\n",
104 | "$$"
105 | ]
106 | },
107 | {
108 | "cell_type": "markdown",
109 | "id": "53653123-42e8-4b2b-8667-fb408b64e432",
110 | "metadata": {},
111 | "source": [
112 | "Now you have all the maths required to run a 2-dimensional Bayesian inference on a simple cosmological model. You should be able to calculate and plot a posterior distribution which shows the most likely values for our universes dark matter and dark energy densities.\n",
113 | "\n",
114 | ""
115 | ]
116 | },
117 | {
118 | "cell_type": "code",
119 | "execution_count": 4,
120 | "id": "73df1c9a-c4bc-4f8f-8ef1-99bab932d948",
121 | "metadata": {},
122 | "outputs": [],
123 | "source": [
124 | "import numpy as np\n",
125 | "import pandas as pd\n",
126 | "import matplotlib.pyplot as plt\n",
127 | "from matplotlib import cm\n",
128 | "import math\n",
129 | "import scipy\n",
130 | "\n",
131 | "c = 299792 # speed of light (km/s)\n",
132 | "H0 = 70 / 1000000 # hubble constant (km/s/pc)\n",
133 | "\n",
134 | "data = pd.read_csv('supernova_data.csv')\n",
135 | "data.sort_values(by = ['z'], inplace=True)"
136 | ]
137 | },
138 | {
139 | "cell_type": "markdown",
140 | "id": "10da8f24-177b-410a-b58e-c58ca930bf6c",
141 | "metadata": {},
142 | "source": [
143 | "The above graph shows the 2D posterior distribution over the parameters $\\Omega_M$ and $\\Omega_{\\Lambda}$. It is cool to see how it is similar to the graph in the real scientific paper. A value of $\\Omega_{\\Lambda} > 0$ means that the universe is accelerating, which looks pretty likely from our result!\n",
144 | "\n",
145 | "We can now impose the additional prior belief that the universe is flat, which means that $\\Omega_{\\Lambda} + \\Omega_M = 1$, shown by the black line. With this prior we can plot the posterior distribution along this line and find the most likely parameter values: $\\Omega_{\\Lambda} = 0.788, \\Omega_M = 0.222$. There are many further statistical tests that can be done to investigate the uncertainty distributions of these parameters, but we'll leave that up to you.\n",
146 | "\n",
147 | ""
148 | ]
149 | }
150 | ],
151 | "metadata": {
152 | "kernelspec": {
153 | "display_name": "Python 3 (ipykernel)",
154 | "language": "python",
155 | "name": "python3"
156 | },
157 | "language_info": {
158 | "codemirror_mode": {
159 | "name": "ipython",
160 | "version": 3
161 | },
162 | "file_extension": ".py",
163 | "mimetype": "text/x-python",
164 | "name": "python",
165 | "nbconvert_exporter": "python",
166 | "pygments_lexer": "ipython3",
167 | "version": "3.9.6"
168 | }
169 | },
170 | "nbformat": 4,
171 | "nbformat_minor": 5
172 | }
173 |
--------------------------------------------------------------------------------
/1Bayes/figs/bayesian_updating.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BorisDeletic/CompStatsForQuant/14102339e7eba0b810287ad822824ae9578769f3/1Bayes/figs/bayesian_updating.png
--------------------------------------------------------------------------------
/1Bayes/figs/cosmological_posterior.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BorisDeletic/CompStatsForQuant/14102339e7eba0b810287ad822824ae9578769f3/1Bayes/figs/cosmological_posterior.png
--------------------------------------------------------------------------------
/1Bayes/figs/cosmological_posterior_cf.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BorisDeletic/CompStatsForQuant/14102339e7eba0b810287ad822824ae9578769f3/1Bayes/figs/cosmological_posterior_cf.png
--------------------------------------------------------------------------------
/1Bayes/figs/flat_universe.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BorisDeletic/CompStatsForQuant/14102339e7eba0b810287ad822824ae9578769f3/1Bayes/figs/flat_universe.png
--------------------------------------------------------------------------------
/1Bayes/figs/guassian_prior.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BorisDeletic/CompStatsForQuant/14102339e7eba0b810287ad822824ae9578769f3/1Bayes/figs/guassian_prior.png
--------------------------------------------------------------------------------
/1Bayes/figs/more_data.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BorisDeletic/CompStatsForQuant/14102339e7eba0b810287ad822824ae9578769f3/1Bayes/figs/more_data.png
--------------------------------------------------------------------------------
/1Bayes/figs/non_uniform.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BorisDeletic/CompStatsForQuant/14102339e7eba0b810287ad822824ae9578769f3/1Bayes/figs/non_uniform.png
--------------------------------------------------------------------------------
/1Bayes/figs/pmf_twocoins.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BorisDeletic/CompStatsForQuant/14102339e7eba0b810287ad822824ae9578769f3/1Bayes/figs/pmf_twocoins.png
--------------------------------------------------------------------------------
/1Bayes/figs/supernova_data.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BorisDeletic/CompStatsForQuant/14102339e7eba0b810287ad822824ae9578769f3/1Bayes/figs/supernova_data.png
--------------------------------------------------------------------------------
/1Bayes/supernova_data.csv:
--------------------------------------------------------------------------------
1 | name,z,mB,mB_raw
2 | 1990af,0.050,36.632,17.723
3 | 1990O,0.031,35.532,16.196
4 | 1992ae,0.075,37.642,18.392
5 | 1992ag,0.026,35.353,16.241
6 | 1992aq,0.101,38.437,19.299
7 | 1992bc,0.020,34.494,15.086
8 | 1992bh,0.045,36.728,17.592
9 | 1992bl,0.043,36.276,17.275
10 | 1992bo,0.018,34.576,15.753
11 | 1992bp,0.079,37.465,18.281
12 | 1992br,0.088,38.121,19.398
13 | 1992bs,0.063,37.540,18.177
14 | 1992P,0.026,35.565,16.037
15 | 1993ag,0.050,36.827,17.799
16 | 1993B,0.071,37.604,18.377
17 | 1993H,0.025,35.192,16.735
18 | 1993O,0.053,36.794,17.614
19 | 1994M,0.024,35.228,16.205
20 | 1994S,0.016,34.071,14.760
21 | 1995ac,0.049,36.383,17.026
22 | 1995bd,0.016,34.083,15.246
23 | 1996ab,0.125,38.885,19.525
24 | 1996bl,0.035,35.837,16.611
25 | 1996bo,0.016,34.405,15.816
26 | 1996bv,0.017,34.319,15.380
27 | 1996C,0.030,35.822,16.636
28 | 1997dg,0.030,35.994,16.821
29 | 1997Y,0.017,34.452,15.284
30 | 1998ab,0.028,35.150,16.048
31 | 1998dx,0.054,36.606,17.660
32 | 1998eg,0.024,35.250,16.089
33 | 1998V,0.017,34.216,15.094
34 | 1999aw,0.039,36.284,16.732
35 | 1999cc,0.032,35.789,16.791
36 | 1999ek,0.018,34.489,15.584
37 | 1999gp,0.026,35.342,16.005
38 | 2000ca,0.025,34.931,15.510
39 | 2000cf,0.036,36.113,17.091
40 | 2000cn,0.023,35.146,16.544
41 | 2000dk,0.016,34.129,15.323
42 | 2000fa,0.022,34.941,15.832
43 | 2001ba,0.031,35.558,16.182
44 | 2001cn,0.015,34.118,15.271
45 | 2001cz,0.017,34.162,15.035
46 | SNLS-03D1au,0.504,42.429,22.978
47 | SNLS-03D1aw,0.582,42.881,23.599
48 | SNLS-03D1ax,0.496,42.180,22.957
49 | SNLS-03D1bp,0.346,41.367,22.465
50 | SNLS-03D1cm,0.870,44.095,24.469
51 | SNLS-03D1co,0.679,43.398,24.094
52 | SNLS-03D1ew,0.868,43.871,24.359
53 | SNLS-03D1fc,0.331,40.946,21.800
54 | SNLS-03D1fl,0.688,43.046,23.629
55 | SNLS-03D1fq,0.800,43.490,24.519
56 | SNLS-03D1gt,0.548,42.825,24.119
57 | SNLS-03D3af,0.532,42.592,23.470
58 | SNLS-03D3aw,0.449,41.866,22.552
59 | SNLS-03D3ay,0.371,41.488,22.201
60 | SNLS-03D3ba,0.291,40.999,22.049
61 | SNLS-03D3bh,0.249,40.571,21.132
62 | SNLS-03D3cc,0.463,42.089,22.558
63 | SNLS-03D3cd,0.461,42.031,22.562
64 | SNLS-03D4ag,0.285,40.731,21.237
65 | SNLS-03D4at,0.633,43.133,23.746
66 | SNLS-03D4aud,0.468,42.708,23.856
67 | SNLS-03D4bcd,0.572,43.521,24.596
68 | SNLS-03D4cn,0.818,43.532,24.652
69 | SNLS-03D4cx,0.949,43.507,24.504
70 | SNLS-03D4cy,0.927,44.553,24.718
71 | SNLS-03D4cz,0.695,43.023,24.019
72 | SNLS-03D4dh,0.627,42.746,23.389
73 | SNLS-03D4di,0.905,43.708,24.288
74 | SNLS-03D4dy,0.604,42.515,23.313
75 | SNLS-03D4fd,0.791,43.353,24.212
76 | SNLS-03D4gf,0.581,42.761,23.351
77 | SNLS-03D4gg,0.592,42.562,23.403
78 | SNLS-03D4gl,0.571,42.465,23.269
79 | SNLS-04D1ag,0.557,42.511,23.003
80 | SNLS-04D1aj,0.721,43.209,23.901
81 | SNLS-04D1ak,0.526,42.644,23.631
82 | SNLS-04D2cf,0.369,41.485,22.340
83 | SNLS-04D2fp,0.415,41.772,22.528
84 | SNLS-04D2fs,0.357,41.441,22.422
85 | SNLS-04D2gb,0.430,41.776,22.796
86 | SNLS-04D2gc,0.521,42.439,23.321
87 | SNLS-04D2gp,0.707,43.237,24.151
88 | SNLS-04D2iu,0.691,43.144,24.258
89 | SNLS-04D2ja,0.741,43.427,24.098
90 | SNLS-04D3co,0.620,43.030,23.781
91 | SNLS-04D3cp,0.830,44.414,24.235
92 | SNLS-04D3cy,0.643,43.023,23.798
93 | SNLS-04D3dd,1.010,44.673,25.120
94 | SNLS-04D3df,0.470,42.268,23.465
95 | SNLS-04D3do,0.610,42.796,23.574
96 | SNLS-04D3ez,0.263,40.682,21.678
97 | SNLS-04D3fk,0.358,41.474,22.532
98 | SNLS-04D3fq,0.730,43.287,24.128
99 | SNLS-04D3gt,0.451,42.038,23.235
100 | SNLS-04D3gx,0.910,44.259,24.708
101 | SNLS-04D3hn,0.552,42.461,23.475
102 | SNLS-04D3is,0.710,43.176,24.256
103 | SNLS-04D3ki,0.930,44.430,24.871
104 | SNLS-04D3kr,0.337,41.259,21.967
105 | SNLS-04D3ks,0.752,43.170,23.882
106 | SNLS-04D3lp,0.983,43.941,24.925
107 | SNLS-04D3lu,0.822,43.544,24.342
108 | SNLS-04D3ml,0.950,43.954,24.552
109 | SNLS-04D3nc,0.817,43.652,24.271
110 | SNLS-04D3nh,0.340,41.323,22.137
111 | SNLS-04D3nr,0.960,43.622,24.542
112 | SNLS-04D3ny,0.810,43.691,24.272
113 | SNLS-04D3oe,0.756,43.453,24.069
114 | SNLS-04D4an,0.613,42.961,24.022
115 | SNLS-04D4bk,0.840,43.475,24.314
116 | SNLS-04D4bq,0.550,42.487,23.362
117 | SNLS-04D4dm,0.811,43.950,24.390
118 | SNLS-04D4dw,0.961,44.000,24.566
119 |
--------------------------------------------------------------------------------
/2MCMC/a_MonteCarloPi.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {
6 | "collapsed": false,
7 | "jupyter": {
8 | "outputs_hidden": false
9 | }
10 | },
11 | "source": [
12 | "# Monte Carlo: Estimating $\\pi$\n",
13 | "\n",
14 | "Notebook structure: \n",
15 | "- What is MC (figure out explanation of why it traverses the probability distribution well)\n",
16 | "- Explain how we can use this to find pi (explain that we do a trivial example first) maths exercise\n",
17 | "- Now write the naive function to calculate an estimate for `n_samples`\n",
18 | "- Now vectorise this function -> show the speed differences (introduction to *magic functions*)\n",
19 | "- Show that we can do the same for a sphere section (what are the convergence properties for this?\n",
20 | "\n",
21 | "The essence of Monte Carlo is to take repeated samples (in this case from a probability distribution to obtain numerical results. Often times the randomness of the Monte Carlo method is used for problems that are deterministic in principle. The origins of the name comes from the code name to secret works of von-Neumann and Ulam in the second world war.\n",
22 | "\n",
23 | "\n",
24 | "\n",
25 | "Our first example is the *Hello World* of Monte Carlo where we will estimate $\\pi$ by picking numbers from a 2D uniform distribution. Here the analytic solution is easy to solve so it serves as an intutibe example as we see our estimate converge towards $\\pi$. The animation above shows the main idea of uniformally picking points from in a 1x1 grid and then see whether these points would lie in a quarter-circle. From this we can estimate $\\pi$.\n",
26 | "\n",
27 | "## The estimator, $\\hat \\pi$\n",
28 | "\n",
29 | "The equation of a circle is $x^2 + y^2 = 1$ therefore if $x_0^2 + y_0^2 < 1$ the point $(x_0, y_0)$ lies within the circle. Therefore if we have $N_\\text{tot}$ samples, we will get a subset $N_\\text{circ}$ which lie within the circle. Since the samples are uniform, on expectation the ratio $E[N_\\text{circ} / N_\\text{tot}] = A_\\text{circ} / A_\\text{tot}$ where the $A$'s are the areas. \n",
30 | "\n",
31 | "$$ E\\left[\\frac{N_\\text{circ}}{N_\\text{tot}}\\right] = \\frac{A_\\text{circ}}{A_\\text{tot}} = \\frac{\\hat \\pi \\cdot 1^1 /4}{1^2} = \\frac \\pi 4 $$\n",
32 | "\n",
33 | "Taking away the expectation and changing the analyic value of $\\pi$ to our estimator $\\hat \\pi$:\n",
34 | "\n",
35 | "$$ \\frac{N_\\text{circ}}{N_\\text{tot}} = \\frac {\\hat \\pi}{4} $$\n",
36 | "\n",
37 | "$$\\hat \\pi = 4 \\cdot \\frac{N_\\text{circ}}{N_\\text{tot}}$$"
38 | ]
39 | },
40 | {
41 | "cell_type": "code",
42 | "execution_count": 1,
43 | "metadata": {
44 | "collapsed": false,
45 | "jupyter": {
46 | "outputs_hidden": false
47 | }
48 | },
49 | "outputs": [],
50 | "source": [
51 | "import random\n",
52 | "import numpy as np\n",
53 | "import matplotlib.pyplot as plt\n",
54 | "from scipy.special import gamma"
55 | ]
56 | },
57 | {
58 | "cell_type": "markdown",
59 | "metadata": {},
60 | "source": [
61 | "### Exercise: Constructing a naive estimator\n",
62 | "\n",
63 | "The first task is to construct a naive estimator but sequentially picking random points and keeping a running total of the number of these points which lie within the circle then using this to estimate $\\pi$. The beauty of a simple problem like this is that we know the value we are aiming for $ \\pi = 3.141\\cdots$ so you should test our your estimator for different values of `n_samples` n.b. that convergence is quite slow so you need a few hundred samples before you start to get good estimates. "
64 | ]
65 | },
66 | {
67 | "cell_type": "code",
68 | "execution_count": 11,
69 | "metadata": {},
70 | "outputs": [],
71 | "source": [
72 | "def estimate_pi_naive(n_samples: int) -> float:\n",
73 | " '''\n",
74 | " YOUR CODE HERE! \n",
75 | "\n",
76 | " hint: check the docs for `np.random.uniform()`\n",
77 | " '''"
78 | ]
79 | },
80 | {
81 | "cell_type": "code",
82 | "execution_count": 12,
83 | "metadata": {},
84 | "outputs": [],
85 | "source": [
86 | "estimate_pi_naive(n_samples=10000)"
87 | ]
88 | },
89 | {
90 | "cell_type": "markdown",
91 | "metadata": {},
92 | "source": [
93 | "### Exercise: Constructing a vectorised estimator\n",
94 | "\n",
95 | "Now having constructed an estimator by sequentially picking points we can construct a more efficient estimator. This is done via picking all the points at once and then checking them all at once too. This is done using `numpy`"
96 | ]
97 | },
98 | {
99 | "cell_type": "code",
100 | "execution_count": 13,
101 | "metadata": {},
102 | "outputs": [],
103 | "source": [
104 | "def estimate_pi_vectorised(n_samples: int) -> float:\n",
105 | " '''\n",
106 | " YOUR CODE HERE!\n",
107 | "\n",
108 | " hint: use `np.where()`\n",
109 | " '''"
110 | ]
111 | },
112 | {
113 | "cell_type": "code",
114 | "execution_count": 14,
115 | "metadata": {},
116 | "outputs": [],
117 | "source": [
118 | "estimate_pi_vectorised(n_samples=10000)"
119 | ]
120 | },
121 | {
122 | "cell_type": "markdown",
123 | "metadata": {},
124 | "source": [
125 | "Now lets see the how the two different ways of writing this estimate differ in their time performance using the *magic function* `%timeit`. This type of function is only available in Jupyter notebooks.\n",
126 | "\n",
127 | "Our vectorised functions should run about ~10x quicker here as the for loops in the vectorised version are run implicitly in a quicker compiled language, C."
128 | ]
129 | },
130 | {
131 | "cell_type": "code",
132 | "execution_count": 16,
133 | "metadata": {},
134 | "outputs": [],
135 | "source": [
136 | "n_samples = int(1e6) # one million samples\n",
137 | "\n",
138 | "%timeit estimate_pi_naive(n_samples)\n",
139 | "%timeit estimate_pi_vectorised(n_samples)"
140 | ]
141 | },
142 | {
143 | "cell_type": "markdown",
144 | "metadata": {},
145 | "source": [
146 | "2l/pi = n_hit / n_tot"
147 | ]
148 | },
149 | {
150 | "cell_type": "markdown",
151 | "metadata": {},
152 | "source": [
153 | "### Extension - Sampling in higher dimensions\n",
154 | "As an extension we can further investigate this problem by looking at how the variance of our estimator changes as we perform this experiment in different dimensions. Does generating points in 3D and calculating intersections with a sphere give higher or lower variance? We have quoted some results for volumes of spheres in arbitrary dimension $d$.\n",
155 | "\n",
156 | "$$ V_s/V_t ={\\frac {\\pi ^{d/2}}{\\Gamma {\\bigl (}{\\tfrac {d}{2}}+1{\\bigr )}}}$$\n",
157 | "\n",
158 | "$$ N_s/N_t={\\frac {\\hat \\pi_d ^{d/2}}{\\Gamma {\\bigl (}{\\tfrac {d}{2}}+1{\\bigr )}}}$$\n",
159 | "\n",
160 | "$$ \\hat \\pi_d = \\left ( \\Gamma(\\frac d 2+1) \\cdot N_s/N_t \\right )^{2/d}$$\n",
161 | "\n",
162 | "$$\\mathrm{Var}(\\hat \\pi_{d,n}) = \\frac{\\left ( \\Gamma(\\frac d 2+1) \\cdot N_s/N_t \\right )^{2/d} - \\pi^2}{n}$$\n",
163 | "\n",
164 | "$$\\mathrm{Var}(\\hat \\pi_{2,n}) = E[\\hat \\pi^2] - E[\\hat \\pi]^2 = \\frac{\\left ( 4 \\pi - \\pi^2 \\right )}{n}$$"
165 | ]
166 | },
167 | {
168 | "cell_type": "code",
169 | "execution_count": null,
170 | "metadata": {},
171 | "outputs": [],
172 | "source": []
173 | }
174 | ],
175 | "metadata": {
176 | "kernelspec": {
177 | "display_name": "Python 3 (ipykernel)",
178 | "language": "python",
179 | "name": "python3"
180 | },
181 | "language_info": {
182 | "codemirror_mode": {
183 | "name": "ipython",
184 | "version": 3
185 | },
186 | "file_extension": ".py",
187 | "mimetype": "text/x-python",
188 | "name": "python",
189 | "nbconvert_exporter": "python",
190 | "pygments_lexer": "ipython3",
191 | "version": "3.9.6"
192 | }
193 | },
194 | "nbformat": 4,
195 | "nbformat_minor": 4
196 | }
197 |
--------------------------------------------------------------------------------
/2MCMC/b_IsingModel.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "id": "bb8986c7-692b-4b27-a34c-79fd8780cac1",
6 | "metadata": {},
7 | "source": [
8 | "# Ising Model\n",
9 | "\n",
10 | "\n",
11 | "\n",
12 | "In this project you will use the Metropolis algorithm to investigate ferromagnetism using a simplified model of the spin‑spin interactions known as the [Ising model](https://en.wikipedia.org/wiki/Ising_model). In the Ising model we treat the magnet as a set of spins on fixed lattice sites in two dimensions. The remarkably simple model predicts many features of real ferromagnetic materials such as second order phase transitions in temperature ([Curie point](https://en.wikipedia.org/wiki/Curie_temperature)).\n",
13 | "\n",
14 | "Suppose we have an $N$ by $N$ 2D lattice, for a total of $N^2$ sites. The energy of the system is written\n",
15 | "$$\n",
16 | "E = - \\sum_{(ij)}{s_i s_j}\n",
17 | "$$\n",
18 | "where $(ij)$ is the sum over nearest neighbour pairs of spins, four total in the 2D case. \n",
19 | "\n",
20 | "In order to calculate an observable of the system such as the magnetisation (average spin value $ M = \\frac{1}{N^2} \\sum s_i $), we need to calculate the [canonical ensemble](https://en.wikipedia.org/wiki/Canonical_ensemble) of the system at a given temperature, and then take the average $\\langle M \\rangle = \\overline{M}$ over many microstates. Therefore, we need an algorithm which will generate lattice configurations according to the [Boltzman distribution](https://en.wikipedia.org/wiki/Boltzmann_distribution) at temperature $T$\n",
21 | "$$\n",
22 | "P(\\text{Lattice} ; E) = \\frac{1}{Z} e^{-E / T}\n",
23 | "$$\n",
24 | "\n",
25 | "Don't worry if the physics behind this is unfamiliar, its really not important. The main idea here is that we have a high dimensional function $E$ (energy) of $N^2$ parameters (spin). And we want to calculate (or estimate) the probability distribution over these parameters according to $P(E)$. "
26 | ]
27 | },
28 | {
29 | "cell_type": "markdown",
30 | "id": "44279038-ac1b-4f46-99ab-6f508c0cd5e7",
31 | "metadata": {},
32 | "source": [
33 | "## Metropolis Algorithm\n",
34 | "\n",
35 | "The [Metropolis algorithm](https://en.wikipedia.org/wiki/Markov_chain_Monte_Carlo) is a Markov-Chain Monte-Carlo method for obtaining a sequence of random samples that is guaranteed to converge to a target distribution. In the last exercise using pure Monte-Carlo, each sample was generated independently with $x,y \\in [0,1]$. \n",
36 | "\n",
37 | "With the Metropolis algorithm, we perform local (small) updates on an initial sample and then choose to probabilistically accept or reject the new sample. If we accept the sample, we add it to our chain and then repeat the process. The intuition with this algorithm is that most of the chain should have lattices with low energy, but we still want some configurations with a high energy to match the desired Boltzmann distribution.\n",
38 | "\n",
39 | "The metropolis algorithm for evolving the lattice microstates is as follows\n",
40 | "1. Select a spin. Calculate $ \\Delta E $ to flip it.\n",
41 | "3. Select a random number $p \\in [0,1] $.\n",
42 | "4. If $ \\exp(- \\Delta E / T ) > p $, flip the spin.\n",
43 | "\n",
44 | "This process is repeated for all $N^2$ spins, after which constitutes a single $\\text{Monte Carlo time step}$. The newly calculated lattice is then added to a list of Markov-Chain samples. \n",
45 | "\n",
46 | "To calculate an observable such as the magnetisation, we simply take the mean value of the magnetisation for each sample in the list.\n",
47 | "$$\n",
48 | "\\langle M \\rangle = \\frac{1}{n_{\\text{samples}}} \\sum_{t=0}^{\\text{samples}} M_t \n",
49 | "$$\n",
50 | "$$\n",
51 | "M_t = \\frac{1}{N^2} \\sum_i s_i \n",
52 | "$$\n",
53 | "\n",
54 | "Here is a plot of a Markov-Chain sequence where we calculate the magnetisation of 1000 samples and take the mean of the entire chain.\n",
55 | ""
56 | ]
57 | },
58 | {
59 | "cell_type": "markdown",
60 | "id": "9e75f41d-6b07-4733-8b08-f3c21f981896",
61 | "metadata": {},
62 | "source": [
63 | "## Phase Diagram\n",
64 | "\n",
65 | "Our main goal is to observe a [phase transition](https://en.wikipedia.org/wiki/Phase_transition) in the Ising model. To do this we have to calculate the (absolute) mean magnetisation $| \\langle M \\rangle |$ for a range of different temperatures. Plotting these on a graph we hope to see a discontinuity where the magnetisation suddenly drops to 0. \n",
66 | "\n",
67 | "\n",
68 | "\n",
69 | "For reference we have also plotted the [analytical value](https://en.wikipedia.org/wiki/Ising_model#Two_dimensions) for the critical temperature $ T_c = 2/\\ln(1+\\sqrt(2)) \\approx 2.26918 $.\n",
70 | "\n",
71 | "It is your task to implement this algorithm and create a similar phase diagram. Start with a small $N=10$ lattice and see how the phase diagram compares against larger lattices. Try to generate at least $1,000$ samples for each temperature."
72 | ]
73 | },
74 | {
75 | "cell_type": "markdown",
76 | "id": "594192e8-9c66-4bc1-b97f-075584e499d0",
77 | "metadata": {},
78 | "source": [
79 | "## Extension (Optimisations)\n",
80 | "\n",
81 | "The Metropolis algorithm for the Ising model can be computed thousands of times faster using an optimised algorithm that exploits the symmetry of the model. The 'checkerboard' algorithm is described in this [paper](https://arxiv.org/pdf/1906.06297.pdf) and can be implemented in python with not too much extension. Try to use np.roll or jax.roll for GPU acceleration and measure the performance difference. "
82 | ]
83 | },
84 | {
85 | "cell_type": "code",
86 | "execution_count": 1,
87 | "id": "a4d5b281-5c96-42a8-87c7-c612ffcfbc25",
88 | "metadata": {},
89 | "outputs": [],
90 | "source": [
91 | "import random\n",
92 | "import math\n",
93 | "import numpy as np\n",
94 | "import matplotlib.pyplot as plt\n",
95 | "\n",
96 | "CRITICAL_TEMP = 2.26918"
97 | ]
98 | }
99 | ],
100 | "metadata": {
101 | "kernelspec": {
102 | "display_name": "Python 3 (ipykernel)",
103 | "language": "python",
104 | "name": "python3"
105 | },
106 | "language_info": {
107 | "codemirror_mode": {
108 | "name": "ipython",
109 | "version": 3
110 | },
111 | "file_extension": ".py",
112 | "mimetype": "text/x-python",
113 | "name": "python",
114 | "nbconvert_exporter": "python",
115 | "pygments_lexer": "ipython3",
116 | "version": "3.9.6"
117 | }
118 | },
119 | "nbformat": 4,
120 | "nbformat_minor": 5
121 | }
122 |
--------------------------------------------------------------------------------
/2MCMC/figs/finite_size_scaling.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BorisDeletic/CompStatsForQuant/14102339e7eba0b810287ad822824ae9578769f3/2MCMC/figs/finite_size_scaling.png
--------------------------------------------------------------------------------
/2MCMC/figs/ising_model.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BorisDeletic/CompStatsForQuant/14102339e7eba0b810287ad822824ae9578769f3/2MCMC/figs/ising_model.png
--------------------------------------------------------------------------------
/2MCMC/figs/magnetisation.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BorisDeletic/CompStatsForQuant/14102339e7eba0b810287ad822824ae9578769f3/2MCMC/figs/magnetisation.png
--------------------------------------------------------------------------------
/2MCMC/figs/phase_transition.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BorisDeletic/CompStatsForQuant/14102339e7eba0b810287ad822824ae9578769f3/2MCMC/figs/phase_transition.png
--------------------------------------------------------------------------------
/2MCMC/figs/pi_estimate.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BorisDeletic/CompStatsForQuant/14102339e7eba0b810287ad822824ae9578769f3/2MCMC/figs/pi_estimate.gif
--------------------------------------------------------------------------------
/3GameTheory/a_RockPaperScissors.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "id": "61fb92b6-235b-4e24-8839-fc2ce880ebc0",
6 | "metadata": {},
7 | "source": [
8 | "# Introduction to Game Theory\n",
9 | "\n",
10 | "[Game Theory](https://en.wikipedia.org/wiki/Game_theory) is the study of mathematical strategies among rational agents in a game. It has very important applications in financial markets, fundamentally due to the fact that every trade occurs between two parties who both believe their side of the trade to be the correct one. In this way, a market forms an effective 'game' with defined rules and millions of (approximately) rational agents participating. \n",
11 | "\n",
12 | "Understanding game theory principles such as [adverse selection](https://en.wikipedia.org/wiki/Adverse_selection) and [nash equilibria](https://en.wikipedia.org/wiki/Nash_equilibrium) are very useful concepts when designing strategies in quant trading. Studying games such as chess and poker are also good resources for learning ideas from game theory and supplement this course well. For a good introduction to poker theory see [The Course](https://books.google.co.uk/books/about/The_Course.html?id=GJSOrgEACAAJ&redir_esc=y)."
13 | ]
14 | },
15 | {
16 | "cell_type": "markdown",
17 | "id": "418f12b7-0a0d-47da-9510-9c05a839e7dc",
18 | "metadata": {},
19 | "source": [
20 | "## Nash Equilibrium\n",
21 | "In this notebook we will look at one algorithm for solving the Nash equilibrium in the trivial example of rock paper scissors. In the next notebook we will look at a more interesting dice game which is inspired by an actual quant trading interview question.\n",
22 | "\n",
23 | "The concept of nash equilibrium essentially defines the solution to a non-cooperative game. It is a fairly nuanced idea and we recommend going through the wiki page or some textbooks before continuing. In summary, \"In a Nash equilibrium, each player is assumed to know the equilibrium strategies of the other players, and no one has anything to gain by changing only one's own strategy\".\n",
24 | "\n",
25 | "The Nash equilibrium for Rock Paper Scissors is known as a [mixed strategy](https://en.wikipedia.org/wiki/Strategy_(game_theory)#Mixed_strategy), and is simply to randomly pick each option randomly with probability $p=1/3$. Our aim is to reproduce this strategy computationally using [counterfactual regret minimization](https://poker.cs.ualberta.ca/publications/NIPS07-cfr.pdf).\n"
26 | ]
27 | },
28 | {
29 | "cell_type": "markdown",
30 | "id": "0d2e51bf-b105-4a58-be66-08eb451e920b",
31 | "metadata": {},
32 | "source": [
33 | "## Counterfactual Regret Minimization\n",
34 | "[Counterfactual regret minimization](https://poker.cs.ualberta.ca/publications/NIPS07-cfr.pdf) is an algorithm for solving nash equilibriums and is currently the state of the art for Texas No Limit Hold'em Poker solvers.\n",
35 | "\n",
36 | "Let $a$ be the actions available to player $i$. We define the *strategy*, $\\sigma_i(a)$, to be a probability distribution over the actions, such that $\\sum_{\\text{actions}} \\sigma_i(a) = 1$. If there are multiple turns in the game, then each turn will have a different strategy depending on the previous actions taken by other players. This defines a *strategy profile* for the player $\\sigma_i$.\n",
37 | "\n",
38 | "In the example of rock-paper-scissors both players have only one turn, and the actions are:\n",
39 | "* Choose Rock\n",
40 | "* Choose Paper\n",
41 | "* Choose Scissors\n",
42 | "\n",
43 | "Next we define the *utility*, $u_i(a)$, as the expected payoff for player $i$ for picking action $a$. Imagine that player A has a strategy where they pick rock 100% of the time. The utility for player B would look like $u_B(a) = \\{ R=0, P=1, S=-1 \\}$ for a $1 bet on each game.\n",
44 | "\n",
45 | "The algorithm works iteratively, starting with a random strategy for both players and slightly adjusting the strategies each iteration to eventually converge onto the nash equilibrium strategy.\n",
46 | "\n",
47 | "Define the immediate counterfactual *regret* on iteration $t$ of an action $a$ as $R_i^t(a)$. It is the difference between the utility of our strategy and the utility of picking action $a$ 100% of the time. \n",
48 | "$$\n",
49 | "r_i^t(a) = u_i(a) - \\sum_{a'} \\sigma_i(a') u_i(a')\n",
50 | "$$\n",
51 | "This quantifies how much regret we have for not choosing action $a$ as part of our strategy. If our regret is very positive, then we would want to add more of action $a$ to our strategy; and if the regret is very negative, then we would want to stop doing action $a$ as much.\n",
52 | "\n",
53 | "The accumulated regret is simply the sum of immediate regret over all iterations\n",
54 | "$$\n",
55 | "R^T_i(a) = \\sum_t^T r_i^t(a)\n",
56 | "$$\n",
57 | "To choose our strategy on iteration $t$ we use the accumulated regret. Let $R^{T,+}_i(a) = \\max(R^T_i(a),0)$ be the positive portion of accumulated regret.\n",
58 | "\n",
59 | "We choose the strategy for each player at iteration $T$ according to\n",
60 | "$$\n",
61 | "\\sigma^T_i(a) = \n",
62 | "\\frac{R^{T,+}_i(a)}{\\sum_{a} R^{T,+}_i(a)}.\n",
63 | "$$\n",
64 | "This has the nice property that the probabilities are always normalised over the actions, such that $\\sum_{\\text{actions}} \\sigma_i(a) = 1$. We also will choose $\\sigma^T_i(a) = 0$ for any action that has negative accumulated regret, as it is a very bad move.\n",
65 | "\n",
66 | "We must also consider the edge case in which $\\sum_a R^{T,+}_i(a)=0$ in which we will simply choose all strategies equally\n",
67 | "$$\n",
68 | "\\sigma^T_i(a) = \\frac{1}{A}\n",
69 | "$$\n",
70 | "where $A$ is the total number of actions.\n",
71 | "\n",
72 | "This now completes the counterfactual regret minimization algorithm. There is a proof (you can find in the linked papers) that choosing strategies according to this scheme will converge to nash equilibrium as $T \\rightarrow \\infty$."
73 | ]
74 | },
75 | {
76 | "cell_type": "markdown",
77 | "id": "0b83c045-c621-4b40-99f8-cdbe362806ed",
78 | "metadata": {},
79 | "source": [
80 | "## Rock Paper Scissors\n",
81 | "Let us now implement this algorithm for rock paper scissors. We perform the CFR steps iteratively for each player. The hero is the current player who is updating their regrets taking the villains (other player's) strategy as an input.\n"
82 | ]
83 | },
84 | {
85 | "cell_type": "code",
86 | "execution_count": 86,
87 | "id": "af3e889e-f532-4a93-8b9a-c46ea4fc7db8",
88 | "metadata": {},
89 | "outputs": [],
90 | "source": [
91 | "import numpy as np\n",
92 | "import matplotlib.pyplot as plt"
93 | ]
94 | },
95 | {
96 | "cell_type": "code",
97 | "execution_count": 93,
98 | "id": "445efaa6-d730-4118-9b83-54970cda7520",
99 | "metadata": {},
100 | "outputs": [],
101 | "source": [
102 | "'''\n",
103 | "Represent actions by their index in the strategy array. Here we will use the convention \n",
104 | "rock = 0\n",
105 | "paper = 1\n",
106 | "scissors = 2\n",
107 | "'''\n",
108 | "actions = [0, 1, 2]\n",
109 | "\n",
110 | "def payoff(action: int, villain_strategy: np.array(float)) -> float:\n",
111 | " '''\n",
112 | " PAYOFF FUNCTION HERE\n",
113 | " '''"
114 | ]
115 | },
116 | {
117 | "cell_type": "code",
118 | "execution_count": 88,
119 | "id": "db043961-83d5-47e4-ad3b-6f2329fb207e",
120 | "metadata": {},
121 | "outputs": [],
122 | "source": [
123 | "'''\n",
124 | "Calculate immediate regret for every action\n",
125 | "'''\n",
126 | "\n",
127 | "def calculate_immediate_regret(hero_strategy: np.array(float), villain_strategy: np.array(float)) -> np.array(float):\n",
128 | " '''\n",
129 | " IMMEDIATE REGRET \n",
130 | " '''\n"
131 | ]
132 | },
133 | {
134 | "cell_type": "code",
135 | "execution_count": 94,
136 | "id": "7a8fadf3-756c-4fa3-9cce-6be98c6b7ebc",
137 | "metadata": {},
138 | "outputs": [],
139 | "source": [
140 | "'''\n",
141 | "Calculate new strategy based on accumulated regret for the hero\n",
142 | "'''\n",
143 | "\n",
144 | "def calculate_strategy(acc_regrets: np.array(float)) -> np.array(float):\n",
145 | " '''\n",
146 | " CALCULATE NEW STRATEGY\n",
147 | " '''\n"
148 | ]
149 | },
150 | {
151 | "cell_type": "code",
152 | "execution_count": 90,
153 | "id": "30c3b22b-5b82-4d01-8935-223b7a4187ff",
154 | "metadata": {},
155 | "outputs": [],
156 | "source": [
157 | "'''\n",
158 | "Run CFR algorithm.\n",
159 | "We set initial strategy for player A and B to both pick rock 100% of the time. \n",
160 | "'''\n",
161 | "\n",
162 | "strategyA = np.array([1.0, 0.0, 0.0])\n",
163 | "strategyB = np.array([1.0, 0.0, 0.0])\n",
164 | "\n",
165 | "acc_regretsA = np.array([0.0, 0.0, 0.0])\n",
166 | "acc_regretsB = np.array([0.0, 0.0, 0.0])\n",
167 | "\n",
168 | "steps = 100\n",
169 | "\n",
170 | "strat_history = []\n",
171 | "\n",
172 | "for t in range(steps):\n",
173 | " acc_regretsA += calculate_immediate_regret(strategyA, strategyB)\n",
174 | " strategyA = calculate_strategy(acc_regretsA)\n",
175 | " \n",
176 | " acc_regretsB += calculate_immediate_regret(strategyB, strategyA)\n",
177 | " strategyB = calculate_strategy(acc_regretsB)\n",
178 | "\n",
179 | " strat_history.append(strategyA)\n"
180 | ]
181 | },
182 | {
183 | "cell_type": "markdown",
184 | "id": "474d8ed1-d33c-4717-89c7-9c6f2484343c",
185 | "metadata": {},
186 | "source": [
187 | "Running this as-is works quite well, but if you plot the strategy over iterations you will notice there are some oscillations around the nash equilibrium. To smooth this effect we perform another averaging step over the strategy history.\n",
188 | "$$\n",
189 | "\\overline{\\sigma}(a) = \n",
190 | "\\frac{\\sum_{t} {\\sigma^t(a)}}{\\sum_{t, a} {\\sigma}^t(a)}.\n",
191 | "$$\n",
192 | "This is simply saying if our strategy for an action oscillates like $\\sigma^t = \\{0.4, 0.5, 0.4, 0.5, 0.4, \\cdots \\}$ between CFR steps, then at the end, the nash equilibrium we will take is the average $\\overline{\\sigma} = 0.45$. \n",
193 | "\n",
194 | "Implementing this and plotting the strategies calculated shows a nice convergence to the nash equilibrium.\n",
195 | "\n",
196 | "\n",
197 | "\n",
198 | "*Rock-Paper-Scissors Nash Equilibrium Converging from CFR*\n",
199 | "\n",
200 | "Another interesting thing to investigate is 'best response' strategies. We can change the CFR loop to only update strategies for player A, keeping B fixed as whatever we set it to, and calculating the optimal counter strategy to B. For example, the obvious 'best response' strategies to player B choosing rock 100% of the time, is to pick paper 100%. \n",
201 | "\n",
202 | "However, there are some non-obvious best responses, even in rock-paper-scissors. For example what is the best response to 50% rock, 50% paper. Or 40% rock, 60% paper? Is there any difference to 30% rock, 70% paper?"
203 | ]
204 | }
205 | ],
206 | "metadata": {
207 | "kernelspec": {
208 | "display_name": "Python 3 (ipykernel)",
209 | "language": "python",
210 | "name": "python3"
211 | },
212 | "language_info": {
213 | "codemirror_mode": {
214 | "name": "ipython",
215 | "version": 3
216 | },
217 | "file_extension": ".py",
218 | "mimetype": "text/x-python",
219 | "name": "python",
220 | "nbconvert_exporter": "python",
221 | "pygments_lexer": "ipython3",
222 | "version": "3.9.6"
223 | }
224 | },
225 | "nbformat": 4,
226 | "nbformat_minor": 5
227 | }
228 |
--------------------------------------------------------------------------------
/3GameTheory/b_InterviewGame.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "id": "2e0ddb88-5b63-4350-b987-cdeeceffed81",
6 | "metadata": {},
7 | "source": [
8 | "# Trading Interview Game\n",
9 | "\n",
10 | "Now we have introduced CFR and understand the basics, lets try apply it to a problem inspired by a real interview question from a top quantitative trading firm.\n",
11 | "\n",
12 | "## The Game - Part 1\n",
13 | "\n",
14 | "The game starts with two players, both have to submit a number between 0 and 100 on a piece of paper. After the numbers are submitted, the papers are checked and whoever submitted the larger number must pay the other player their number. \n",
15 | "\n",
16 | "A few examples:\n",
17 | "* Player A submits 90, Player B submits 10. Player B wins so receives \\$10.\n",
18 | "* Player A submits 15, Player B submits 20. Player A wins so receives \\$15.\n",
19 | "* Player A submits 1, Player B submits 99. Player A wins so receives \\$1.\n",
20 | "\n",
21 | "Imagine you are playing this game and versing an opponent who submits a random number between 1 and 100.\n",
22 | "The warm-up question, which can be solved with just paper and pen, is to calculate the optimal number for you to submit, such that you maximise you expected value in this game. \n",
23 | "\n",
24 | "\n"
25 | ]
26 | },
27 | {
28 | "cell_type": "markdown",
29 | "id": "70a0251d-de83-40ff-b241-c81ab583f017",
30 | "metadata": {},
31 | "source": [
32 | "## The Game - Part 2\n",
33 | "\n",
34 | "Now we introduce a third player to the game who is perfectly rational, as are you, and there is still a player who submits a random bid. The penalty for losing a game is now greater, as the person who submits the highest number must now pay all the other players their number. If there is a tie for highest bid, they each pay half of the third players number.\n",
35 | "\n",
36 | "For example:\n",
37 | "* Player A submits 50, Player B submits 40, Player C submits 30. A loses so pays B \\\\$40 and pays C \\\\$30.\n",
38 | "* Player A submits 20, Player B submits 50, Player C submits 50. B and C tie to lose, so both pay A \\\\$10 each.\n",
39 | "\n",
40 | "\n",
41 | "Our previous strategy of picking a single number is no longer profitable as we will get exploited by the new player. It is likely we will now need to play a mixed strategy. Let's try solve this problem using CFR where we will find the nash equilibrium between player A and player B, and play this strategy to maximise our expected value. \n",
42 | "\n",
43 | "It is worth trying to solve this problem with just pen and paper too, as would be expected in an interview. We have therefore leftout the graph showing the answer and have it in the solutions document.\n",
44 | "\n",
45 | "### Extension\n",
46 | "In order to calculate a solution which converged fast enough, I had to use a slight modification on CFR called [CFR+](https://arxiv.org/pdf/1407.5042).\n",
47 | "\n",
48 | "To check your nash equilibrium solution you can see how its EV compares to a pure strategy of choosing a single number. This is an easy way to check if you have made a mistake, as the nash strategy should beat all pure strategies. \n",
49 | "\n",
50 | "Further, you can compute the *exploitability* of a strategy to get a numerical value for the maximum theoretical exploitability of your strategy. You can also compare this to exploitability of pure strategies. I'll leave it up to you to figure out how to do this"
51 | ]
52 | },
53 | {
54 | "cell_type": "code",
55 | "execution_count": 41,
56 | "id": "ef19153b-c3b5-4455-b6c8-57cbe37b8f24",
57 | "metadata": {},
58 | "outputs": [],
59 | "source": [
60 | "import numpy as np\n",
61 | "import matplotlib.pyplot as plt\n",
62 | "N = 101\n",
63 | "actions = np.arange(1, N)"
64 | ]
65 | },
66 | {
67 | "cell_type": "code",
68 | "execution_count": 79,
69 | "id": "ba70c7e9-d536-4235-8086-2364ccae53b8",
70 | "metadata": {},
71 | "outputs": [],
72 | "source": [
73 | "# hero number is first\n",
74 | "def game_outcome(numbers: list[int]) -> int:\n",
75 | " '''\n",
76 | " numbers: [hero_number, villain1_number, villain2_number]\n",
77 | " \n",
78 | " Return the win or loss for the hero given the submitted numbers\n",
79 | " according to the rules of the game\n",
80 | " '''"
81 | ]
82 | },
83 | {
84 | "cell_type": "code",
85 | "execution_count": 80,
86 | "id": "539291ad-0803-427f-83ce-54e05427af38",
87 | "metadata": {},
88 | "outputs": [],
89 | "source": [
90 | "'''\n",
91 | "Action is the number to submit (100 possible actions)\n",
92 | "\n",
93 | "Tip: to speedup the computation, we can incorporate the randomness \n",
94 | "of player C's bid into our payoff function by taking an average over \n",
95 | "all 100 possible choices, instead of actually choosing a random number for player C.\n",
96 | "'''\n",
97 | "\n",
98 | "def payoff(hero_action: int, villain_strategy: np.array(float)) -> float:\n",
99 | " '''\n",
100 | " PAYOFF FUNCTION HERE\n",
101 | " ''' "
102 | ]
103 | },
104 | {
105 | "cell_type": "code",
106 | "execution_count": 81,
107 | "id": "3be25fd7-6ab7-4602-9277-d7e654849c40",
108 | "metadata": {},
109 | "outputs": [],
110 | "source": [
111 | "'''\n",
112 | "Calculate immediate regret for every action\n",
113 | "'''\n",
114 | "def calculate_immediate_regret(hero_strategy: np.array(float), villain_strategy: np.array(float)) -> np.array(float):\n",
115 | " '''\n",
116 | " IMMEDIATE REGRET \n",
117 | " '''\n"
118 | ]
119 | },
120 | {
121 | "cell_type": "code",
122 | "execution_count": 82,
123 | "id": "e39f99ca-418e-490b-bf1b-56c2641355b8",
124 | "metadata": {},
125 | "outputs": [],
126 | "source": [
127 | "'''\n",
128 | "Calculate new strategy based on accumulated regret for the hero\n",
129 | "'''\n",
130 | "\n",
131 | "def calculate_strategy(acc_regrets: np.array(float)) -> np.array(float):\n",
132 | " '''\n",
133 | " CALCULATE NEW STRATEGY\n",
134 | " '''\n"
135 | ]
136 | },
137 | {
138 | "cell_type": "code",
139 | "execution_count": 83,
140 | "id": "6f9e4017-d113-4a04-bfa8-808434ee9182",
141 | "metadata": {
142 | "scrolled": true
143 | },
144 | "outputs": [
145 | {
146 | "ename": "UFuncTypeError",
147 | "evalue": "Cannot cast ufunc 'add' output from dtype('O') to dtype('float64') with casting rule 'same_kind'",
148 | "output_type": "error",
149 | "traceback": [
150 | "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
151 | "\u001b[0;31mUFuncTypeError\u001b[0m Traceback (most recent call last)",
152 | "Cell \u001b[0;32mIn[83], line 17\u001b[0m\n\u001b[1;32m 14\u001b[0m strat_history \u001b[38;5;241m=\u001b[39m []\n\u001b[1;32m 16\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m t \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28mrange\u001b[39m(steps):\n\u001b[0;32m---> 17\u001b[0m acc_regretsA \u001b[38;5;241m+\u001b[39m\u001b[38;5;241m=\u001b[39m calculate_immediate_regret(strategyA, strategyB)\n\u001b[1;32m 18\u001b[0m strategyA \u001b[38;5;241m=\u001b[39m calculate_strategy(acc_regretsA)\n\u001b[1;32m 20\u001b[0m acc_regretsB \u001b[38;5;241m+\u001b[39m\u001b[38;5;241m=\u001b[39m calculate_immediate_regret(strategyB, strategyA)\n",
153 | "\u001b[0;31mUFuncTypeError\u001b[0m: Cannot cast ufunc 'add' output from dtype('O') to dtype('float64') with casting rule 'same_kind'"
154 | ]
155 | }
156 | ],
157 | "source": [
158 | "'''\n",
159 | "Run CFR algorithm.\n",
160 | "We set initial strategy for player A and B to both pick rock 100% of the time. \n",
161 | "'''\n",
162 | "\n",
163 | "strategyA = np.ones(N) / N\n",
164 | "strategyB = np.ones(N) / N\n",
165 | "\n",
166 | "acc_regretsA = np.zeros(N)\n",
167 | "acc_regretsB = np.zeros(N)\n",
168 | "\n",
169 | "steps = 100\n",
170 | "\n",
171 | "strat_history = []\n",
172 | "\n",
173 | "for t in range(steps):\n",
174 | " acc_regretsA += calculate_immediate_regret(strategyA, strategyB)\n",
175 | " strategyA = calculate_strategy(acc_regretsA)\n",
176 | " \n",
177 | " acc_regretsB += calculate_immediate_regret(strategyB, strategyA)\n",
178 | " strategyB = calculate_strategy(acc_regretsB)\n",
179 | "\n",
180 | " strat_history.append(strategyA)\n"
181 | ]
182 | }
183 | ],
184 | "metadata": {
185 | "kernelspec": {
186 | "display_name": "Python 3 (ipykernel)",
187 | "language": "python",
188 | "name": "python3"
189 | },
190 | "language_info": {
191 | "codemirror_mode": {
192 | "name": "ipython",
193 | "version": 3
194 | },
195 | "file_extension": ".py",
196 | "mimetype": "text/x-python",
197 | "name": "python",
198 | "nbconvert_exporter": "python",
199 | "pygments_lexer": "ipython3",
200 | "version": "3.12.5"
201 | }
202 | },
203 | "nbformat": 4,
204 | "nbformat_minor": 5
205 | }
206 |
--------------------------------------------------------------------------------
/3GameTheory/figs/numbers_game_nash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BorisDeletic/CompStatsForQuant/14102339e7eba0b810287ad822824ae9578769f3/3GameTheory/figs/numbers_game_nash.png
--------------------------------------------------------------------------------
/3GameTheory/figs/rps_converging.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BorisDeletic/CompStatsForQuant/14102339e7eba0b810287ad822824ae9578769f3/3GameTheory/figs/rps_converging.png
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Computational Statistics For Quantitative Finance
2 |
3 | ## What this book is
4 | This notebook is a set of short guided projects to help build skills and intuition for the types of problems we have found to be useful for a career in Quantitative Finance. The main topics we will try focus on are
5 | * Writing fast, vectorized python code
6 | * Statistics, probability, and Bayesian inference
7 | * Creating simple, readable graphs with matplotlib to quickly portray and analyse data
8 | * Monte Carlo methods and Markov-Chain processes for statistical models
9 | * Introduction to Game Theory
10 |
11 | Each of the 3 topics are structured with two projects.
12 | The first aims to introduce the new concept and guide you through a simple example with short exercises to build familiarity with the subject.
13 | The second project is a more advanced application of the topic, with only an introduction to the problem and a few goals to get you started, being more open-ended for you to explore.
14 |
15 | 1. ### Bayesian Inference
16 | * Coins in a bag
17 | * Measuring the expansion of the universe (Cosmology)
18 | 2. ### Markov-Chain Monte-Carlo
19 | * Estimating Pi
20 | * Ising Model
21 | 3. ### Game Theory
22 | * Rock Paper Scissors
23 | * Quant Interview Game
24 |
25 | These projects hope to peak your interest and show you what is out there.
26 |
27 | ## What this book is NOT
28 | This is not for interview preperation. There are lots of good existing sources for that (a few we liked linked at the bottom). These projects should be for interest in the style of problems that traders and researchers work on in the day to day at Quant firms.
29 |
30 | This is not the best use of your time the weekend before your interview. You can easily spend months learning each of these topics in far more depth and nuance, as they are all extremely rich and we only scratch the surface.
31 |
32 | You can find the full solutions in the solutions branch of the GitHub, but try to only look at them as a last resort.
33 |
34 | ## Gallery
35 | Here is a highlight of some of the results which you should be able to acheive after finishing this book.
36 |
37 |
39 |
42 | 43 |
48 | 49 |
54 | 55 |
60 | 61 |
66 | 67 | 68 | --------------------------------------------------------------------------------