Browse Source

First Commit

tripeur 4 years ago
commit
1089d0429b
6 changed files with 413 additions and 0 deletions
  1. 141 0
      .gitignore
  2. 53 0
      Coin.py
  3. 39 0
      app.py
  4. 70 0
      populate.py
  5. 33 0
      static/js/app.js
  6. 77 0
      templates/async_list.html

+ 141 - 0
.gitignore

@@ -0,0 +1,141 @@
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+share/python-wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+MANIFEST
+
+# PyInstaller
+#  Usually these files are written by a python script from a template
+#  before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.nox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*.cover
+*.py,cover
+.hypothesis/
+.pytest_cache/
+cover/
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+local_settings.py
+db.sqlite3
+db.sqlite3-journal
+
+# Flask stuff:
+instance/
+.webassets-cache
+
+# Scrapy stuff:
+.scrapy
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+.pybuilder/
+target/
+
+# Jupyter Notebook
+.ipynb_checkpoints
+
+# IPython
+profile_default/
+ipython_config.py
+
+# pyenv
+#   For a library or package, you might want to ignore these files since the code is
+#   intended to run in multiple environments; otherwise, check them in:
+# .python-version
+
+# pipenv
+#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
+#   However, in case of collaboration, if having platform-specific dependencies or dependencies
+#   having no cross-platform support, pipenv may install dependencies that don't work, or not
+#   install all needed dependencies.
+#Pipfile.lock
+
+# PEP 582; used by e.g. github.com/David-OConnor/pyflow
+__pypackages__/
+
+# Celery stuff
+celerybeat-schedule
+celerybeat.pid
+
+# SageMath parsed files
+*.sage.py
+
+# Environments
+.env
+.venv
+env/
+venv/
+ENV/
+env.bak/
+venv.bak/
+
+# Spyder project settings
+.spyderproject
+.spyproject
+
+# Rope project settings
+.ropeproject
+
+# mkdocs documentation
+/site
+
+# mypy
+.mypy_cache/
+.dmypy.json
+dmypy.json
+
+# Pyre type checker
+.pyre/
+
+# pytype static type analyzer
+.pytype/
+
+# Cython debug symbols
+cython_debug/
+
+# sqlite db
+.db

+ 53 - 0
Coin.py

@@ -0,0 +1,53 @@
+from sqlalchemy import create_engine, ForeignKey
+from sqlalchemy import Column, Date, Integer, String, DateTime, FLOAT
+from sqlalchemy.ext.declarative import declarative_base
+from sqlalchemy.orm import relationship, backref
+
+engine = create_engine('sqlite:///coins.db', echo=False)
+Base = declarative_base()
+ 
+########################################################################
+class Coin(Base):
+    """"""
+    __tablename__ = "coin"
+ 
+    row_id = Column(Integer, primary_key=True)
+    id = Column(String)
+    name = Column(String)
+    symbol = Column(String)
+    
+    #----------------------------------------------------------------------
+    def __init__(self, id, name, symbol):
+        """"""
+        self.id = id
+        self.name = name
+        self.symbol = symbol
+
+    def __repr__(self):
+        return "<Coin id='" + str(self.id) + "'>" 
+
+class CoinMarketPosition(Base):
+    """"""
+    __tablename__ = "coin_position"
+ 
+    row_id = Column(Integer, primary_key=True)
+    coin_id = Column(Integer, ForeignKey("coin.row_id"))
+    date = Column(DateTime)
+    total_volumes = Column(FLOAT)
+    market_caps = Column(FLOAT)
+    prices = Column(FLOAT)
+    
+    #----------------------------------------------------------------------
+    def __init__(self, coin_id, date, total_volumes, market_caps, prices):
+        """"""
+        self.coin_id = coin_id
+        self.date = date
+        self.total_volumes = total_volumes
+        self.market_caps = market_caps
+        self.prices = prices
+
+    def __repr__(self):
+        return "<CoinMarketPosition coin='" + str(self.coin_id) +"' date='" + str(self.date) + "'>"
+
+# create tables
+Base.metadata.create_all(engine)

