{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "import pandas as pd\n",
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "import seaborn as sns\n",
    "import random\n",
    "\n",
    "from scipy.stats import gamma\n",
    "\n",
    "%matplotlib inline"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Discounted integral 'Voting'"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Simple model proposed for implementing and testing a voting scheme\n",
    "\n",
    "- Assume a dynamic supply of governance tokens accessed by a bonding ETH (linear bonding curve)\n",
    "- Assume this tokens also represent a stake in a revenue generating process\n",
    "- The revenue generating process is has one parameter which is \"governed\" \n",
    "- The revenue generated is random and there is a true \"best parameter\" unknown to the voters which may change\n",
    "- The goal of the 'voting' system is for the selected parameter to trend toward the \"best parameter\" (even if it changes)\n",
    "- In this set up, voting is completely passive, \"votes\" are automatically determined by each agents belief state and counted according to their balance of the 'Tokens' that representing their voting capacity\n",
    "- An agent has the right to change their belief or preference at any time but the effect of their prior beliefs or prefences continues to influence the system, decaying in time according to the forgetfulness parameter\n",
    "- These tokens also represent their stake in the pool of Ether being generated by the revenue process\n",
    "\n",
    "This is a sensor fusion problem -- coordination problem. The environment, the pool of agents, the process, the actions and the system updates have been made mind-numbingly noisy in order to show the effect of the di"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Define the Revenue generating process\n",
    "def revenue(true_best_param,current_voted_param ):\n",
    "    #use an concave function with unique maximum as true_best_param = current_voted_param\n",
    "    base_scale = 1\n",
    "    scale = base_scale*np.exp(-(true_best_param-current_voted_param)**2)\n",
    "    shape = .5\n",
    "    return gamma(scale, shape).rvs()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 7740 characters]\n",
      "text/plain": [
       "<matplotlib.figure.Figure at 0x112cb64a8>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "#test the revenue random variable as a function of how right the param is\n",
    "N = 1000\n",
    "bins = 10\n",
    "d11=np.zeros(N)\n",
    "d12=np.zeros(N)\n",
    "d21=np.zeros(N)\n",
    "d110=np.zeros(N)\n",
    "d101=np.zeros(N)\n",
    "for i in range(N):\n",
    "    d11[i] = revenue(1,1)\n",
    "    d12[i] = revenue(1,2)\n",
    "    d21[i] = revenue(2,1)\n",
    "    d110[i] = revenue(1,10)\n",
    "    d101[i] = revenue(10,1)\n",
    "\n",
    "plt.hist(d11, bins, alpha=0.5, label='d11')\n",
    "plt.hist(d12, bins, alpha=0.5, label='d12')\n",
    "plt.hist(d21, bins, alpha=0.5, label='d21')\n",
    "plt.hist(d110, bins, alpha=0.5, label='d110')\n",
    "plt.hist(d101, bins, alpha=0.5, label='d101')\n",
    "\n",
    "plt.legend(loc='upper right')\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Lets assuming that all tokens in existence is given by T\n",
    "\n",
    "def bond_mint(eth, Eth, Tokens):\n",
    "    return eth*Tokens/Eth\n",
    "\n",
    "def burn_withdraw(tokens, Eth, Tokens):\n",
    "    return tokens*Eth/Tokens\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "#define a set of agents\n",
    "agents = {}\n",
    "\n",
    "#in this passive voting model agents maintain a belief or a preference as part of their state with respect\n",
    "#to the contract the 'token' came from\n",
    "#their belief or preference may be updated by them at will\n",
    "def add_agent(eth, tbparam, Eth, Tokens):\n",
    "    agents[str(len(agents))] = {\"eth\":eth,\n",
    "                           \"tokens\":0,\n",
    "                           \"param_belief\": 2*tbparam*np.random.uniform(),\n",
    "                           \"value_belief\":2*Tokens/Eth*np.random.uniform()}\n",
    "    \n",
    "\n",
    "def agent_acts(a, Eth, Tokens, r, p):\n",
    "    #update value belief\n",
    "    agents[a][\"value_belief\"] = 2*Tokens/Eth*np.random.uniform()\n",
    "    \n",
    "    #update tokens held -- buy or sell -- pretty naive with random belief and random amount\n",
    "    if agents[a][\"value_belief\"]<Tokens/Eth:\n",
    "        urv =np.random.uniform() \n",
    "        burn = agents[a][\"tokens\"]*urv\n",
    "        eth_out = burn_withdraw(burn, Eth, Tokens)\n",
    "        \n",
    "        agents[a][\"tokens\"] = agents[a][\"tokens\"]-burn\n",
    "        agents[a][\"eth\"] = agents[a][\"eth\"] + eth_out\n",
    "        Tokens = Tokens - burn\n",
    "        Eth = Eth - eth_out\n",
    "        \n",
    "    else:\n",
    "        \n",
    "        urv =np.random.uniform() \n",
    "        bond =agents[a][\"eth\"]*urv\n",
    "        mint = bond_mint(bond, Eth, Tokens)\n",
    "        \n",
    "        agents[a][\"tokens\"] = agents[a][\"tokens\"]+mint\n",
    "        agents[a][\"eth\"] = agents[a][\"eth\"] -bond\n",
    "        Tokens = Tokens + mint\n",
    "        Eth = Eth + bond\n",
    "        \n",
    "    #update believe by taking a tiny step along slope of observations\n",
    "    #only assumption is that the agent roughly estimates the direction of better returns from observed revenue events\n",
    "    agents[a][\"param_belief\"] = np.max([0,agents[a][\"param_belief\"] + np.random.uniform()*np.polyfit(p, r, 1)[0]])\n",
    "\n",
    "    #print(agents[a][\"value_belief\"])\n",
    "    return Eth, Tokens, agents[a]\n",
    "\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "K= 50000\n",
    "alpha=.95 #forgetfulness\n",
    "true_best_param = 5*np.ones(K) \n",
    "#throw a step change in there to mix things up\n",
    "#true_best_param[:int(K/2)] =.5*true_best_param[:int(K/2)]\n",
    "\n",
    "\n",
    "E=np.zeros(K)\n",
    "T=np.zeros(K)\n",
    "\n",
    "A=np.zeros(K)\n",
    "\n",
    "Eth =1\n",
    "Tokens =100\n",
    "E[0] = Eth\n",
    "T[0] = Tokens\n",
    "A[0] = 1\n",
    "\n",
    "agents = {}\n",
    "add_agent(gamma(5,1).rvs(), true_best_param[0], Eth, Tokens)\n",
    "\n",
    "votes=np.zeros(K)\n",
    "count=np.zeros(K)\n",
    "param=np.zeros(K)\n",
    "rev=np.zeros(K)\n",
    "\n",
    "n = np.zeros(K)\n",
    "n[0]=1\n",
    "\n",
    "for k in range(1,K):\n",
    "    Eth = E[k-1]\n",
    "    Tokens = T[k-1]\n",
    "    #resolve governance -- what is the belief of the param\n",
    "    votes[k] = (np.sum([agents[a][\"tokens\"]*agents[a][\"param_belief\"] for a in list(agents.keys())])+ alpha*votes[k-1])\n",
    "    count[k] = (np.sum([agents[a][\"tokens\"] for a in list(agents.keys())])+ alpha*count[k-1])\n",
    "    if count[k]>0:\n",
    "        param[k] = votes[k]/count[k]\n",
    "    else:\n",
    "        param[k] = 1\n",
    "    rev[k] = revenue(true_best_param[k],param[k])\n",
    "    Eth = Eth+rev[k]\n",
    "    \n",
    "    #new agents join\n",
    "    if np.random.uniform()< np.log10(K/k):\n",
    "        new = int(2*np.random.uniform())\n",
    "        for i in range(new):\n",
    "            add_agent(gamma(5,1).rvs(), true_best_param[k], Eth, Tokens)\n",
    "    n[k]=len(agents)\n",
    "        \n",
    "    #pick some to update on the order of log of agents\n",
    "    active = random.sample(list(agents.keys()), int(np.log2(len(agents))))\n",
    "    for a in active:\n",
    "        Eth, Tokens, val = agent_acts(a, Eth, Tokens, rev[:k+1], param[:k+1])\n",
    "        agents[a] = val\n",
    "    \n",
    "    A[k] = len(active)\n",
    "    E[k] = Eth\n",
    "    T[k] = Tokens\n",
    "    "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "49999"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "k"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[<matplotlib.lines.Line2D at 0x1a1d075470>]"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "[encoded data, 12764 characters]\n",
      "text/plain": [
       "<matplotlib.figure.Figure at 0x112f094a8>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "plt.plot(range(K),n)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(array([2.9647e+04, 1.4560e+04, 4.0430e+03, 1.1820e+03, 3.9800e+02,\n",
       "        1.0900e+02, 3.8000e+01, 1.4000e+01, 6.0000e+00, 3.0000e+00]),\n",
       " array([ 0.        ,  1.10725381,  2.21450762,  3.32176142,  4.42901523,\n",
       "         5.53626904,  6.64352285,  7.75077666,  8.85803046,  9.96528427,\n",
       "        11.07253808]),\n",
       " <a list of 10 Patch objects>)"
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "[encoded data, 6508 characters]\n",
      "text/plain": [
       "<matplotlib.figure.Figure at 0x112c9ae80>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "plt.hist(rev)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 37,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(array([0.00000000e+00, 0.00000000e+00, 0.00000000e+00, 0.00000000e+00,\n",
       "        0.00000000e+00, 0.00000000e+00, 0.00000000e+00, 0.00000000e+00,\n",
       "        0.00000000e+00, 0.00000000e+00, 0.00000000e+00, 0.00000000e+00,\n",
       "        8.04513681e-05, 0.00000000e+00, 0.00000000e+00, 1.60902736e-04,\n",
       "        5.09008724e-04, 1.28404393e-02, 9.21238778e-02, 5.70530723e-01,\n",
       "        2.71035714e+00, 6.61315611e+00, 0.00000000e+00, 0.00000000e+00,\n",
       "        0.00000000e+00, 0.00000000e+00, 0.00000000e+00, 2.41354104e-04,\n",
       "        0.00000000e+00, 0.00000000e+00, 0.00000000e+00, 0.00000000e+00,\n",
       "        0.00000000e+00, 0.00000000e+00, 0.00000000e+00, 0.00000000e+00,\n",
       "        0.00000000e+00, 0.00000000e+00, 0.00000000e+00]),\n",
       " array([-2.00000000e+00, -1.90000000e+00, -1.80000000e+00, -1.70000000e+00,\n",
       "        -1.60000000e+00, -1.50000000e+00, -1.40000000e+00, -1.30000000e+00,\n",
       "        -1.20000000e+00, -1.10000000e+00, -1.00000000e+00, -9.00000000e-01,\n",
       "        -8.00000000e-01, -7.00000000e-01, -6.00000000e-01, -5.00000000e-01,\n",
       "        -4.00000000e-01, -3.00000000e-01, -2.00000000e-01, -1.00000000e-01,\n",
       "         1.77635684e-15,  1.00000000e-01,  2.00000000e-01,  3.00000000e-01,\n",
       "         4.00000000e-01,  5.00000000e-01,  6.00000000e-01,  7.00000000e-01,\n",
       "         8.00000000e-01,  9.00000000e-01,  1.00000000e+00,  1.10000000e+00,\n",
       "         1.20000000e+00,  1.30000000e+00,  1.40000000e+00,  1.50000000e+00,\n",
       "         1.60000000e+00,  1.70000000e+00,  1.80000000e+00,  1.90000000e+00]),\n",
       " <a list of 39 Patch objects>)"
      ]
     },
     "execution_count": 37,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "[encoded data, 4956 characters]\n",
      "text/plain": [
       "<matplotlib.figure.Figure at 0x1a235e07b8>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "plt.hist((true_best_param-param)/true_best_param, bins=(np.arange(-1,1,.1)), density=True, alpha=.5)\n",
    "plt.hist((true_best_param-param)/true_best_param, bins=(np.arange(-1,1,.1)), density=True, weights = rev, alpha=.25)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Text(0.5,0,'Number of Agents')"
      ]
     },
     "execution_count": 27,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "[encoded data, 16296 characters]\n",
      "text/plain": [
       "<matplotlib.figure.Figure at 0x1a210c59e8>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "plt.semilogy(n, np.abs(true_best_param - param)/true_best_param, n, .1)\n",
    "plt.title(\"Logscale absolute percent error\")\n",
    "plt.labels([\"error\", \"10% reference\"])\n",
    "plt.xlabel(\"Number of Agents\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[<matplotlib.lines.Line2D at 0x1a1f219cc0>,\n",
       " <matplotlib.lines.Line2D at 0x1a1f219e80>]"
      ]
     },
     "execution_count": 21,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "[encoded data, 8416 characters]\n",
      "text/plain": [
       "<matplotlib.figure.Figure at 0x1a1d6a7550>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "plt.plot(range(K), true_best_param, range(K), param )"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[<matplotlib.lines.Line2D at 0x10cfd4b70>]"
      ]
     },
     "execution_count": 12,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "[encoded data, 11292 characters]\n",
      "text/plain": [
       "<matplotlib.figure.Figure at 0x1a1cebe0b8>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "plt.plot(range(K), E/T)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[<matplotlib.lines.Line2D at 0x10cff07b8>,\n",
       " <matplotlib.lines.Line2D at 0x112eaac18>]"
      ]
     },
     "execution_count": 13,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "[encoded data, 14292 characters]\n",
      "text/plain": [
       "<matplotlib.figure.Figure at 0x112cd96d8>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "plt.plot(range(K), E, range(K), T)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[<matplotlib.lines.Line2D at 0x10f2ccda0>]"
      ]
     },
     "execution_count": 14,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "[encoded data, 12732 characters]\n",
      "text/plain": [
       "<matplotlib.figure.Figure at 0x112d23e10>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "plt.plot(E, T)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<matplotlib.collections.PathCollection at 0x1a1d00b320>"
      ]
     },
     "execution_count": 15,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "[encoded data, 8976 characters]\n",
      "text/plain": [
       "<matplotlib.figure.Figure at 0x10f2b1358>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "plt.scatter(param,rev)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[4, 6, 0, 4]"
      ]
     },
     "execution_count": 16,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "[encoded data, 16392 characters]\n",
      "text/plain": [
       "<matplotlib.figure.Figure at 0x11013cef0>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "sns.kdeplot(param,rev)\n",
    "plt.axis([4,6,0,4])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<seaborn.axisgrid.JointGrid at 0x1a1e495278>"
      ]
     },
     "execution_count": 17,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "[encoded data, 21988 characters]\n",
      "text/plain": [
       "<matplotlib.figure.Figure at 0x1a1e480320>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "sns.jointplot(param, rev, kind=\"kde\", size=7, space=0, xlim=[4,6],ylim=[0,5])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
