Stealing pages from the server...

Visualise Crypto and Twitter with SQL and FastAPI


Introduction

In this article, first, I’ll populate crypto database using Python and SQL. I retrieve the list of crypto coin assets, verify the data, and tackle any errors I encounter along the way. Second, I’ll talk about how to keep the database up to date with the latest prices, and retrieve daily data from the yahoo finance API. Third, a database for twitter data will be built as well. Finally, I’ll set up to build a web UI using FastAPI.

Crypto

What is cryptocurrency? Cryptocurrency is a form of payment that can be exchanged online for goods and services. Many companies have issued their own currencies, often called tokens, and these can be traded specifically for the good or service that the company provides. Think of them as you would arcade tokens or casino chips. You’ll need to exchange real currency for the cryptocurrency to access the good or service.

SQL

Before we jump into building a database for something, we need to clarify some general types of database activities in the basic SQL commands for them.

  • Data Definition Language (DDL)
    • CREATE: define a new table
    • ALTER: change the types of records for a table
    • DROP: remove a table
  • Data Manipulation Language (DML)
    • INSERT: add records to tables
    • UPDATE: change part of a record
    • DELETE: remove records from a table
  • Data Query Language (DQL)
    • SELECT: retrieve data
  • Data Control Language (DCL)
    • GRANT: give database privileges
    • REVOKE: take away privileges

Initialise Database

At the very begining, I create a database file called “crypto.db”, by typing touch crypto.db in the command line. I want to crawl the crypto coin data on a daily basis, so I create a list which contains the crypto coin I want to keep up to date.

symbols = [
	'BTC-USD', 'ETH-USD', 'BNB-USD', 'DOGE-USD', 'USDT-USD', 
	'LTC-USD', 'LINK-USD', 'USDC-USD', 'THETA-USD', 'XMR-USD'
]

cryptocurrencies = [
	'Bitcoin USD', 'Ethereum USD', 'Binance USD', 'Dogecoin USD', 'Tether USD', 
	'Litecoin USD', 'Chainlink USD', 'USDCoin USD', 'ThETA USD', 'Monero USD'
]

Create Table

Next, I will create two tables, one for symbol mapping to name of cryptocurrency, another for prices information.

import sqlite3

def create_table():
	connection = sqlite3.connect('crypto.db')
	cursor = connection.cursor()
	cursor.execute("""
		CREATE TABLE IF NOT EXISTS crypto (
			id INTEGER PRIMARY KEY, 
			symbol TEXT NOT NULL, 
			cryptocurrency TEXT NOT NULL
		);
	""")
	cursor.execute("""
		CREATE TABLE IF NOT EXISTS crypto_price (
			id INTEGER PRIMARY KEY, 
			crypto_id INTEGER, 
			date NOT NULL, 
			open NOT NULL, 
			high NOT NULL, 
			low NOT NULL, 
			close NOT NULL, 
			volume NOT NULL, 
			FOREIGN KEY (crypto_id) REFERENCES crypto (id)
		);
	""")
	connection.commit()

create_table()

Insert Symbols into Table

After creating the two tables, I insert the symbol information into the table crypto.

def insert_into_table():
	connection = sqlite3.connect('crypto.db')
	cursor = connection.cursor()

	for symbol, cryptocurrency in zip(symbols, cryptocurrencies):
		# Insert values into table only if they don't exist in a row.
		cursor.execute("""
			INSERT INTO crypto (symbol, cryptocurrency)
			SELECT ?, ?
			WHERE NOT EXISTS (
				SELECT symbol, cryptocurrency FROM crypto WHERE symbol=? AND cryptocurrency=?
			);
		""", (symbol, cryptocurrency, symbol, cryptocurrency))
	connection.commit()

insert_into_table()

Drop Table

(Optional) If you want to drop the table and re-create a new list of different cryptocurrencies, then you can use the function below.

