[
  {
    "path": "Procfile",
    "content": "web: gunicorn app:app --log-file=-\n"
  },
  {
    "path": "README.md",
    "content": "## Deprecated\n\nThis project is no longer maintained."
  },
  {
    "path": "app.py",
    "content": "from flask  import Flask, redirect, url_for, render_template, request\n\nimport threading\nimport signal\nimport json\nimport os\nimport time\n\n# Locals\nfrom scripts import mongio, apis, settings\n\napp = Flask(__name__)\n\n# ======== Tasks =========================================================== #\ndef refresh_positions(exchanges):\n    E = []\n    for exchange in exchanges:\n        e = getattr(apis, exchange.title())\n        key = getattr(settings, '{0}_key'.format(exchange))\n        secret = getattr(settings, '{0}_secret'.format(exchange))\n        try:\n            passphrase = getattr(settings, '{0}_passphrase'.format(exchange))\n            E.append(e(key, secret, passphrase).get_balances())\n        except AttributeError:\n            E.append(e(key, secret).get_balances())\n    return sum(E).positions()\n\n# ======== Routing =========================================================== #\n\n@app.route('/', methods=['GET'])\ndef index():\n    if 'positions' not in mongio.db.collection_names():\n        mongio.db.create_collection('positions')\n        mongio.save(settings.mongo_portfolio, 'positions', [])\n        mongio.save(settings.mongo_portfolio, 'equity', {'btc':{}, 'usd':{}})\n\n    positions = mongio.load(settings.mongo_portfolio, 'positions')\n    equity = mongio.load(settings.mongo_portfolio, 'equity')\n    return render_template('index.html', p=positions, e=equity)\n\n@app.route('/refresh', methods=['POST'])\ndef refresh():\n    positions = refresh_positions(settings.exchanges)\n    mongio.save(settings.mongo_portfolio, 'positions', positions)\n    positions = mongio.load(settings.mongo_portfolio, 'positions')\n    return json.dumps({'success': True, 'positions': positions})\n\n# ======== Main ============================================================== #\n\nif __name__ == \"__main__\":\n    app.run()"
  },
  {
    "path": "requirements.txt",
    "content": "Flask\ngunicorn\npymongo\npandas\nrequests\ndatetime\nschedule\n"
  },
  {
    "path": "runtime.txt",
    "content": "python-3.6.0\n"
  },
  {
    "path": "scripts/__init__.py",
    "content": "\n"
  },
  {
    "path": "scripts/apis.py",
    "content": "import sys\nimport time\nimport hmac\nimport hashlib\nimport urllib\nimport requests\nimport json\nimport base64\n\n# Local Files\nsys.path.append(\"..\")\nfrom scripts import models\n\n\nclass Gemini:\n    def __init__(self, api_key, api_secret):\n        self.api_key = api_key\n        self.api_secret = api_secret\n\n    def raw_balances(self):\n        url = \"https://api.gemini.com/v1/balances\"\n        nonce = int(time.time() * 1000)\n        message_json = json.dumps({\"request\": \"/v1/balances\", \"nonce\": nonce})\n        message = base64.b64encode(message_json.encode())\n        signature = hmac.new(self.api_secret.encode(), message, hashlib.sha384).hexdigest()\n        headers = {'Content-Type': \"text/plain\",\n                   'Content-Length': \"0\",\n                   'X-GEMINI-APIKEY': self.api_key,\n                   'X-GEMINI-PAYLOAD': message,\n                   'X-GEMINI-SIGNATURE': signature,\n                   'Cache-Control': \"no-cache\"}\n        response = requests.request(\"POST\", url, headers=headers)\n        return response.json()\n\n    def get_balances(self):\n        E = models.Exchange('Gemini')\n        raw = self.raw_balances()\n        for t in raw:\n            balance = float(t['available'])\n            if balance > 0:\n                ticker = t['currency']\n                try:\n                    price = requests.get('https://api.gemini.com/v1/pubticker/{0}'.format(ticker + 'btc')).json()['last']\n                    E.tokens.append(models.Token(ticker, balance, 'Gemini', price=price))\n                except KeyError:\n                    E.tokens.append(models.Token(ticker, balance, 'Gemini'))\n        return E\n\n\nclass Binance:\n    def __init__(self, api_key, api_secret):\n        self.api_key = api_key\n        self.api_secret = api_secret\n\n    def raw_balances(self):\n        session = requests.session()\n        session.headers.update({'Accept': 'application/json','User-Agent': 'binance/python','X-MBX-APIKEY': self.api_key})\n        kwargs = {'params': {'timestamp': int(time.time()*1000)}}\n        query_string = urllib.parse.urlencode(kwargs['params'])\n        apisign = hmac.new(self.api_secret.encode('utf-8'), query_string.encode('utf-8'), hashlib.sha256).hexdigest()\n        kwargs['params']['signature'] = apisign\n        response = getattr(session, 'get')('https://api.binance.com/api/v3/account', timeout=10, **kwargs)\n        return response.json()['balances']\n\n    def get_balances(self):\n        E = models.Exchange('Binance')\n        prices = requests.get('https://api.binance.com//api/v1/ticker/allPrices').json()\n        prices = {i['symbol']: i['price'] for i in prices}\n        raw = self.raw_balances()\n        for t in raw:\n            balance = float(t['free'])\n            if balance > 0:\n                ticker = t['asset']\n                try:\n                    price = prices['{0}BTC'.format(ticker)]\n                    E.tokens.append(models.Token(ticker, balance, 'Binance', price=price))\n                except KeyError:\n                    E.tokens.append(models.Token(ticker, balance, 'Binance'))\n        return E\n\nclass Bittrex:\n    def __init__(self, api_key, api_secret):\n        self.api_key = api_key\n        self.api_secret = api_secret\n\n    def raw_balances(self):\n        nonce = str(int(time.time()*1000))\n        query_string = 'https://bittrex.com/api/v1.1/account/getbalances?'\n        query_string += 'apikey=' + self.api_key + \"&nonce=\" + nonce + '&'\n        apisign = hmac.new(self.api_secret.encode(), query_string.encode(), hashlib.sha512).hexdigest()\n        response = requests.get(query_string, headers={\"apisign\": apisign})\n        return response.json()['result']\n\n    def get_balances(self):\n        E = models.Exchange('Bittrex')\n        prices = requests.get('https://bittrex.com/api/v1.1/public/getmarketsummaries').json()['result']\n        prices = {i['MarketName']: i['Last'] for i in prices}\n        raw = self.raw_balances()\n        for t in raw:\n            balance = float(t['Available'])\n            if balance > 0:\n                ticker = t['Currency']\n                try:\n                    price = prices['BTC-{0}'.format(ticker)]\n                    E.tokens.append(models.Token(ticker, balance, 'Bittrex', price=price))\n                except KeyError:\n                    E.tokens.append(models.Token(ticker, balance, 'Bittrex'))\n        return E\n\nclass Poloniex:\n    def __init__(self, api_key, api_secret):\n        self.api_key = api_key\n        self.api_secret = api_secret\n\n    def raw_balances(self):\n        args = {'command': 'returnCompleteBalances'}\n        args['nonce'] = int(time.time()*1000000)\n        data = urllib.parse.urlencode(args)\n        sign = hmac.new(self.api_secret.encode('utf-8'), data.encode('utf-8'), hashlib.sha512)\n        response = requests.post('https://poloniex.com/tradingApi', data=args,\n                   headers={'Sign': sign.hexdigest(), 'Key': self.api_key}, timeout=10)\n        return response.json()\n\n    def get_balances(self):\n        E = models.Exchange('Poloniex')\n        raw = self.raw_balances()\n        for t in raw:\n            balance = float(raw[t]['available'])\n            if balance > 0:\n                value = float(raw[t]['btcValue'])\n                ticker = t\n                try:\n                    E.tokens.append(models.Token(ticker, balance, 'Poloniex', value=value))\n                except KeyError:\n                    E.tokens.append(models.Token(ticker, balance, 'Poloniex'))                \n        return E  \n\nclass Gdax:\n    def __init__(self, api_key, api_secret, api_passphrase):\n        self.api_key = api_key\n        self.api_secret = api_secret\n        self.api_passphrase = api_passphrase\n        \n    def raw_balances(self):\n        timestamp = str(time.time())\n        message = timestamp+'GET/accounts/'\n        message = message.encode('ascii')\n        hmac_key = base64.b64decode(self.api_secret)\n        signature = hmac.new(hmac_key, message, hashlib.sha256)\n        signature_b64 = base64.b64encode(signature.digest())\n        response = requests.get('https://api.gdax.com/accounts/', headers={\n            'Content-Type': 'Application/JSON',\n            'CB-ACCESS-SIGN': signature_b64,\n            'CB-ACCESS-TIMESTAMP': timestamp,\n            'CB-ACCESS-KEY': self.api_key,\n            'CB-ACCESS-PASSPHRASE': self.api_passphrase})\n        return response.json()\n\n    def get_balances(self):\n        E = models.Exchange('Gdax')\n        raw = self.raw_balances()\n        for t in raw:\n            balance = float(t['available'])\n            if balance > 0:\n                ticker = t['currency']\n                try:\n                    price = requests.get('https://api.gdax.com/products/{0}-BTC/ticker'.format(ticker)).json()['price']\n                    E.tokens.append(models.Token(ticker, balance, 'Gdax', price=price))\n                except KeyError:\n                    E.tokens.append(models.Token(ticker, balance, 'Gdax'))\n        return E\n"
  },
  {
    "path": "scripts/models.py",
    "content": "import requests\nimport copy\n\ndef get_performances():\n    data = requests.get('https://api.coinmarketcap.com/v1/ticker/').json()\n    p = {}\n    for ticker in data:\n        t = dict(ticker)\n        p[t['symbol']] = {'daily': t['percent_change_24h'],\n                          'weekly': t['percent_change_7d']}\n    return p\n\nclass Exchange:\n    def __init__(self, name):\n        self.name = name\n        self.tokens = []\n\n    def __str__(self):\n        string = \"Exchange: {0}\\n\".format(self.name)\n        for token in self.tokens:\n            string += \"{0} {1} = {2} BTC\\n\".format(token.balance, token.name, token.value)\n        return string\n\n    def __add__(self, other):\n        new = copy.deepcopy(self)\n        new.name += \"/\"+other.name\n        for t1 in new.tokens:\n            for t2 in other.tokens:\n                if t1 == t2:\n                    t1 += t2                \n        new.tokens += [t for t in other.tokens if t not in new.tokens]\n        return new\n\n    def __radd__(self, other):\n        if other is int(0):\n            return self\n        else:\n            return self.__add__(other)\n\n    def positions(self):\n        p = get_performances()\n        self.tokens.sort(key=lambda t: t.value, reverse=True)\n        total_btc = sum([token.value for token in self.tokens])\n        positions = []\n        for token in self.tokens:\n\n            # Ignore dust\n            if token.value < 0.001:\n                continue\n\n            try:\n                positions.append({'Token'      : token.name, \n                                  'Balance'    : round(token.balance, 2),\n                                  'Value'      : round(token.value, 5),\n                                  'Allocation' : round(100*token.value/total_btc, 3),\n                                  'Daily'      : p[token.name]['daily'],\n                                  'Weekly'     : p[token.name]['weekly'],\n                                  'Exchanges'  : \"/\".join(token.exchanges)})\n            except KeyError as e:\n                positions.append({'Token'      : token.name, \n                                  'Balance'    : round(token.balance, 2),\n                                  'Value'      : round(token.value, 5),\n                                  'Allocation' : round(100*token.value/total_btc, 3),\n                                  'Daily'      : '--',\n                                  'Weekly'     : '--',\n                                  'Exchanges'  : \"/\".join(token.exchanges)})\n        return positions\n\nclass Token:\n    def __init__(self, name, balance, exchange, value=None, price=None):\n        self.name = name.upper()\n        self.balance = float(balance)\n        self.exchanges = [exchange]\n        if name == \"BTC\":\n            self.value = float(balance)\n            return\n        if name == \"USD\" or name == \"USDT\":\n            self.value = 0\n            return\n        try:\n            self.value = float(value)\n        except TypeError:\n            try:\n                self.value = float(price)*float(balance)\n            except TypeError:\n                self.value = 0\n\n    def __str__(self):\n        return \"{0} {1} = {2} BTC\".format(self.balance, self.name, self.value)\n\n    def __eq__(self, other):\n        return self.name == other.name\n\n    def __add__(self, other):\n        self.balance += other.balance\n        self.value += other.value\n        self.exchanges += other.exchanges\n        return self\n"
  },
  {
    "path": "scripts/mongio.py",
    "content": "import pymongo\nimport json\nimport sys\n\n# Local Files\nsys.path.append(\"..\")\nfrom scripts import settings\n\nclient = pymongo.MongoClient(settings.mongo_server, settings.mongo_id)\ndb = client[settings.mongo_client]\ndb.authenticate(settings.mongo_user, settings.mongo_pass)\n\ndef save(account, datatype, data):\n    d = db.positions.find_one({'account': account})\n    if d is not None:\n        d[datatype] = json.dumps(data)\n        db.positions.save(d)\n    else:\n        db.positions.insert_one({'account': account, datatype: json.dumps(data)})\n\ndef load(account, datatype):\n    d = db.positions.find_one({'account': account})\n    return json.loads(d[datatype])\n"
  },
  {
    "path": "static/js/helpers.js",
    "content": "var rancol = function () {\n    return function (bg) {\n        var hue = Math.floor(Math.random() * 360);\n        var hsl = 'hsl('+hue+', 90%, 70%)'; // 100 87.5\n        function checkHex(v) {\n            return 1 === v.length ? '0'+v : v;\n        }\n        var data, r, g, b, a,\n        cnv = document.createElement('canvas'),\n        ctx = cnv.getContext('2d'),\n        alpha = /a\\(/.test(hsl),\n        output = {};\n        return cnv.width = cnv.height = 1,\n        bg && (ctx.fillStyle = bg, ctx.fillRect(0, 0, 1, 1)),\n        ctx.fillStyle = hsl,\n        ctx.fillRect(0, 0, 1, 1),\n        data = ctx.getImageData(0, 0, 1, 1).data,\n        r = data[0],\n        g = data[1],\n        b = data[2],\n        a = (data[3] / 255).toFixed(2),\n        output.hex = '#'+checkHex(r.toString(16))+checkHex(g.toString(16))+checkHex(b.toString(16)),\n        output.hex;\n    };\n}();\n\nfunction colorize(n) {\n    var colors = []\n    for (var i = 0; i < n; i++ ){\n      colors.push(rancol())\n    }\n    return colors\n}"
  },
  {
    "path": "templates/index.html",
    "content": "<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\">\n    <meta name=\"author\" content=\"Anthony Federico\">\n    <title>cryptoview</title>\n    <script src=\"https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js\"></script>\n    <script src=\"https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/4.0.8/handlebars.min.js\"></script>\n    <script src=\"https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.2.13/semantic.min.js\"></script>    \n    <script src=\"https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.1/Chart.bundle.min.js\"></script>\n    <script src=\"../static/js/stupidtable.min.js\"></script>\n    <script src=\"../static/js/helpers.js\"></script>\n    <link rel=\"stylesheet\" href=\"https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.2.10/semantic.css\">\n  </head>\n  \n  <style>\n  body {\n    background-color: #2b2b42;\n  }\n  </style>\n\n  <body>\n  <div class=\"two ui buttons\">\n    <button data-chart=\"equity-chart\" class=\"ui button charts\">Equity Chart</button>\n    <button data-chart=\"allocations-chart\" class=\"ui button charts\">Portfolio Allocations</button>\n  </div>\n  \n  <button id=\"refresh\" class=\"circular ui icon button\" style=\"position:absolute;left:18;top:50\">\n    <i id=\"refresh-icon\" class=\"icon refresh\"></i>\n  </button>\n  <br>\n  <br>\n  <div id=\"container\" style=\"width:75%;margin:auto\">\n    <canvas id=\"equity-chart\" width=\"300\" height=\"100\"></canvas>\n  </div>\n  <div id=\"container\" style=\"width:75%;margin:auto\">\n    <canvas id=\"allocations-chart\" width=\"300\" height=\"100\" style=\"display:none\" ></canvas>\n  </div>\n  <br>\n  <div id=\"positions\"></div>\n  </body>\n</html>\n\n<script>\n$(document).on(\"click\", \".charts\", function() {\n  c = $(this).attr('data-chart')\n  e = document.getElementsByClassName(\"charts\");\n  for (var i = 0; i < e.length; i++ ){\n    id = $(e[i]).attr('data-chart')\n    document.getElementById(id).style.display = \"none\"\n  }\n  document.getElementById(c).style.display = \"block\"\n})\n</script>\n\n<script>\nvar equity  = {{ e|tojson }}\nvar dayEquity = Object.keys(equity['btc'])\nvar btcEquity = Object.values(equity['btc'])\nvar usdEquity = Object.values(equity['usd'])\nnew Chart(document.getElementById(\"equity-chart\"), {\n  type: 'line',\n  data: {\n    labels: dayEquity,\n    datasets: [{ \n        data: btcEquity,\n        label: \"BTC\",\n        borderColor: \"#3e95cd\",\n        fill: false,\n        yAxisID: \"y-axis-1\"\n      }, {\n        data: usdEquity,\n        label: \"USD\",\n        borderColor: \"#c45850\",\n        fill: false,\n        yAxisID: \"y-axis-0\"\n      }\n    ]\n  },\n  options: {\n    legend: {\n      labels: {\n        fontColor: 'white'\n      }\n    },\n    scales: {\n      yAxes: [{\n        position: \"left\",\n        \"id\": \"y-axis-0\",\n      }, {\n        position: \"right\",\n        \"id\": \"y-axis-1\"\n      }]\n    }    \n  }\n});\n\nvar allocations = {{ p|tojson }}\nvar tokenAllocations = []\nvar valueAllocations = []\nfor (i = 0; i < allocations.length; i++) { \n    tokenAllocations.push(allocations[i]['Token'])\n    valueAllocations.push(allocations[i]['Value'])\n}\n\nnew Chart(document.getElementById(\"allocations-chart\"), {\n  type: 'doughnut',\n  data: {\n    labels: tokenAllocations,\n    datasets: [{ \n        labels: tokenAllocations,\n        backgroundColor: colorize(tokenAllocations.length),\n        data: valueAllocations,\n      }\n    ]\n  },\n  options: {\n    legend: {\n      labels: {\n        fontColor: 'white'\n        }\n    }\n  }\n});\n</script>\n\n\n{% raw %}\n<script id=\"positions-template\" type=\"text/x-handlebars-template\">\n  <table id=\"sortable\" class=\"ui sortable celled large inverted compact table\" style=\"background-color:#2b2b42\">\n    <thead>\n      <tr class=\"center aligned\">\n        <th data-sort=\"string\" style=\"color:#c04560\"><p>Ticker</p></th>\n        <th data-sort=\"float\" style=\"color:#c04560\"><p>Balance</p></th>\n        <th data-sort=\"float\" style=\"color:#c04560\"><p>Value</p></th>\n        <th data-sort=\"float\" style=\"color:#c04560\"><p>Allocation</p></th>\n        <th data-sort=\"float\" style=\"color:#c04560\"><p>Daily</p></th>\n        <th data-sort=\"float\" style=\"color:#c04560\"><p>Weekly</p></th>\n        <th style=\"color:#c04560\"><p>Exchanges</p></th>\n      </tr>\n    </thead>\n    <tbody>\n      {{#positions}}\n        <tr class=\"center aligned\">\n          <td><strong><p>{{Token}}</p></strong></td>\n          <td><i><p>{{Balance}}</p></i></td>\n          <td><p>{{Value}}</p></td>\n          <td><p>{{Allocation}}%</p></td>\n          <td><p>{{Daily}}%</p></td>\n          <td><p>{{Weekly}}%</p></td>\n          <td><p>{{Exchanges}}</p></td>\n        </tr>\n      {{/positions}}\n    </tbody>\n  </table>\n</script>\n{% endraw %}\n\n<script>\nvar source = $(\"#positions-template\").html()\nvar template = Handlebars.compile(source)\nvar positions  = {'positions': {{ p|tojson }}}\n$('#positions').html(template(positions))\n$(\"#sortable\").stupidtable()\n</script>\n\n<script>\n$(document).on(\"click\", \"#refresh\", function() {\n  document.getElementById(\"refresh-icon\").classList.add('loading')\n  $.post({\n    type: \"POST\",\n    url: \"/refresh\",\n    data: {},\n    success: function(response){\n      var success = JSON.parse(response)['success']\n      var positions = JSON.parse(response)['positions']\n      if (success) {\n        $('#positions').html(template({'positions': positions}))\n        $(\"#sortable\").stupidtable()\n        //refreshAllocations(positions)\n        document.getElementById(\"refresh-icon\").classList.remove('loading')\n      }\n    }\n  })\n})\n</script>\n\n<script>\nvar allocations = []\nvar tokens = []\nfunction refreshAllocations(p) {\n  var allocations = []\n  var tokens = []\n  for (var i = 0; i < p.length; ++i) {\n    allocations.push(p[i]['Allocation'])\n    tokens.push(p[i]['Token'])\n    if (i == p.length) {\n      chartAllocations.update_values([{values: allocations}], tokens);\n    }\n  }\n  let dataAllocations = {\n    labels: tokens,\n    datasets: [\n      {\n        title: \"Allocations\",\n        values: allocations\n      }\n    ]\n  };\n  let chartAllocations = new Chart({\n    parent: \"#allocations\",\n    data: dataAllocations,\n    type: 'percentage'\n  });\n}\n//refreshAllocations({{ p|tojson }})\n</script>"
  },
  {
    "path": "updaters.py",
    "content": "import requests\nimport datetime\nimport schedule\nimport time\n\n# Local Files\nfrom scripts import mongio, settings\n\ndef get_btc_price():\n    btc = requests.get('https://api.coinmarketcap.com/v1/ticker/bitcoin/').json()\n    return float(btc[0]['price_usd'])\n\ndef update_equity():\n    now = datetime.datetime.now().strftime(\"%Y-%m-%d\")\n    print('{0} | Updating equity'.format(now))\n    positions = mongio.load(settings.mongo_portfolio, 'positions')\n    btc = sum([p['Value'] for p in positions])\n    usd = btc*get_btc_price()\n    equity = mongio.load(settings.mongo_portfolio, 'equity')\n    equity['btc'][now] = round(btc, 3) \n    equity['usd'][now] = round(usd, 3)\n    mongio.save(settings.mongo_portfolio, 'equity', equity)\n\nif __name__ == \"__main__\":\n    print('Starting scheduler...')\n    schedule.every(15).minutes.do(update_equity)\n    while True:\n        schedule.run_pending()\n        time.sleep(1)"
  }
]