В этой заключительной части серии я создам приложение для инвестиционной платформы с помощью Flask. Я буду брать данные из Google BigQuery и постоянно скармливать их приложению. В предыдущей статье я построил конвейер ETL, который запускается один раз в день и загружает новые данные Forex в BigQuery.

Пока конвейер активен, BigQuery будет иметь обновленные данные Forex EOD (конец дня), готовые для загрузки в инвестиционную платформу.

Обзор проекта

Репозиторий GitHub



Части проекта

  1. Получение данных из BigQuery.
  2. Создание участков
  3. Создание приложения
  • Домашняя страница
  • макет, css и стиль
  • Демонстрация символов
  • Добавление графиков в приложение

Часть №1 — Извлечение данных из BigQuery

Прежде чем приступить к созданию приложения, давайте возьмем данные Forex из BigQuery. Я подключаюсь к BigQuery, запрашиваю курсы и символы и преобразовываю их в DataFrame Panda.

import os
import pandas as pd
from google.cloud import bigquery

def get_rates():

    # Setting Google Credentials
    os.environ['GOOGLE_APPLICATION_CREDENTIALS'] = '/projects/stock_analysis_platform/dags/ServiceKey_GoogleCloud.json'

    # Creating a BigQuery client
    bigquery_client = bigquery.Client()

    # Fetching the data from BigQuery
    query = "SELECT * FROM Forex_Platform.rates order by date, symbol;"

    # Retrieving the results from BigQuery
    query_job = bigquery_client.query(query)
    results = query_job.result()

    # Creating a Pandas DataFrame
    df = results.to_dataframe()
    
    return df

Проверяем данные:

# Get rates from BigQuery
df = get_rates()

# Extracting the rates, dates, symbols
dates = df.date.unique()
symbols = df.symbol.unique()
rates = df.rate.unique()

# Printing the first five rows
print(dates[0:5])
print(symbols[0:5])
print(rates[0:5])

Выход:

Часть № 2 — Создание графиков

Затем я создам скрипт, который генерирует линейный график и график гистограммы одного символа. Я сделаю функцию, которая принимает символ в качестве входных данных и генерирует эти графики. Эта функция будет активироваться всякий раз, когда пользователи нажимают на определенный символ в приложении.

Я буду использовать Plotly для создания простых интерактивных графиков.

def generate_plots(symbol):

    # Get rates from BigQuery
    df = get_rates()

    # Extract the rates of the symbol in the URL
    rates = df[df.symbol == symbol].rate
    
    # Create a figure with 2 columns
    fig = make_subplots(rows=1, cols=2)

    # Populate the figure with Scatter, Histogram
    fig.add_trace(go.Scatter(x=dates, y=rates, name='Line Plot', ), row=1, col=1)
    fig.add_trace(go.Histogram(x=rates, name = 'Histogram', histnorm = 'probability'), row=1, col=2)

    # Customizing Plots
    # Customize the appearance of the histogram
    fig.update_layout(
        title_font_size=20,  # Increase title font size
        xaxis_title_font_size=16,  # Increase x-axis title font size
        yaxis_title_font_size=16,  # Increase y-axis title font size
        barmode="overlay",  # Display bars on top of each other
        bargroupgap=0.01,  # Reduce gap between bar groups
        xaxis_tickangle=-45,  # Rotate x-axis tick labels
        plot_bgcolor="#d6f0f7",  # Set plot background color
        paper_bgcolor="#d6f0f7",  # Set paper background color
        legend_title_font_size=16,  # Increase legend title font size
        legend_font_size=14  # Increase legend font size
    )

    ## Adding title, xaxis and yaxis
    fig.update_layout(title = f'Symbol: USD vs {symbol}', xaxis_title = 'Date', yaxis_title = 'Rate')
        
    # Set the size of the figure
    fig.update_layout(width=1200, height=600)

    # Plot figure and render in HTML
    #plot_html = plotly.offline.plot(fig, include_plotlyjs=True, output_type='div')

    fig.show()

Обратите внимание, что я прокомментировал последнюю часть функции. Эта часть будет отображать графики в HTML. Это позволит нам вставлять графики в веб-страницы приложения.

Давайте попробуем использовать эту функцию для рисования некоторых основных графиков. Я выберу символ AED:

generate_plots('AED')

Часть № 3 — Создание приложения

Домашняя страница

Теперь, когда все настроено, давайте приступим к созданию приложения. Начну с главной страницы. Домашняя страница будет простой веб-страницей, приветствующей пользователей, которые входят в приложение.

Во-первых, я создам исходный код с помощью Flask.

from flask import Flask, render_template

app = Flask(__name__)


# Page #1 - Home page
@app.route('/')
@app.route('/home')
def home():
    return render_template('home.html', title = 'home')