def drop_table():
	connection = sqlite3.connect("crypto.db")
	cursor = connection.cursor()
	cursor.execute("""
		DROP TABLE crypto_price;
	""")
	cursor.execute("""
		DROP TABLE crypto;
	""")
	connection.commit()

Populate Prices

I will retrieve daily price data from yahoo finance API and populate it into the pre-build table crypto_price in the database. This sets up to begin building a web UI in the next part.

Insert Prices into Table

In this section, I insert the prices information, which I get it from yahoo finance API, into the table crypto_price.

import yfinance as yf

def populate_prices():
	connection = sqlite3.connect("crypto.db")
	connection.row_factory = sqlite3.Row
	cursor = connection.cursor()
	cursor.execute("""
		SELECT * FROM crypto;
	""")
	rows = cursor.fetchall()
	symbols = [row['symbol'] for row in rows]
	cryptocurrencies = [row['cryptocurrency'] for row in rows]
	crypto_dict = {row['symbol']: row['id'] for row in rows}

	for symbol, cryptocurrency in zip(symbols, cryptocurrencies):
		print(f"Processing symbol {cryptocurrency}")
		df = yf.download(symbol, progress=False)
		df = df.reset_index(drop=False)
		df.columns = df.columns.str.lower()
		crypto_id = crypto_dict[symbol]
		for idx, row in df.iterrows():
			d = str(row["date"])
			o = row["open"]
			h = row["high"]
			l = row["low"]
			c = row["close"]
			v = row["volume"]
			cursor.execute("""
				INSERT INTO crypto_price (crypto_id, date, open, high, low, close, volume)
				SELECT ?, ?, ?, ?, ?, ?, ?
				WHERE NOT EXISTS (
					SELECT * FROM crypto_price 
					WHERE crypto_id=? AND date=?
				);
			""", (crypto_id, d, o, h, l, c, v, crypto_id, d))
	connection.commit()

You can use DB Browser to check whether prices data is stored correctly.

Or input sqlite3 crypto.db in command line , and check it through some SQL command.

SELECT * FROM crypto_price

Web UI

In this section, I begin building a web frontend for the crypto coin price database. I cover the basics of FastAPI, a lightweight Python framework for buildings API’s and web applications. I use Semantic UI and jinja2 templates to quickly create a simple, but nice looking UI for our database.

FastAPI

The current folder and files should look like this:

G:.
|   app.py
|   crypto.db
|
+---templates
|       crypto_detail.html
|       index.html
|       layout.html

Create a file app.py with:

import sqlite3
from fastapi import FastAPI, Request
from fastapi.templating import Jinja2Templates 
app = FastAPI()
templates = Jinja2Templates(directory="templates")


@app.get("/")
def index(request: Request):
    connection = sqlite3.connect("crypto.db")
    connection.row_factory = sqlite3.Row
    cursor = connection.cursor()
    cursor.execute("""
        SELECT * FROM crypto ORDER BY symbol;
    """)
    rows = cursor.fetchall()
    return templates.TemplateResponse(
        "index.html", 
        {"request": request, "rows": rows})


@app.get("/crypto/{symbol}")
def crypto_detail(request: Request, symbol):
    connection = sqlite3.connect("crypto.db")
    connection.row_factory = sqlite3.Row
    cursor = connection.cursor()
    cursor.execute("""
        SELECT * FROM crypto WHERE symbol=?;
    """, (symbol, ))
    row = cursor.fetchone()

    cursor.execute("""
        SELECT * FROM crypto_price WHERE crypto_id=? ORDER BY date DESC
    """, (row['id'], ))
    bars = cursor.fetchall()

    return templates.TemplateResponse(
        "crypto_detail.html", 
        {"request": request, "row": row, "bars": bars})

where layout.html is like the following

