В этой заключительной части серии я создам приложение для инвестиционной платформы с помощью Flask. Я буду брать данные из Google BigQuery и постоянно скармливать их приложению. В предыдущей статье я построил конвейер ETL, который запускается один раз в день и загружает новые данные Forex в BigQuery.
Пока конвейер активен, BigQuery будет иметь обновленные данные Forex EOD (конец дня), готовые для загрузки в инвестиционную платформу.
Обзор проекта
Репозиторий GitHub
Части проекта
- Получение данных из BigQuery.
- Создание участков
- Создание приложения
- Домашняя страница
- макет, 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 для хранения данных о пользователях и многое другое. Надеюсь, вам понравилась эта серия статей и вы узнали что-то новое.