# Run the app
if __name__ == '__main__':
    app.run(debug=True)

Эта функция «home» будет отображать файл с именем home.html всякий раз, когда пользователи приходят по адресам: «/» или «/home».

Затем нам нужно создать файл с именем home.html и добавить его в папку с названием «templates». По умолчанию flask сначала ищет HTML-файлы для отображения в каталоге с именем «templates».

Поэтому создайте папку под названием «templates» и добавьте home.html. Файл home.html выглядит следующим образом:

<h1>Welcome to the Forex Platform!</h1>
<p>Thank you for choosing our platform for your forex trading needs. We hope you have a successful and profitable experience with us.</p>
<p>Our platform offers a wide range of currency pairs to trade, as well as advanced trading tools and resources to help you make informed decisions. Whether you are a beginner or a seasoned trader, we have something to offer you.</p>
<p>Take a look around and explore all that our platform has to offer. We are confident that you will find everything you need to succeed in the forex market.</p>

Давайте запустим приложение и перейдем к localhost:5000/home, чтобы проверить домашнюю страницу.

Макет, CSS и стиль

Приложение работает, но немного глючит. Давайте добавим к нему стиль.

Я создам базовый шаблон стиля, от которого будут наследоваться все веб-страницы приложения. Таким образом, все веб-страницы будут иметь примерно одинаковый стиль и макет.

Во-первых, давайте добавим некоторые базовые статические стили CSS. Создайте каталог с именем static в папке приложения и добавьте следующий файл с именем main.css:

body {
    background: #d6f0f7;
    color: #333333;
    margin-top: 5rem;
    font-family: Cambria, Cochin, Georgia, Times, 'Times New Roman', serif, sans-serif;
    font-size: large;
  }
  
  h1, h2, h3, h4, h5, h6 {
    color: #444444;
  }
  
  .bg-steel {
    background-color: #1b1b1b;
  }
  
  .site-header .navbar-nav .nav-link {
    color: #cbd5db;
    font-size: 1rem;
    font-weight: 400;
    text-transform: lowercase;
  }
  
  .site-header .navbar-nav .nav-link:hover {
    color: #ffffff;
  }
  
  .site-header .navbar-nav .nav-link.active {
    font-weight: 500;
    border-bottom: 2px solid #ffffff;
  }
  
  .content-section {
    background: #ffffff;
    padding: 20px;
    border: 1px solid #dddddd;
    border-radius: 3px;
    box-shadow: 0 2px 4px rgba(0,0,0,0.1);
    margin-bottom: 40px;
  }
  
  .article-title {
    color: #444444;
    font-size: 2rem;
    font-weight: 600;
    margin-bottom: 0.5rem;
  }
  
  a.article-title:hover {
    color: #428bca;
    text-decoration: none;
  }
  
  .article-content {
    white-space: pre-line;
    font-size: 1.5rem;
    line-height: 1.5;
    margin-bottom: 1.5rem;
  }
  
  .article-img {
    height: 65px;
    width: 65px;
    margin-right: 16px;
    border-radius: 50%;
    object-fit: cover;
  }
  
  .article-metadata {
    display: flex;
    align-items: center;
    padding-bottom: 1px;
    margin-bottom: 4px;
    border-bottom: 1px solid #e3e3e3
  }
  
  .article-metadata a:hover {
    color: #333;
    text-decoration: none;
  }
  
  .article-metadata img {
    height: 25px;
    width: 25px;
    border-radius: 50%;
    object-fit: cover;
    margin-right: 0.5rem;
  }
  
  .article-metadata .article-author {
    font-size: 1.2rem;
    font-weight: 600;
    margin-right: 0.5rem;
  }
  
  .article-metadata .article-date {
    font-size: 1.2rem;
    color: #999;
  }
  
  .account-img {
    height: 125px;
    width: 125px;
    margin-right: 20px;
    margin-bottom: 16px;
    border-radius: 50%;
    object-fit: cover;
  }
  
  
  /* Change the background color of the button */
  button {
  background-color: rgb(129, 129, 230);
  }
  
  /* Change the text color of the button */
  button {
  color: white;
  }
  
  /* Add a border to the button */
  button {
  border: 2px solid black;
  }
  
  /* Change the font size of the button */
  button {
  font-size: 10;
  }
  
  /* Add some padding to the button */
  button {
  padding: 10px;
  }
  
  /* Use a custom font for the symbols */
  button {
  font-family: 'Arial', 'Helvetica Neue', sans-serif;
  }
  
  /* Add a hover effect to the button */
  button:hover {
  background-color: red;
  }

Затем я добавлю файл с именем layout.html в каталог шаблонов со следующим кодом:

<!DOCTYPE html>
<html>
<head>
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

    <!-- Bootstrap CSS -->
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">

    <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='main.css') }}">
    
    {% if title %}
        <title>Bar's Forex Platform - {{ title }} </title>
    {% else %}
        <title>Bar's Forex Platform </title>
    {% endif %}
</head>
 
<body>
    <header class="site-header">
      <nav class="navbar navbar-expand-md navbar-dark bg-steel fixed-top">
        <div class="container">
          <a class="navbar-brand mr-4" href="/">Forex Platform</a>
          <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarToggle" aria-controls="navbarToggle" aria-expanded="false" aria-label="Toggle navigation">
            <span class="navbar-toggler-icon"></span>
          </button>
          <div class="collapse navbar-collapse" id="navbarToggle">

            <!-- Navbar Left Side -->
            <div class="navbar-nav mr-auto">
              <a class="nav-item nav-link" href="/">Home</a>
              <a class="nav-item nav-link" href="/plots">Plots</a>
            </div>
          </div>
        </div>
      </nav>
    </header>
    <main role="main" class="container">
      <div class="row">
        <div class="col-md-8">
          {% block content %}{% endblock %}
        </div>
      </div>
    </main>


    <!-- Optional JavaScript -->
    <!-- jQuery first, then Popper.js, then Bootstrap JS -->
    <script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js" integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" crossorigin="anonymous"></script>
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js" integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" crossorigin="anonymous"></script>
</body>
</html>

наконец, я изменю home.html, чтобы он наследовал этот базовый макет, указанный в layout.html. home.html теперь выглядит так:

{% extends "layout.html" %}

{% block content %}
  <h1>Welcome to the Forex Platform!</h1>
  <p>Thank you for choosing our platform for your forex trading needs. We hope you have a successful and profitable experience with us.</p>
  <p>Our platform offers a wide range of currency pairs to trade, as well as advanced trading tools and resources to help you make informed decisions. Whether you are a beginner or a seasoned trader, we have something to offer you.</p>
  <p>Take a look around and explore all that our platform has to offer. We are confident that you will find everything you need to succeed in the forex market.</p>

{% endblock %}

Как мы видим, home.html «расширяется» от layout.html. Другими словами, все, что происходит от layout.html и заключено в ключевые слова {% block content %} {% endblock %}, будет иметь стиль, указанный в layout.html. .

Давайте проверим, как теперь выглядит домашняя страница:

Демонстрация символов

Теперь, когда приложение выглядит лучше, давайте продолжим его улучшать.

Я добавлю веб-страницу plot.html, на которой будут представлены все символы, доступные на платформе. Исходный код выглядит так:

# Page #2 - Plots page. Showcase all the currencies
@app.route('/')
@app.route('/plots')
def plots():
  
  # Rendering plots.html
    return render_template('plots.html', title = 'plots', symbols= symbols)

я также добавлю файл plots.html в каталог шаблонов, который выглядит следующим образом:

{% extends "layout.html" %}

{% block content %}

  <h1>Select a Currency Pair</h1>
  <p>Please select a currency pair from the list below to view its price plot:</p>

  {% for symbol in symbols %}
    <a href="/plots/{{ symbol }}"><button>{{ symbol }}</button></a>
  {% endfor %}

{% endblock %}

Как мы видим, каждый символ отображается в виде ссылки (ведущей к «/plots/{{symbol}}») и имеет кнопку, позволяющую пользователям щелкнуть по нему и перейти по указанному адресу. Давайте проверим это:

Добавление графиков в приложение

Сейчас эти кнопки ведут на несуществующую веб-страницу. Цель состоит в том, чтобы использовать созданную нами ранее функцию под названием generate_plots(symbol) для отображения линии и графика гистограммы для каждого символа после того, как пользователь нажмет кнопку.

Я добавлю это в исходный код приложения:

# Page #3 - Single plot page. Showcase various analytics for a single currency
@app.route('/plots/<symbol>')
def plot_png(symbol):
  
  # Generating plots for a symbol
    plot_html = generate_plots(symbol=symbol)

  # Rendering symbol_plot.html
    return render_template('symbol_plot.html', plot=plot_html, title = 'Symbol Dashboard')

И создайте файл с именем symbol_plot.html в каталоге шаблонов. HTML-код такой:

{% extends "layout.html" %}
{% block content %}

<div>
    {{ plot|safe }}
</div>
      
{% endblock %}

Теперь давайте проверим приложение:

Чтобы эта статья была короткой, я остановлюсь здесь. В моем репозитории GitHub есть полный код приложения.

Вы заметите, что я добавил такие функции, как формы входа и регистрации, базу данных SQLite для хранения данных о пользователях и многое другое. Надеюсь, вам понравилась эта серия статей и вы узнали что-то новое.