<!DOCTYPE html>
<html>
<head>
	<title>crypto</title>
	<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/semantic-ui@2.4.2/dist/semantic.min.css">
	<script src="https://cdn.jsdelivr.net/npm/semantic-ui@2.4.2/dist/semantic.min.js"></script>
</head>
<body>
	<div class="ui container">
		{% block content %}
		{% endblock %}
	</div>
</body>
</html>

and index.html is like the following

{% extends "layout.html" %}

{% block content %}
	<h1>Crypto List</h1>
	<table class="ui selectable striped table">
	  <thead>
	    <tr>
	      <th>Symbol</th>
	      <th>Coin</th>
	    </tr>
	  </thead>
	  <tbody>
	  	{% for row in rows %}
		    <tr>
		      <td>{{ row.symbol }}</td>
		      <td><a href="/crypto/{{ row.symbol }}">{{ row.cryptocurrency }}</a></td>
		    </tr>
	    {% endfor %}
	  </tbody>
	</table>
{% endblock %}

and crypto_detail.html is like the following

{% extends "layout.html" %}

{% block content %}

	<h1>
		<a href="/">
			Crypto 
		</a>
		<i class="angle double right icon"></i>
		{{ row.cryptocurrency }} ({{ row.symbol }})
	</h1>

	<table class="ui selectable striped table">
	  <thead>
	    <tr>
	      <th>Date</th>
	      <th>Open</th>
	      <th>High</th>
	      <th>Low</th>
	      <th>Close</th>
	      <th>Volume</th>
	    </tr>
	  </thead>
	  <tbody>
	  	{% for bar in bars %}
		    <tr>
		      <td>{{ bar.date }}</td>
		      <td>{{ bar.open }}</td>
		      <td>{{ bar.high }}</td>
		      <td>{{ bar.low }}</td>
		      <td>{{ bar.close }}</td>
		      <td>{{ bar.volume }}</td>
		    </tr>
	    {% endfor %}
	  </tbody>
	</table>

{% endblock %}

Type in uvicorn app:app --reload, and open your browser at http://127.0.0.1:8000, then you can see the nice looking web UI.

If you click on any symbol under the ‘Coin’ column, you can get to its detailed page.

Add Tradingview Chart

TradingView Advanced Chart Widget is a free and powerful charting solution that easily embeds into any website. Simply adjust the settings and click Apply to see a preview, then copy the embed code and paste it into your site code. You can personalize the chart by modifying the default symbol, watchlist, adding tools for technical analysis and a lot more. You can even add hotlists or an economic calendar to make the widget into an entire analytics platform.

You can get the embedding code over here. Next, put this into crypto_detail.html.

{% extends "layout.html" %}

{% block content %}

	<h1>
		<a href="/">
			Crypto 
		</a>
		<i class="angle double right icon"></i>
		{{ row.cryptocurrency }} ({{ row.symbol }})
	</h1>

	<!-- TradingView Widget BEGIN -->
	<div class="tradingview-widget-container">
	  <div id="tradingview_05834"></div>
	  <div class="tradingview-widget-copyright"><a href="https://www.tradingview.com/symbols/NASDAQ-AAPL/" rel="noopener" target="_blank"><span class="blue-text">AAPL Chart</span></a> by TradingView</div>
	  <script type="text/javascript" src="https://s3.tradingview.com/tv.js"></script>
	  <script type="text/javascript">
	  new TradingView.widget(
	  {
	  "width": "100%",
	  "height": 610,
	  "symbol": "{{ ticker }}",
	  "interval": "D",
	  "timezone": "Europe/London",
	  "theme": "light",
	  "style": "1",
	  "locale": "en",
	  "toolbar_bg": "#f1f3f6",
	  "enable_publishing": false,
	  "allow_symbol_change": true,
	  "container_id": "tradingview_05834"
	}
	  );
	  </script>
	</div>
	<!-- TradingView Widget END -->

	<table class="ui selectable striped table">
	  <thead>
	    <tr>
	      <th>Date</th>
	      <th>Open</th>
	      <th>High</th>
	      <th>Low</th>
	      <th>Close</th>
	      <th>Volume</th>
	    </tr>
	  </thead>
	  <tbody>
	  	{% for bar in bars %}
		    <tr>
		      <td>{{ bar.date }}</td>
		      <td>{{ bar.open }}</td>
		      <td>{{ bar.high }}</td>
		      <td>{{ bar.low }}</td>
		      <td>{{ bar.close }}</td>
		      <td>{{ bar.volume }}</td>
		    </tr>
	    {% endfor %}
	  </tbody>
	</table>