+ 39 - 0
app.py

@@ -0,0 +1,39 @@
+from flask import Flask, render_template, jsonify
+from datetime import datetime
+from sqlalchemy import create_engine,func
+from sqlalchemy.orm import sessionmaker
+from Coin import Coin, CoinMarketPosition
+
+app = Flask(__name__)
+
+engine = create_engine('sqlite:///coins.db', echo=False, connect_args={'check_same_thread':False})
+
+# create a Session
+Session = sessionmaker(bind=engine)
+session = Session()
+
+def result50(date=datetime.now()):
+    result = []
+    for x in session.query(CoinMarketPosition,Coin).filter(func.Date(CoinMarketPosition.date) == date.date()).distinct(Coin.id).join(Coin).order_by(CoinMarketPosition.market_caps.desc()).limit(50).all():
+        result.append((x[1].symbol, x[0].market_caps))
+    return result
+    
+@app.route("/")
+@app.route('/top50async/<date>/')
+def top50async(date="2020-01-12"):
+    return render_template('async_list.html', date=date)
+
+@app.route('/top50json/<date>/')
+def top50json(date):
+    d = datetime.strptime(date, "%d-%m-%Y")
+    results = result50(d)
+    arr = [{'name':x[0],'marketCap':x[1]} for x in results]
+    return jsonify(arr)
+
+@app.route('/coin/<id>/')
+def coinHistory(id):
+    results = session.query(CoinMarketPosition).filter(CoinMarketPosition.coin_id==id).all()
+    return jsonify([ {'date':marketPos.date,'marketCap':marketPos.market_caps} for marketPos in results])
+
+if __name__ == '__main__':
+    app.run(debug=True)

+ 70 - 0
populate.py

@@ -0,0 +1,70 @@
+from datetime import datetime
+from tqdm import tqdm 
+from pandas import DataFrame
+from sqlalchemy import create_engine
+from sqlalchemy.orm import sessionmaker
+from pycoingecko import CoinGeckoAPI
+from Coin import Coin, CoinMarketPosition
+from requests import HTTPError
+import time
+from multiprocessing import Pool
+
+DATABASE_URI = 'sqlite:///coins.db'
+engine = create_engine('sqlite:///coins.db', echo=False)
+
+# create a Session
+Session = sessionmaker(bind=engine)
+session = Session()
+
+cg = CoinGeckoAPI()
+def importMarketPosition(coin:Coin):
+    engine = create_engine(DATABASE_URI, echo=False)
+
+    # create a Session
+    Session = sessionmaker(bind=engine)
+    session = Session()
+
+    # skip already bootstraped items
+    if session.query(CoinMarketPosition).filter(CoinMarketPosition.coin_id == coin.row_id).first() is not None:
+        return
+    try:
+        positions = cg.get_coin_market_chart_by_id(coin.id,"usd","max")
+    except (HTTPError) as e:
+        print("We wait 60s :(", e.response)
+        time.sleep(61)
+        positions = cg.get_coin_market_chart_by_id(coin.id,"usd","max")
+
+    df_prices = DataFrame(positions["prices"])
+    df_prices.columns=["date","prices"]
+
+    df_total_volumes = DataFrame(positions["total_volumes"])
+    df_total_volumes.columns=["date","total_volumes"]
+
+    df_market_caps = DataFrame(positions["market_caps"])
+    df_market_caps.columns=["date","market_caps"]
+
+    df = df_prices.set_index('date').join(df_market_caps.set_index('date'),"date",'outer').join(df_total_volumes.set_index('date'),"date",'outer')
+
+    for t,row in df.iterrows():
+        try:
+            market_position = CoinMarketPosition(coin.row_id,datetime.fromtimestamp(t/1000),row.total_volumes,row.market_caps,row.prices)
+            session.add(market_position)
+        except OSError as e:
+            print("ERROR", "Invalid data for coin ", coin.id,row.total_volumes,row.market_caps,row.prices)
+
+    session.commit()
+
+
+if __name__ == "__main__":
+    # Create objects
+    for c in cg.get_coins_list():
+        obj = Coin (c["id"], c["symbol"], c["name"])
+        if session.query(Coin).filter(Coin.id == obj.id).first() is None:
+            session.add(obj)
+    # Save the change made on the database.            
+    session.commit()
+
+    # collect market position  
+    with Pool(10) as p:
+        coin_list = session.query(Coin).order_by(Coin.row_id).all()
+        r = list(tqdm(p.imap(importMarketPosition, coin_list), total=len(coin_list)))

