{ "cells": [ { "cell_type": "code", "execution_count": 1, "metadata": { "collapsed": true, "scrolled": false }, "outputs": [], "source": [ "%matplotlib notebook" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "collapsed": false, "scrolled": false }, "outputs": [], "source": [ "import random\n", "import math\n", "import numpy as np\n", "import matplotlib.pyplot as plt\n", "import time\n", "from sklearn.utils.extmath import cartesian" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Hello there, If you have found this through reddit or social media sharing sites, you should be aware that this iPython Notebook is a companion piece to a talk I gave at Sydney Python. The full write-up can be found [here](http://blog.chewxy.com/2016/09/30/how-to-make-money/) and the slides that precede and succeed this simulation can be found [here](https://speakerdeck.com/chewxy/how-to-make-money-how-money-is-created-in-the-economy). I recommend reading in this order: \n", "\n", "1. [Slides](https://speakerdeck.com/chewxy/how-to-make-money-how-money-is-created-in-the-economy) (until Let's Simulate)\n", "2. This simulation\n", "3. [Slides](https://speakerdeck.com/chewxy/how-to-make-money-how-money-is-created-in-the-economy) (for some takeaway bullet points)\n", "4. [Blog post](http://blog.chewxy.com/2016/09/30/how-to-make-money/) for further discussions\n", "\n", "Also, come caveats: I have purposely omitted discussions of stuff such as the effect of money supply and demand on wealth. It should be quite immediately obvious the moment you start thinking of money as a good as well. However, I have found through previous experience of teaching this to people, that it's not a good way to help them understand how money is created\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Welcome to the great nation of Dystopianistan. The eternal leader is Kimberly Il-Smith. Here in Dystopianistan, things are done slightly differently from how you know them to be. But there are some quite familiar things. We'll start with one of them: the idea of `Bank`s\n", "\n", "`Bank`s provide a place for the citizens of Dystopianistan to store their money. It also provides loans to any citizen who needs it. Loans in Dystopianistan are rather simple - there is no concept of compound interest. Also, all loans are set for a fixed period of time\n", "\n", "`Bank`s loan money with special rules - they can loan more than they actually have. If a `Bank` holds \\$200 in its holdings, it can loan up to \\$1000. The reason is because through a lot of study, it's been found that the citizens of Dystopianistan are generally good debtors - they pay back whatever they owe, plus interest, most of the time. This allows the banks to count on future returns, and so they're allowed to lend out more money than they have in store." ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "collapsed": true, "scrolled": true }, "outputs": [], "source": [ "interest_rate = 0.1\n", "reserve_rate = 0.2\n", "\n", "class Bank(object):\n", " def __init__(self, interest_rate, reserve_rate):\n", " self.interest_rate = interest_rate\n", " self.reserve_rate = reserve_rate\n", " self.loans = []\n", " self.unrecovered = 0.0\n", " self.holding = 0.0\n", " def can_lend(self, amount):\n", " # check if there is enough money in holdings\n", " if self.unrecovered+amount > 1/self.reserve_rate*self.holding:\n", " return False # cannot loan any more money\n", " return True\n", " def lend(self, loan):\n", " self.unrecovered += loan.amount\n", " def repay(self, loan):\n", " self.unrecovered -= loan.repay_amount()\n", "\n", "\n", "class Loan(object):\n", " def __init__(self, lender, borrower, amount, repay_at, interest_rate):\n", " self.lender = lender\n", " self.borrower = borrower\n", " self.amount = amount\n", " self.repay_at = repay_at\n", " self.interest_rate = interest_rate\n", " def repay_amount(self):\n", " return self.amount * (1 + self.interest_rate)\n", " def __repr__(self):\n", " return \"{} | {}\".format(self.repay_amount(), self.repay_at)\n", "\n", "\n", "\n", "\n", "central_bank = Bank(interest_rate, reserve_rate)\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In Dystopianistan, citizens are also able to make investments. Investments are also simple here. At the end of 7 periods, citizens get a return on investment by means of upgrading their skills - they simply become more productive depending on how much investment they put in." ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "collapsed": true, "scrolled": true }, "outputs": [], "source": [ "class Investment(object):\n", " def __init__(self, principle, return_rate, return_at):\n", " self.principle = principle\n", " self.return_rate = return_rate\n", " self.return_at = return_at\n", " self.p = abs(random.gauss(0, 0.01)) # half normal distribution\n", " def __repr__(self):\n", " return \"{} | {}\".format(self.principle, self.return_at)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The citizens of Dystopianistan are very different from what you normally know as people. They are born as full adults, with the capacity to produce things. Right from the moment they are born, they are thrust into the economic machine, producing and consuming goods. Due to their highly efficient nature, the citizens of Dystopianistan only consume goods once every period. Likewise, they produce goods only once every period.\n", "\n", "Despite all that, the citizens of Dystopianistan are not robots. They are quite varied. Each citizen has different likes and dislikes (we call those `preferences`), and each citizen has different skills in producing goods (we call those `productivities`). Also, each citizen of Dystopianistan have different preferences for risk - some like to save more money now (`savings_pref`), some like to invest more money for future benefit (`investment_pref`). \n", "\n", "Consumption of goods make Dystopianistanians happy - a citizen may be good at producing oranges, but she may like apples more than oranges. Therefore she does the logical thing - sell her oranges and buy apples instead. However, sometimes, the prices of apples may be too high for her consumption habit, so she has to either cut down her consumption of apples, or take out a loan.\n", "\n", "Consequently, every Dystopianistanian also has a likelihood of taking out a loan to fuel his/her spending habits (we call that `consumerism`).\n", "\n", "In determining if a citizen is qualified for a loan, we'd have to calculate their `creditworthiness`. " ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "collapsed": false, "scrolled": false }, "outputs": [], "source": [ "DEFAULT_THRESHOLD = 4\n", "class Person(object):\n", " def __init__(self, bank, endowment, productivities, preferences, investment_pref, savings_pref):\n", " '''\n", " :param bank: which Bank does this Person banks with?\n", " :param endowment: how much savings does a person initially start with? rich families = high endowment\n", " :param productivities: a list of numbers the size of `basket`. \n", " This represents how many goods Person can produce per period.\n", " :param preferences: a list of floats the size of `basket`\n", " :param investment_pref: a float between [0,1). 0 = person doesn't care about his/her future earnings\n", " '''\n", " self.bank = bank\n", " self.savings = endowment\n", " self.bank.holding += self.savings\n", " self.productivities = productivities\n", " self.preferences = preferences\n", " self.investment_pref = investment_pref\n", " self.savings_pref = savings_pref\n", " self.loans = []\n", " self.investments = []\n", " self.last_income = endowment\n", " self.nonpayment = 0\n", " self.defaulted = False\n", " self.consumerism = np.random.beta(3,5)\n", "\n", " def utility(self, q, investment):\n", " '''\n", " utility represents a utility a Person has when presented with an allocation of goods and investment.\n", " \n", " :param q: a list of quantities\n", " :param investment: a number\n", " '''\n", " u = 1\n", " for g, p in zip(q, self.preferences):\n", " u *= math.pow(g, p)\n", " u *= math.pow(investment, self.investment_pref)\n", " return u\n", "\n", " def normalized_preferences(self):\n", " return [pref/(sum(self.preferences)+self.investment_pref+self.savings_pref) for pref in self.preferences]\n", " \n", " def normalized_productivities(self):\n", " return [prod/sum(self.productivities) for prod in self.productivities]\n", " \n", " def produce(self, priceMatrix, current):\n", " '''\n", " produce simulates the production capactity of a Person. \n", " '''\n", " normal_prod = np.array(self.normalized_productivities())\n", " budget = self.budget(current)\n", " quantities = normal_prod * priceMatrix\n", " return quantities\n", "\n", " def consume(self, priceMatrix, current):\n", " '''\n", " Returns the demand given the prices. Demand is inelastick, because these Persons are turdballs.\n", " '''\n", " normal_pref = np.array(self.normalized_preferences())\n", " budget = self.budget(current)\n", " quantities = normal_pref * budget / priceMatrix\n", " return quantities\n", " \n", " def budget(self, current):\n", " '''\n", " The budget forms the constraint for a Person in his/her consumption\n", " '''\n", " repayments = 0.0\n", " for loan in self.loans:\n", " if current == loan.repay_at:\n", " repayments += loan.repay_amount()\n", " return self.creditworthiness()*self.consumerism + self.savings - (1-self.consumerism) * repayments\n", "\n", " def creditworthiness(self):\n", " '''\n", " creditworthiness determines how much a Person can borrow from the bank\n", " '''\n", " if self.savings > 0:\n", " return self.last_income + (self.last_income * interest_rate) + self.savings\n", " elif self.last_income > 0:\n", " return self.last_income + (self.last_income * interest_rate)\n", " return 0 # NO CREDIT FOR YOU!\n", " \n", " def debt(self):\n", " '''\n", " How much debt does a Person have?\n", " '''\n", " amount = 0.0\n", " for loan in self.loans:\n", " amount += loan.repay_amount()\n", " return amount\n", " \n", " def spend(self, amount, current):\n", " if amount < self.savings:\n", " self.savings -= amount\n", " return\n", " elif amount > self.savings:\n", " if self.bank.can_lend(amount):\n", " loan = Loan(self.bank, self, self.creditworthiness(), current+5, interest_rate)\n", " self.loans.append(loan)\n", " self.bank.lend(loan)\n", " else:\n", " self.savings = 0\n", " \n", " def earn(self, income):\n", " self.savings += income\n", " self.last_income = income\n", "\n", " def invest(self, amount, current):\n", " investment = Investment(amount, interest_rate, current+7)\n", " self.investments.append(investment)\n", "\n", " def step(self, prices, current):\n", " production = self.produce(prices, current)\n", " income = sum([p*q for p, q in zip(prices, production)])\n", " consumption = self.consume(prices, current)\n", " \n", " inv_pref = self.investment_pref/(sum(self.preferences)+self.investment_pref)\n", " inv_amt = inv_pref * self.budget(current)\n", " \n", " norm_prod = self.normalized_productivities()\n", " prod_cost = [p*q for p, q in zip(prices, production)]\n", " prod_cost = sum(n*c for n, c in zip(norm_prod, prod_cost))\n", " \n", " cost = sum([p*q for p, q in zip(prices, consumption)]) + inv_amt + prod_cost\n", " self.earn(income)\n", " self.spend(cost, current)\n", " if self.savings > inv_amt:\n", " self.invest(inv_amt, current)\n", " \n", " # remove loans that are up\n", " remove = []\n", " cantpay = False\n", " for loan in self.loans:\n", " if loan.repay_at <= current:\n", " if self.savings >= loan.repay_amount():\n", " self.savings -= loan.repay_amount()\n", " self.bank.repay(loan)\n", " remove.append(loan)\n", " else:\n", " cantpay = True\n", " for loan in remove:\n", " self.loans.remove(loan)\n", " \n", " if cantpay:\n", " self.nonpayment += 1\n", " \n", " # realize investments\n", " remove = []\n", " for investment in self.investments:\n", " if investment.return_at == current:\n", " self.savings += investment.principle*(1+investment.return_rate)\n", " self.productivities = [prod *(1+investment.p) for prod in self.productivities]\n", " remove.append(investment)\n", " for investment in remove:\n", " self.investments.remove(investment)\n", " \n", " # check for defaults\n", " if self.nonpayment >= DEFAULT_THRESHOLD:\n", " self.defaulted = True\n", " default_amt = 0.0\n", " for loan in self.loans:\n", " default_amt += loan.amount\n", " self.bank.unrecovered -= default_amt\n", " \n" ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "collapsed": false, "scrolled": true }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Step 0\n", "Price: \n", "[[ 5.60191322 7.01336885 7.12751418]\n", " [ 8.66811908 4.56525387 7.09286451]\n", " [ 7.99165882 8.03726514 3.87125812]]\n", "Budget 189.01043990690778\n", "Productivity \n", "[[ 2.40081995 0. 4.07286525]\n", " [ 3.71490818 0. 4.05306543]\n", " [ 3.42499664 0. 2.2121475 ]]\n", "Consumption \n", "[[ 0. 4.57357645 6.00044237]\n", " [ 0. 7.0261544 6.0297554 ]\n", " [ 0. 3.99093199 11.04763277]]\n", "Income: 42.47858983516505 [ 73.02185606 0. 66.34104276]\n", "Savings: 97.47168364658936\n", "Cost: [ 0. 96.22853572 128.3047143 ]\n", "Debt: 158.61794571179803\n", "Creditworthiness 144.19813246527093\n", "Loans: [158.61794571179803 | 5]\n", "Investments: [16.038089286975342 | 7]\n", "\n", "\n", "\n", "Step 1\n", "Price: \n", "[[ 7.63988269 4.41222354 8.21191414]\n", " [ 8.87513079 4.20549432 2.89029569]\n", " [ 3.12333561 5.97389287 6.57323107]]\n", "Budget 307.61531531020654\n", "Productivity \n", "[[ 3.27423544 0. 4.69252237]\n", " [ 3.80362748 0. 1.65159754]\n", " [ 1.3385724 0. 3.75613204]]\n", "Consumption \n", "[[ 0. 10.70947777 7.67220393]\n", " [ 0. 11.2359229 21.79828181]\n", " [ 0. 7.90985225 9.58485702]]\n", "Income: 63.549365420926776 [ 62.95327685 0. 67.99811986]\n", "Savings: 161.02104906751612\n", "Cost: [ 0. 141.75782993 189.01043991]\n", "Debt: 158.61794571179803\n", "Creditworthiness 230.92535103053558\n", "Loans: [158.61794571179803 | 5]\n", "Investments: [16.038089286975342 | 7, 23.626304988363472 | 8]\n", "\n", "\n", "\n" ] } ], "source": [ "basket = 3\n", "endowment = random.gauss(50,10)\n", "preferences = np.random.randint(0,10, basket)\n", "skills = np.random.randint(0,10, basket)\n", "investment_pref = np.random.randint(0,10)\n", "savings_pref = np.random.randint(0,10)\n", "A = Person(central_bank,endowment, skills, preferences, investment_pref, savings_pref)\n", "\n", "for i in range(2):\n", " prices = np.random.beta(3,2, (3,basket)) * 10\n", " production = A.produce(prices,i)\n", " income = sum([p*q for p, q in zip(prices, production)])\n", " consumption = A.consume(prices,i)\n", " consumes = sum([p*q for p,q in zip(prices, consumption)])\n", " A.step(prices[0], i)\n", "\n", " print(\"Step {}\".format(i))\n", " print(\"Price: \\n{}\".format(prices))\n", " print(\"Budget {}\".format(A.budget(i)))\n", " print(\"Productivity \\n{}\".format(production))\n", " print(\"Consumption \\n{}\".format(consumption))\n", " print(\"Income: {} {}\".format(A.last_income, income))\n", " print(\"Savings: {}\".format(A.savings))\n", " print(\"Cost: {}\".format(consumes))\n", " print(\"Debt: {}\".format(A.debt()))\n", " print(\"Creditworthiness {}\".format(A.creditworthiness()))\n", " print(\"Loans: {}\".format(A.loans))\n", " print(\"Investments: {}\".format(A.investments))\n", " print(\"\\n\\n\")\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Consider a small subset of the population of Dystopianistan where there are two citizens, Alice and Bob, and they only produce two goods: apples and oranges.\n", "\n", "This is what they produced:\n", "\n", "\n", "\n", "\n", "\n", "
WhoApples ProducedOranges Produced
Alice510
Bob125
\n", "\n", "This is what each of them prefer to have:\n", "\n", "\n", "\n", "\n", "\n", "\n", "
WhoApples PreferedOranges Prefered
Alice82
Bob913
\n", "\n", "This can be solved quite easily with basic algebra:\n", "\n", "$$3A - 8O = 0$$\n", "$$A = \\frac{8}{3}O$$\n", "\n", "While it was simple to solve for Alice and Bob, the process of solving it for everyone take $\\mathcal{O}(n^n)$. Instead, the geniuses at Dystopianistan came up with a novel idea: \n", "\n", "1. Poll everyone to see what they would produce given that a good is at a certain price.\n", "2. Poll everyone to see what they would consume given that a good is at a certain price.\n", "3. Calculate a fair price for every good in the world\n", "4. Everyone by law must sell all the goods they produce per period and buy whatever they want at that price.\n", "5. They would build a supercomputer to calculate all the prices - it'd be called InvisibleHandatron 2000.\n", "\n", "Through meticulous study, they have discovered that this method is indeed the most fair method of redistributing the goods - at least in theory. The end result was [Pareto optimal](https://en.wikipedia.org/wiki/Pareto_efficiency) - where no one can be made better off without making someone worse off.\n", "\n", "So, back to the example with Alice and Bob. Let's say the fair price for Apples are \\$3, and the fair price for oranges are \\$8.\n", "\n", "First, they both sell all their goods to InvisbleHandatron 2000:\n", "\n", "\n", "\n", "\n", "\n", "\n", "
WhoIncome From ApplesIncome from OrangesTotal Income
Alice\\$15\\$80\\$95
Bob\\$36\\$40$76
\n", "\n", "They then buy back however much they prefer from InvisibleHandatron 2000:\n", "\n", "\n", "\n", "\n", "\n", "\n", "
WhoCost of buying ApplesCost of buying OrangesTotal Cost
Alice\\$24\\$20\\$45
Bob\\$27\\$104\\$131
\n", "\n", "Here you might notice Bob doesn't make quite enough money to buy what he prefers. But who knows? He may crack into his savings. Or he might take a loan to fund his fruit-related addiction. Or he may choose to eat fewer fruits and go to the toilet less. Either way, the story has ended.\n" ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "collapsed": false, "scrolled": false }, "outputs": [], "source": [ "class Market(object):\n", " current = 0\n", " def __init__(self, thickness, basket, bank, max_price, steps):\n", " self.basket = basket\n", " self.participants = []\n", " self.bank = bank\n", " self.max_price = max_price # government intervention! This is the price ceiling that the government sets\n", " self.steps = steps # how many steps to break down from 0 to max price\n", " for i in range(thickness):\n", " endowment = random.gauss(50,10)\n", " preferences = np.random.randint(1,10, self.basket)\n", " skills = np.random.randint(1,10, self.basket)\n", " investment_pref = np.random.randint(0, 10)\n", " savings_pref = np.random.randint(0,10)\n", " participant = Person(bank,endowment, skills, preferences, investment_pref, savings_pref)\n", " self.participants.append(participant)\n", " def demand(self, priceMatrix):\n", " quantities = np.zeros((priceMatrix.shape[0], priceMatrix.shape[1]))\n", " for participant in self.participants:\n", " quantities += participant.consume(priceMatrix, self.current)\n", " return quantities\n", " \n", " def supply(self, priceMatrix):\n", " quantities = np.zeros((priceMatrix.shape[0], priceMatrix.shape[1]))\n", " for participant in self.participants:\n", " q = participant.produce(priceMatrix, self.current)\n", " quantities += q\n", " return quantities \n", "\n", " \n", " def market_price(self):\n", " prices = (np.arange(self.steps) + 1) * self.max_price / float(self.steps)\n", " priceMatrix = np.tile(prices.reshape(self.steps,1), (1, self.basket))\n", "# priceMatrix = cartesian([prices for i in range(m.basket)])\n", " \n", " supply = self.supply(priceMatrix)\n", " demand = self.demand(priceMatrix)\n", " surplus = supply - demand\n", " min_surplus = np.argmin(np.abs(surplus),0)\n", " eq_prices = []\n", " for i in range(self.basket):\n", " eq_prices.append(priceMatrix[min_surplus[i]][i])\n", " return np.array(eq_prices)\n", "\n", " def step(self):\n", " eq_prices = self.market_price()\n", " for participant in self.participants:\n", " if not participant.defaulted:\n", " central_bank.holding -= participant.savings\n", " participant.step(eq_prices, m.current)\n", " central_bank.holding += participant.savings\n", " \n", " m.current += 1\n", " \n", " def m1(self):\n", " money = 0.0\n", " for participant in self.participants:\n", " if not participant.defaulted and participant.savings > 0:\n", " money += participant.savings\n", " return money\n", " def m2(self):\n", " money = 0.0\n", " for participant in self.participants:\n", " if not participant.defaulted:\n", " for loan in participant.loans:\n", " money += loan.amount * (1+loan.interest_rate)\n", " return money\n", "\n", " def actives(self):\n", " return sum([1 for p in self.participants if not p.defaulted])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The market is quite self explanatory - at every time period, it calculates the equilibrium price. Remember, the citizens of Dystopianistan are [price takers](http://www.investopedia.com/terms/p/pricetaker.asp) with InvisbleHandatron being the price maker. \n", "\n", "Here we see how the price is calculated:" ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "collapsed": false, "scrolled": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Market Price: [ 7. 8. 8. 8.]\n" ] }, { "data": { "application/javascript": [ "/* Put everything inside the global mpl namespace */\n", "window.mpl = {};\n", "\n", "mpl.get_websocket_type = function() {\n", " if (typeof(WebSocket) !== 'undefined') {\n", " return WebSocket;\n", " } else if (typeof(MozWebSocket) !== 'undefined') {\n", " return MozWebSocket;\n", " } else {\n", " alert('Your browser does not have WebSocket support.' +\n", " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", " 'Firefox 4 and 5 are also supported but you ' +\n", " 'have to enable WebSockets in about:config.');\n", " };\n", "}\n", "\n", "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n", " this.id = figure_id;\n", "\n", " this.ws = websocket;\n", "\n", " this.supports_binary = (this.ws.binaryType != undefined);\n", "\n", " if (!this.supports_binary) {\n", " var warnings = document.getElementById(\"mpl-warnings\");\n", " if (warnings) {\n", " warnings.style.display = 'block';\n", " warnings.textContent = (\n", " \"This browser does not support binary websocket messages. \" +\n", " \"Performance may be slow.\");\n", " }\n", " }\n", "\n", " this.imageObj = new Image();\n", "\n", " this.context = undefined;\n", " this.message = undefined;\n", " this.canvas = undefined;\n", " this.rubberband_canvas = undefined;\n", " this.rubberband_context = undefined;\n", " this.format_dropdown = undefined;\n", "\n", " this.image_mode = 'full';\n", "\n", " this.root = $('
');\n", " this._root_extra_style(this.root)\n", " this.root.attr('style', 'display: inline-block');\n", "\n", " $(parent_element).append(this.root);\n", "\n", " this._init_header(this);\n", " this._init_canvas(this);\n", " this._init_toolbar(this);\n", "\n", " var fig = this;\n", "\n", " this.waiting = false;\n", "\n", " this.ws.onopen = function () {\n", " fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n", " fig.send_message(\"send_image_mode\", {});\n", " fig.send_message(\"refresh\", {});\n", " }\n", "\n", " this.imageObj.onload = function() {\n", " if (fig.image_mode == 'full') {\n", " // Full images could contain transparency (where diff images\n", " // almost always do), so we need to clear the canvas so that\n", " // there is no ghosting.\n", " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", " }\n", " fig.context.drawImage(fig.imageObj, 0, 0);\n", " };\n", "\n", " this.imageObj.onunload = function() {\n", " this.ws.close();\n", " }\n", "\n", " this.ws.onmessage = this._make_on_message_function(this);\n", "\n", " this.ondownload = ondownload;\n", "}\n", "\n", "mpl.figure.prototype._init_header = function() {\n", " var titlebar = $(\n", " '
');\n", " var titletext = $(\n", " '
');\n", " titlebar.append(titletext)\n", " this.root.append(titlebar);\n", " this.header = titletext[0];\n", "}\n", "\n", "\n", "\n", "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n", "\n", "}\n", "\n", "\n", "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n", "\n", "}\n", "\n", "mpl.figure.prototype._init_canvas = function() {\n", " var fig = this;\n", "\n", " var canvas_div = $('
');\n", "\n", " canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n", "\n", " function canvas_keyboard_event(event) {\n", " return fig.key_event(event, event['data']);\n", " }\n", "\n", " canvas_div.keydown('key_press', canvas_keyboard_event);\n", " canvas_div.keyup('key_release', canvas_keyboard_event);\n", " this.canvas_div = canvas_div\n", " this._canvas_extra_style(canvas_div)\n", " this.root.append(canvas_div);\n", "\n", " var canvas = $('');\n", " canvas.addClass('mpl-canvas');\n", " canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n", "\n", " this.canvas = canvas[0];\n", " this.context = canvas[0].getContext(\"2d\");\n", "\n", " var rubberband = $('');\n", " rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n", "\n", " var pass_mouse_events = true;\n", "\n", " canvas_div.resizable({\n", " start: function(event, ui) {\n", " pass_mouse_events = false;\n", " },\n", " resize: function(event, ui) {\n", " fig.request_resize(ui.size.width, ui.size.height);\n", " },\n", " stop: function(event, ui) {\n", " pass_mouse_events = true;\n", " fig.request_resize(ui.size.width, ui.size.height);\n", " },\n", " });\n", "\n", " function mouse_event_fn(event) {\n", " if (pass_mouse_events)\n", " return fig.mouse_event(event, event['data']);\n", " }\n", "\n", " rubberband.mousedown('button_press', mouse_event_fn);\n", " rubberband.mouseup('button_release', mouse_event_fn);\n", " // Throttle sequential mouse events to 1 every 20ms.\n", " rubberband.mousemove('motion_notify', mouse_event_fn);\n", "\n", " rubberband.mouseenter('figure_enter', mouse_event_fn);\n", " rubberband.mouseleave('figure_leave', mouse_event_fn);\n", "\n", " canvas_div.on(\"wheel\", function (event) {\n", " event = event.originalEvent;\n", " event['data'] = 'scroll'\n", " if (event.deltaY < 0) {\n", " event.step = 1;\n", " } else {\n", " event.step = -1;\n", " }\n", " mouse_event_fn(event);\n", " });\n", "\n", " canvas_div.append(canvas);\n", " canvas_div.append(rubberband);\n", "\n", " this.rubberband = rubberband;\n", " this.rubberband_canvas = rubberband[0];\n", " this.rubberband_context = rubberband[0].getContext(\"2d\");\n", " this.rubberband_context.strokeStyle = \"#000000\";\n", "\n", " this._resize_canvas = function(width, height) {\n", " // Keep the size of the canvas, canvas container, and rubber band\n", " // canvas in synch.\n", " canvas_div.css('width', width)\n", " canvas_div.css('height', height)\n", "\n", " canvas.attr('width', width);\n", " canvas.attr('height', height);\n", "\n", " rubberband.attr('width', width);\n", " rubberband.attr('height', height);\n", " }\n", "\n", " // Set the figure to an initial 600x600px, this will subsequently be updated\n", " // upon first draw.\n", " this._resize_canvas(600, 600);\n", "\n", " // Disable right mouse context menu.\n", " $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n", " return false;\n", " });\n", "\n", " function set_focus () {\n", " canvas.focus();\n", " canvas_div.focus();\n", " }\n", "\n", " window.setTimeout(set_focus, 100);\n", "}\n", "\n", "mpl.figure.prototype._init_toolbar = function() {\n", " var fig = this;\n", "\n", " var nav_element = $('
')\n", " nav_element.attr('style', 'width: 100%');\n", " this.root.append(nav_element);\n", "\n", " // Define a callback function for later on.\n", " function toolbar_event(event) {\n", " return fig.toolbar_button_onclick(event['data']);\n", " }\n", " function toolbar_mouse_event(event) {\n", " return fig.toolbar_button_onmouseover(event['data']);\n", " }\n", "\n", " for(var toolbar_ind in mpl.toolbar_items) {\n", " var name = mpl.toolbar_items[toolbar_ind][0];\n", " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", " var image = mpl.toolbar_items[toolbar_ind][2];\n", " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", "\n", " if (!name) {\n", " // put a spacer in here.\n", " continue;\n", " }\n", " var button = $('');\n", " button.click(method_name, toolbar_event);\n", " button.mouseover(tooltip, toolbar_mouse_event);\n", " nav_element.append(button);\n", " }\n", "\n", " // Add the status bar.\n", " var status_bar = $('');\n", " nav_element.append(status_bar);\n", " this.message = status_bar[0];\n", "\n", " // Add the close button to the window.\n", " var buttongrp = $('
');\n", " var button = $('');\n", " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", " button.mouseover('Stop Interaction', toolbar_mouse_event);\n", " buttongrp.append(button);\n", " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", " titlebar.prepend(buttongrp);\n", "}\n", "\n", "mpl.figure.prototype._root_extra_style = function(el){\n", " var fig = this\n", " el.on(\"remove\", function(){\n", "\tfig.close_ws(fig, {});\n", " });\n", "}\n", "\n", "mpl.figure.prototype._canvas_extra_style = function(el){\n", " // this is important to make the div 'focusable\n", " el.attr('tabindex', 0)\n", " // reach out to IPython and tell the keyboard manager to turn it's self\n", " // off when our div gets focus\n", "\n", " // location in version 3\n", " if (IPython.notebook.keyboard_manager) {\n", " IPython.notebook.keyboard_manager.register_events(el);\n", " }\n", " else {\n", " // location in version 2\n", " IPython.keyboard_manager.register_events(el);\n", " }\n", "\n", "}\n", "\n", "mpl.figure.prototype._key_event_extra = function(event, name) {\n", " var manager = IPython.notebook.keyboard_manager;\n", " if (!manager)\n", " manager = IPython.keyboard_manager;\n", "\n", " // Check for shift+enter\n", " if (event.shiftKey && event.which == 13) {\n", " this.canvas_div.blur();\n", " event.shiftKey = false;\n", " // Send a \"J\" for go to next cell\n", " event.which = 74;\n", " event.keyCode = 74;\n", " manager.command_mode();\n", " manager.handle_keydown(event);\n", " }\n", "}\n", "\n", "mpl.figure.prototype.handle_save = function(fig, msg) {\n", " fig.ondownload(fig, null);\n", "}\n", "\n", "\n", "mpl.find_output_cell = function(html_output) {\n", " // Return the cell and output element which can be found *uniquely* in the notebook.\n", " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", " // IPython event is triggered only after the cells have been serialised, which for\n", " // our purposes (turning an active figure into a static one), is too late.\n", " var cells = IPython.notebook.get_cells();\n", " var ncells = cells.length;\n", " for (var i=0; i= 3 moved mimebundle to data attribute of output\n", " data = data.data;\n", " }\n", " if (data['text/html'] == html_output) {\n", " return [cell, data, j];\n", " }\n", " }\n", " }\n", " }\n", "}\n", "\n", "// Register the function which deals with the matplotlib target/channel.\n", "// The kernel may be null if the page has been refreshed.\n", "if (IPython.notebook.kernel != null) {\n", " IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n", "}\n" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "#reset in case the below case \"no loans allowed\" is run previously\n", "def budget(self, current):\n", " '''\n", " The budget forms the constraint for a Person in his/her consumption\n", " '''\n", " repayments = 0.0\n", " for loan in self.loans:\n", " if current == loan.repay_at:\n", " repayments += loan.repay_amount()\n", " return self.creditworthiness()*self.consumerism + self.savings - (1-self.consumerism) * repayments\n", "Person.budget = budget\n", "central_bank = Bank(interest_rate, reserve_rate)\n", "\n", "n_steps = 60\n", "participant_count = 1000\n", "max_price = 100\n", "price_steps = 200\n", "basket = 7\n", "m = Market(participant_count, basket, central_bank, max_price, price_steps)\n", "print(central_bank.holding)\n", "print(len(m.participants))\n", "mp = m.market_price()\n", "\n", "fig,(ax1,ax2, ax3) = plt.subplots(3,1, sharex=True)\n", "ax1.set_xlabel('Time')\n", "ax1.set_ylabel('Money Supply')\n", "ax1.set_xlim(0,n_steps)\n", "ax1.set_ylim(0,700000)\n", "ax2.set_xlabel('Time')\n", "ax2.set_ylabel('Prices')\n", "ax2.set_xlim(0,n_steps)\n", "ax2.set_ylim(0,35)\n", "ax3.set_ylabel(\"Participants\")\n", "ax3.set_ylim(0,participant_count*1.1)\n", "\n", "\n", "steps =[0]\n", "m1s = [m.m1()]\n", "m2s = [m.m2()]\n", "participants = [participant_count]\n", "prices = [[p] for p in mp]\n", "colors = ['r', 'b', 'g', 'c', 'm', 'y','k']\n", "\n", "ax1.plot(steps, m1s, c='r')\n", "ax1.plot(steps, m2s, c='b')\n", "ax3.plot(steps, participants)\n", "for i, p in enumerate(prices):\n", " ax2.plot(steps, p, c=colors[i])\n", "fig.canvas.draw()\n", "\n", "for i in range(n_steps):\n", " mp = m.market_price()\n", " m.step()\n", "\n", " steps.append(i)\n", " m1 = m.m1()\n", " m2 = m.m2()\n", " \n", " # plotting shit\n", " m1s.append(m1)\n", " m2s.append(m2)\n", " for i, p in enumerate(mp):\n", " prices[i].append(p)\n", "\n", " participants.append(m.actives())\n", " \n", " maxY1 = max(m1s+m2s)*1.1\n", " maxY2 = max([p for pl in prices for p in pl ])*1.1\n", " ax1.set_ylim(0, maxY1)\n", " ax2.set_ylim(0, maxY2)\n", " plotmoney(ax1, steps, m1s, m2s)\n", " plotprices(ax2, steps, prices)\n", " plotpeople(ax3, steps, participants)\n", " \n", " fig.canvas.draw()\n", "# print(\"Time {}. M1: {}, M2: {}, Total: {}\".format(i, m1, m2, m1+m2))\n", " " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "What happens if loans cannot be made? " ] }, { "cell_type": "code", "execution_count": 11, "metadata": { "collapsed": false, "scrolled": true }, "outputs": [], "source": [ "import types\n", "central_bank2 = Bank(interest_rate, reserve_rate)\n", "central_bank2.can_lend = types.MethodType(lambda bank, amount: False, central_bank2) # never do this on prod servers!\n", "Person.budget = lambda self, cuurent: self.savings" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As you'll note - nothing interesting happens if there are no loans allowed. The economy stagnates. A stagnating economy usually spells civil unrest" ] }, { "cell_type": "code", "execution_count": 12, "metadata": { "collapsed": false, "scrolled": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "50120.42960338321\n", "1000\n" ] }, { "data": { "application/javascript": [ "/* Put everything inside the global mpl namespace */\n", "window.mpl = {};\n", "\n", "mpl.get_websocket_type = function() {\n", " if (typeof(WebSocket) !== 'undefined') {\n", " return WebSocket;\n", " } else if (typeof(MozWebSocket) !== 'undefined') {\n", " return MozWebSocket;\n", " } else {\n", " alert('Your browser does not have WebSocket support.' +\n", " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", " 'Firefox 4 and 5 are also supported but you ' +\n", " 'have to enable WebSockets in about:config.');\n", " };\n", "}\n", "\n", "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n", " this.id = figure_id;\n", "\n", " this.ws = websocket;\n", "\n", " this.supports_binary = (this.ws.binaryType != undefined);\n", "\n", " if (!this.supports_binary) {\n", " var warnings = document.getElementById(\"mpl-warnings\");\n", " if (warnings) {\n", " warnings.style.display = 'block';\n", " warnings.textContent = (\n", " \"This browser does not support binary websocket messages. \" +\n", " \"Performance may be slow.\");\n", " }\n", " }\n", "\n", " this.imageObj = new Image();\n", "\n", " this.context = undefined;\n", " this.message = undefined;\n", " this.canvas = undefined;\n", " this.rubberband_canvas = undefined;\n", " this.rubberband_context = undefined;\n", " this.format_dropdown = undefined;\n", "\n", " this.image_mode = 'full';\n", "\n", " this.root = $('
');\n", " this._root_extra_style(this.root)\n", " this.root.attr('style', 'display: inline-block');\n", "\n", " $(parent_element).append(this.root);\n", "\n", " this._init_header(this);\n", " this._init_canvas(this);\n", " this._init_toolbar(this);\n", "\n", " var fig = this;\n", "\n", " this.waiting = false;\n", "\n", " this.ws.onopen = function () {\n", " fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n", " fig.send_message(\"send_image_mode\", {});\n", " fig.send_message(\"refresh\", {});\n", " }\n", "\n", " this.imageObj.onload = function() {\n", " if (fig.image_mode == 'full') {\n", " // Full images could contain transparency (where diff images\n", " // almost always do), so we need to clear the canvas so that\n", " // there is no ghosting.\n", " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", " }\n", " fig.context.drawImage(fig.imageObj, 0, 0);\n", " };\n", "\n", " this.imageObj.onunload = function() {\n", " this.ws.close();\n", " }\n", "\n", " this.ws.onmessage = this._make_on_message_function(this);\n", "\n", " this.ondownload = ondownload;\n", "}\n", "\n", "mpl.figure.prototype._init_header = function() {\n", " var titlebar = $(\n", " '
');\n", " var titletext = $(\n", " '
');\n", " titlebar.append(titletext)\n", " this.root.append(titlebar);\n", " this.header = titletext[0];\n", "}\n", "\n", "\n", "\n", "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n", "\n", "}\n", "\n", "\n", "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n", "\n", "}\n", "\n", "mpl.figure.prototype._init_canvas = function() {\n", " var fig = this;\n", "\n", " var canvas_div = $('
');\n", "\n", " canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n", "\n", " function canvas_keyboard_event(event) {\n", " return fig.key_event(event, event['data']);\n", " }\n", "\n", " canvas_div.keydown('key_press', canvas_keyboard_event);\n", " canvas_div.keyup('key_release', canvas_keyboard_event);\n", " this.canvas_div = canvas_div\n", " this._canvas_extra_style(canvas_div)\n", " this.root.append(canvas_div);\n", "\n", " var canvas = $('');\n", " canvas.addClass('mpl-canvas');\n", " canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n", "\n", " this.canvas = canvas[0];\n", " this.context = canvas[0].getContext(\"2d\");\n", "\n", " var rubberband = $('');\n", " rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n", "\n", " var pass_mouse_events = true;\n", "\n", " canvas_div.resizable({\n", " start: function(event, ui) {\n", " pass_mouse_events = false;\n", " },\n", " resize: function(event, ui) {\n", " fig.request_resize(ui.size.width, ui.size.height);\n", " },\n", " stop: function(event, ui) {\n", " pass_mouse_events = true;\n", " fig.request_resize(ui.size.width, ui.size.height);\n", " },\n", " });\n", "\n", " function mouse_event_fn(event) {\n", " if (pass_mouse_events)\n", " return fig.mouse_event(event, event['data']);\n", " }\n", "\n", " rubberband.mousedown('button_press', mouse_event_fn);\n", " rubberband.mouseup('button_release', mouse_event_fn);\n", " // Throttle sequential mouse events to 1 every 20ms.\n", " rubberband.mousemove('motion_notify', mouse_event_fn);\n", "\n", " rubberband.mouseenter('figure_enter', mouse_event_fn);\n", " rubberband.mouseleave('figure_leave', mouse_event_fn);\n", "\n", " canvas_div.on(\"wheel\", function (event) {\n", " event = event.originalEvent;\n", " event['data'] = 'scroll'\n", " if (event.deltaY < 0) {\n", " event.step = 1;\n", " } else {\n", " event.step = -1;\n", " }\n", " mouse_event_fn(event);\n", " });\n", "\n", " canvas_div.append(canvas);\n", " canvas_div.append(rubberband);\n", "\n", " this.rubberband = rubberband;\n", " this.rubberband_canvas = rubberband[0];\n", " this.rubberband_context = rubberband[0].getContext(\"2d\");\n", " this.rubberband_context.strokeStyle = \"#000000\";\n", "\n", " this._resize_canvas = function(width, height) {\n", " // Keep the size of the canvas, canvas container, and rubber band\n", " // canvas in synch.\n", " canvas_div.css('width', width)\n", " canvas_div.css('height', height)\n", "\n", " canvas.attr('width', width);\n", " canvas.attr('height', height);\n", "\n", " rubberband.attr('width', width);\n", " rubberband.attr('height', height);\n", " }\n", "\n", " // Set the figure to an initial 600x600px, this will subsequently be updated\n", " // upon first draw.\n", " this._resize_canvas(600, 600);\n", "\n", " // Disable right mouse context menu.\n", " $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n", " return false;\n", " });\n", "\n", " function set_focus () {\n", " canvas.focus();\n", " canvas_div.focus();\n", " }\n", "\n", " window.setTimeout(set_focus, 100);\n", "}\n", "\n", "mpl.figure.prototype._init_toolbar = function() {\n", " var fig = this;\n", "\n", " var nav_element = $('
')\n", " nav_element.attr('style', 'width: 100%');\n", " this.root.append(nav_element);\n", "\n", " // Define a callback function for later on.\n", " function toolbar_event(event) {\n", " return fig.toolbar_button_onclick(event['data']);\n", " }\n", " function toolbar_mouse_event(event) {\n", " return fig.toolbar_button_onmouseover(event['data']);\n", " }\n", "\n", " for(var toolbar_ind in mpl.toolbar_items) {\n", " var name = mpl.toolbar_items[toolbar_ind][0];\n", " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", " var image = mpl.toolbar_items[toolbar_ind][2];\n", " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", "\n", " if (!name) {\n", " // put a spacer in here.\n", " continue;\n", " }\n", " var button = $('