{% endblock %}

Twitter

Twitter is many things. It is a special place to vent and share ideas in the form of memes and tweetstorms, diatribes and inquisitions. It contains a section to catch up on sports, worldwide events, breaking news — everything. So, I determine to scrape the data from the people to follow on crypto information on Twitter. Some user account I follow on is from this website.

Build the Index Page

There’ll be somewhere different in the home page, let’s change it a little bit in layout.html.

<!DOCTYPE html>
<html>
<head>
	<title>crypto</title>
	<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/semantic-ui@2.4.2/dist/semantic.min.css">
	<script src="https://cdn.jsdelivr.net/npm/semantic-ui@2.4.2/dist/semantic.min.js"></script>
	<script type="text/javascript">
		$('.ui .item').tab();
	</script>
	<script type="text/javascript">
	$(document).ready(function(){
	    $(document).on('click','.dropdown .item',function(e){
	        $('.ui .item').removeClass('active');
	        $(this).addClass('active');
	});
	});
	</script>
</head>
<body>
	<div class="ui secondary  menu">
	  <a href="/" class="item">
	    Crypto
	  </a>
	  <a href="/twitter" class="item">
	    Twitter
	  </a>
	  <a href="/news" class="item">
	    News
	  </a>
	  <div class="right menu">
	    <div class="item">
	      <div class="ui icon input">
	        <input type="text" placeholder="Search...">
	        <i class="search link icon"></i>
	      </div>
	    </div>
	    <a class="ui item">
	      Logout
	    </a>
	  </div>
	</div>
	<div class="ui container">
		{% block content %}
		{% endblock %}
	</div>
</body>
</html>

Build the Database Table

Below is the list of people I want to keep in the database.

usernames = [
	"binance", "VitalikButerin", "aantonop", "ethereumJoseph", "coinbase", 
	"naval", "tayvano_", "NickSzabo4", "simondlr", "cburniske", 
	"AmberBaldet", "koeppelmann", "jwolpert", "iam_preethi", "lrettig", 
	"ricburton", "el33th4xor", "bcrypt", "evan_van_ness", "mikeraymcdonald", 
	"FEhrsam", "laurashin", "AriDavidPaul", "avsa", "0xstark", 
	"JohnLilic", "Disruptepreneur", "wheatpond", "leashless", "ethereum", 
	"APompliano", "twobitidiot", "trentmc0", "Melt_Dem", "Bitcoin", 
	"brian_armstrong", "nlw", "elonmusk", "DocumentingBTC", "BTCTN"
]

It’s basically the same to build a table for twitter data as previous.

def create_tweet_table():
	connection = sqlite3.connect('crypto.db')
	cursor = connection.cursor()
	cursor.execute("""
		CREATE TABLE IF NOT EXISTS twitter (
			id INTEGER PRIMARY KEY, 
			username TEXT NOT NULL
		);
	""")
	cursor.execute("""
		CREATE TABLE IF NOT EXISTS twitter_tweet (
			id INTEGER PRIMARY KEY, 
			username_id INTEGER, 
			date NOT NULL, 
			tweet TEXT NOT NULL, 
			username TEXT NOT NULL, 
			replies_count INTEGER NOT NULL, 
			retweets_count INTEGER NOT NULL, 
			likes_count INTEGER NULL,  
			FOREIGN KEY (username_id) REFERENCES twitter (id)
		);
	""")
	connection.commit()