+ 33 - 0
static/js/app.js

@@ -0,0 +1,33 @@
+
+const coinTop50 = {
+    data() {
+      return {
+        date :document.getElementById("startDate").innerText,
+        items: [],
+      }
+    },
+    mounted() {
+      this.requestData(this.date)
+      const elems = document.querySelectorAll('.datepicker');
+      M.Datepicker.init(elems, {onClose:()=>{
+        this.date = this.datepicker.date.toISOString().slice(0,10)
+      }});
+    },
+    watch:{
+      date(newDate,oldDate){
+        this.requestData(newDate)
+      }
+    },
+    methods:{
+      requestData(sDate){
+        const d = new Date(sDate)
+        const url = `/top50json/${d.getDate()}-${("0"+(d.getMonth()+1)).slice(-2)}-${d.getFullYear()}/`;
+        axios.get(url)
+            .then(this.handleJsonUpdate)
+      },
+      handleJsonUpdate(response){
+        this.items = response.data
+      }
+    }
+  }
+Vue.createApp(coinTop50).mount('#app')

+ 77 - 0
templates/async_list.html

@@ -0,0 +1,77 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <!--Import Google Icon Font-->
+    <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
+    <!--Import materialize.css-->
+    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css">
+
+    <!--Let browser know website is optimized for mobile-->
+    <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
+    <style>
+      .flip-list-move {
+        transition: transform 1s;
+      }
+      .flip-list-enter-active, .flip-list-leave-active {
+        transition: all 1s;
+      }
+      .flip-list-enter, .flip-list-leave-to /* .list-leave-active below version 2.1.8 */ {
+        opacity: 0;
+        transform: translateX(30px);
+      }
+      .currency{
+        font-weight: bold;
+        white-space: nowrap;
+      }
+      .amount{
+        text-align: end;
+        width: 100%;
+      }
+      .amount::before{
+        content: "$ ";
+      }
+    </style>
+  </head>
+
+  <body>
+    
+    <div id="startDate" class="hide">{{date}}</div>
+    <div id="app">
+      <div class="row">
+        <nav class="nav-extended">
+          <div class="nav-wrapper blue-grey">
+            <div class="col s12">
+              <a class="brand-logo">Max app</a>
+            </div>
+          </div>
+        </nav>
+      </div>
+      <div class="container">
+        <form action="#">
+          <p class="range-field">
+            <label for="date">Select Date</label>
+            <input type="text"  id="date" v-model="date" class="datepicker">
+            <input type="range" min="1970-01-01" max="2021-03-14" />
+          </p>
+        </form>
+        <h4>Top 50 Cryptocurrency by market cap </h4>
+        {% raw %}
+        <transition-group name="flip-list" tag="div" class="row">
+          <div class=" col xl2 l3 m4 s6" v-for="item,k in items" :key="item.name">
+            <div class="card-panel blue-grey">
+              <div class="white-text currency">{{k+1}}. {{ item.name }}</div>
+              <div class="white-text amount">{{ Math.round(item.marketCap).toLocaleString() }}</div>
+             </div>
+          </div>
+        </transition-group>
+        {% endraw %}
+      </div>
+    </div>
+    
+    <!--JavaScript at end of body for optimized loading-->
+    <script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/js/materialize.min.js"></script>
+    <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
+    <script src="https://unpkg.com/vue@next"></script>
+    <script src="{{ url_for('static', filename='js/app.js')}}"></script>
+  </body>
+</html>