def insert_twitter_username_into_table():
	connection = sqlite3.connect('crypto.db')
	cursor = connection.cursor()

	for username in usernames:
		cursor.execute("""
			INSERT INTO twitter (username)
			SELECT ?
			WHERE NOT EXISTS (
				SELECT username FROM twitter WHERE username=?
			);
		""", (username, username))
	connection.commit()

Twint

Twint is an advanced Twitter scraping tool written in Python that allows for scraping Tweets from Twitter profiles without using Twitter’s API.

Twint utilizes Twitter’s search operators to let you scrape Tweets from specific users, scrape Tweets relating to certain topics, hashtags & trends, or sort out sensitive information from Tweets like e-mail and phone numbers.

Set up and Installation

It must be some challenge installing twint, I will guide you through this.

  1. pip install --upgrade aiohttp_socks
  2. pip3 install --upgrade -e git+https://github.com/twintproject/twint.git@origin/master#egg=twint
  3. In orser to solve OSError: [WinError 87] The parameter is incorrect. please open output.py in ./src/twint/twint first. Make the following changes to fixes this issue:
  • Add (line#9):

    import string
  • Replace (line#123):

    word = ''
    for i in output:
    	if i in string.printable:
    		word = word + i
    print(word.replace('\n', ' '))

Populate Tweets into Database

Let’s start scraping tweets using twint, this may take a large amount of time the first time. For your information, you have to set initialise=True in insert_into_twitter_tweet_table() the first time to fetch all the tweets from each user account. After this, every time you re-scrape the tweets, you only have to set the delta parameter depends on the time period you want to crawl the twitter data. If you’re a MacOS user, you can use crontab with setting schedule using this website, and if you’re a windows user, you can use the built-in Task Scheduler. If you don’t want to use Task Scheduler, you can use the Windows Subsystem for Linux which will allow you to use cron jobs like on Linux.

import os
import sqlite3
import pandas as pd
from datetime import timedelta
from datetime import date as dt


def scrape_tweet(username, since="2012-01-01"):
	os.system(f"twint -u {username} --since {since} -o file.csv --csv")
	df = pd.read_csv("file.csv", sep="\t")
	df = df[[
		"date", "id", "username", "tweet", "replies_count", 
		"retweets_count", "likes_count"
	]]
	df.columns = [
		"date", "tweet_id", "username", "tweet", 
		"replies_count", "retweets_count", "likes_count"
	]
	os.system("rm file.csv")
	return df


def insert_into_twitter_tweet_table(initialise=False, delta=1):
	connection = sqlite3.connect("crypto.db")
	connection.row_factory = sqlite3.Row
	cursor = connection.cursor()
	cursor.execute("""
		SELECT * FROM twitter;
	""")
	rows = cursor.fetchall()
	usernames = [row['username'] for row in rows]
	username_dict = {row['username']: row['id'] for row in rows}
	# print(username_dict)

	for username in usernames:
		print(f"Processing {username}")
		if initialise:
			df = scrape_tweet(username)
			df = df.reset_index(drop=True)
			df.columns = df.columns.str.lower()
		else:
			try: 
				selected_date = dt.today() - timedelta(days=delta)
				selected_date.isoformat()
				df = scrape_tweet(username, since=selected_date)
				df = df.reset_index(drop=True)
				df.columns = df.columns.str.lower()
			except:
				continue
		username_id = username_dict[username]
		for idx, row in df.iterrows():
			i = username_id
			d = str(row["date"])
			tw = row["tweet"]
			un = row["username"]
			re = row["replies_count"]
			rt = row["retweets_count"]
			lc = row['likes_count']
			cursor.execute("""
				INSERT INTO twitter_tweet (
					username_id, date, tweet, username, 
					replies_count, retweets_count, likes_count
				)
				SELECT ?, ?, ?, ?, ?, ?, ?
				WHERE NOT EXISTS (
					SELECT * FROM twitter_tweet
					WHERE date=? AND tweet=?
				);
			""", (i, d, tw, un, re, rt, lc, d, tw))
	connection.commit()

Build Font-end Page

Do the thing which we did in previous. Build the html page for twitter data related to each user account, and put these html file in templates folder.

First, create a html file called twitter_index.html.

{% extends "layout.html" %}

{% block content %}
	<h1>Twitter User List</h1>
	<table class="ui selectable striped table">
	  <thead>
	    <tr>
	      <th>Username</th>
	    </tr>
	  </thead>
	  <tbody>
	  	{% for row in rows %}
		    <tr>
		      <td><a href="/twitter/{{ row.username }}">{{ row.username }}</a></td>
		    </tr>
	    {% endfor %}
	  </tbody>
	</table>
{% endblock %}

Next, create a html file called twitter_detail.html.

{% extends "layout.html" %}

{% block content %}
	<h1>Twitter</h1>
	<table class="ui selectable striped table">
	  <thead>
	    <tr>
	      <th>Date</th>
	      <th>Tweet</th>
	      <th># of Replies</th>
	      <th># of Retweets</th>
	      <th># of Likes</th>
	    </tr>
	  </thead>
	  <tbody>
	  	{% for tweet in tweets %}
		    <tr>
		      <td>{{ tweet.date }}</td>
		      <td>{{ tweet.tweet }}</td>
		      <td>{{ tweet.replies_count }}</td>
		      <td>{{ tweet.retweets_count }}</td>
		      <td>{{ tweet.likes_count }}</td>
		    </tr>
	    {% endfor %}
	  </tbody>
	</table>
{% endblock %}

Finally, add twitter_index() and twitter_detail() in app.py file.

@app.get("/twitter")
def twitter_index(request: Request):
    connection = sqlite3.connect("crypto.db")
    connection.row_factory = sqlite3.Row
    cursor = connection.cursor()
    cursor.execute("""
        SELECT * FROM twitter ORDER BY username;
    """)
    rows = cursor.fetchall()
    return templates.TemplateResponse(
        "twitter_index.html", 
        {"request": request, "rows": rows})

@app.get("/twitter/{name}")
def twitter_detail(request: Request, name):
    connection = sqlite3.connect("crypto.db")
    connection.row_factory = sqlite3.Row
    cursor = connection.cursor()
    print(name)
    cursor.execute("""
        SELECT * FROM twitter WHERE username=?;
    """, (name, ))
    row = cursor.fetchone()

    cursor.execute("""
        SELECT * FROM twitter_tweet WHERE username_id=? ORDER BY date DESC
    """, (row['id'], ))
    tweets = cursor.fetchall()

    return templates.TemplateResponse(
        "twitter_detail.html", 
        {"request": request, "row": row, "tweets": tweets})

Voila!

Conclusion

I’ve done implementing a very simple crypto web app using FastAPI, so that we can check on it efficiently and effectively. Have fun!


Author: Yang Wang
Reprint policy: All articles in this blog are used except for special statements CC BY 4.0 reprint polocy. If reproduced, please indicate source Yang Wang !
 Previous
Calculate the Singular Value Decomposition Calculate the Singular Value Decomposition
Singular Value Decomposition (SVD) is a widely used technique to decompose a matrix into several component matrices, exposing many of the useful and interesting properties of the original matrix.
2021-03-16
Next 
Triple Barrier Method for ML Triple Barrier Method for ML
Time series prediction has been widely applied to the finance industry in applications such as stock market price and commodity price forecasting. Machine learning methods have been widely used in financial time series prediction in recent years. How to label financial time series data to determine the prediction accuracy of machine learning models and subsequently determine final investment returns is a hot topic.
2021-03-15
  TOC