DevGang
Авторизоваться

Как создать приложение Forex с помощью Python и Streamlit

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

Сегодня мы собираемся углубиться в одну из таких замечательных библиотек — Streamlit. Хотя Streamlit изначально был представлен как приложение, предназначенное просто для демонстрации ваших работ в области искусственного интеллекта и машинного обучения, со временем оно превратилось в неоспоримый инструмент разработки веб-приложений.

Цель этой статьи — изучить этот мощный пакет путем разработки полноценного приложения для анализа валютных пар. Приложение будет показывать коэффициент конверсии, а также такие детали, как исторические данные, историческая волатильность и точки разворота, которые имеют решающее значение для технического анализа. Поскольку приложение в значительной степени полагается на надежные данные Forex, мы будем использовать набор API-интерфейсов Forex TraderMade, чтобы обеспечить приложение необходимыми данными.  

Это будет очень увлекательное путешествие, поскольку мы будем обсуждать API, важность проектирования каркасов, настройку стиля приложения с помощью CSS и многое другое. Без лишних слов, давайте углубимся в статью!

Понимание данных

Прежде чем приступить к фактической разработке приложения, очень важно создать адекватную базу данных, с которыми мы собираемся работать. Для создания нашего приложения мы будем использовать два разных API, предоставленных TraderMade. Первый — это API-интерфейс конвертации валюты TraderMade, который предоставляет курсы конвертации валюты в режиме реального времени. Вы можете увидеть этот API в действии и проверить точность данных с помощью собственного приложения конвертера валют TraderMade. Второй — API Timeseries, который можно использовать для получения исторических курсов валют.

Начнем с API конвертации валют. Прежде чем извлекать какие-либо данные с помощью этой конечной точки API, давайте сначала импортируем все необходимые пакеты в нашу среду Python:

# ИМПОРТ ПАКЕТОВ 

import streamlit as st
import requests
import pandas as pd

Если вы не установили ни один из импортированных пакетов, обязательно сделайте это с помощью команды pip в своем терминале. Теперь у нас есть все необходимое для извлечения некоторых данных.

Следующий код Python использует API конвертации валюты и получает курс обмена валюты USD/INR в режиме реального времени:

api_key = 'YOUR API KEY'
from_currency = 'USD'
to_currency = 'INR'
amount = 10

converted_details = requests.get(f'https://marketdata.tradermade.com/api/v1/convert?api_key={api_key}&from={from_currency[:3]}&to={to_currency[:3]}&amount={amount}').json()
converted_details

Довольно просто, правда?! Давайте углубимся в URL-адрес API, чтобы лучше понять конечную точку. Всего есть четыре параметра, которые входят в конечную точку API: ключ API (api_key), валюта, в которую и из которой мы хотим конвертировать (tofrom), и общая сумма, подлежащая конвертации. В приведенном выше коде мы пытаемся конвертировать 10 долларов США в INR. Результатом является ответ JSON, который выглядит следующим образом:  

{'base_currency': 'USD',
 'endpoint': 'convert',
 'quote': 83.27016,
 'quote_currency': 'INR',
 'requested_time': 'Sat, 18 May 2024 13:46:26 GMT',
 'timestamp': 1716039986,
 'total': 832.7016}

Следующий API, который мы будем использовать для создания приложения, — это API Timeseries, который помогает извлекать исторические данные о валютной паре. Следующий код использует API для получения исторических данных USD/INR с мая 2023 года по май 2024 года:

api_key = 'YOUR API KEY'
json = requests.get(f'https://marketdata.tradermade.com/api/v1/timeseries?currency=EURUSD&api_key={api_key}&start_date=2023-05-01&end_date=2024-04-30&format=records').json()
df = pd.DataFrame(json['quotes']).dropna().reset_index().drop('index', axis = 1)
df.date = pd.to_datetime(df.date)
df = df.set_index('date')

df.head()

Код во многом похож на предыдущий, за исключением изменения URL-адреса API. Timeseries API состоит из трех обязательных и трех необязательных параметров.

Обязательными параметрами являются: currency (валютная пара), start_date (начальная дата кадра данных) и end_date (дата, до которой мы хотим извлечь исторические данные). Необязательные параметры: interval и period (интервал времени между точками данных) и format (желаемый формат ответа API).

Результатом кода является кадр данных Pandas, который выглядит следующим образом:

Исторические данные USD/INR
Исторические данные USD/INR

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

Набросок макета

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

Каркас приложения
Каркас приложения

Это очень простой макет, сочетающий в себе минимализм и иерархию. Можно придумать сложную структуру, но она может быть не самой оптимальной, поскольку анализ Forex сам по себе сложен.

Теперь каркас можно разделить на три части: панель конвертера валют (которая включает в себя виджеты ввода и конвертированную сумму), панель исторических данных (исторические данные валютной пары с интерактивным графиком, представляющим то же самое) и панель статистики, которая показывает историческую волатильность и точки разворота (как данные, так и интерактивный график).

Чтобы добиться точного макета, показанного выше, и украсить его, мы будем реализовывать обширный собственный CSS, поскольку предустановленные дизайны Streamlit слишком общие и базовые. С учетом вышесказанного давайте начнем создавать приложение в Streamlit.

Строительные блоки: создание App.py, style.css, config.toml

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

Для начала создайте App.py (назовите файл по своему усмотрению), который будет основным файлом для запуска приложения Streamlit. Это файл, в котором происходит фактическая сборка приложения.

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

Наконец, чтобы глобально изменить тему приложения Streamlit, нам нужно создать отдельный каталог с именем .streamlit, внутри которого нам нужен файл config.toml. После создания файла TOML скопируйте и вставьте следующий код, чтобы воспроизвести тему информационной панели:

[theme]
primaryColor="#2962ff"
backgroundColor="#131722"
secondaryBackgroundColor="#0c0e15"
textColor="#f6f6f6"

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

1. Конвертер валют

Давайте возьмем окончательную панель конвертера валют в качестве справочного материала, который облегчит нам объяснение процесса разработки.

Панель конвертера валют
Панель конвертера валют

Чтобы добиться интерфейса, показанного выше, мы можем использовать следующий подход.

Подход к разработке:

  • Укажите широкий макет приложения с потоковой подсветкой.
  • Создайте четыре разных столбца, используя st.columns.
  • Получите список доступных валют с помощью API «Список валют в реальном времени» TraderMade и создайте инструмент st.selectbox для выбора валюты, которую мы хотим конвертировать.
  • Создайте st.number_input, чтобы указать общую сумму для конвертации.
  • Разместите значок, используя st.image.
  • Создайте другой st.selectbox, используя ранее извлеченный список, чтобы выбрать валюту, в которую мы хотим конвертировать.
  • Создайте ряд из разных столбцов, чтобы разместить кнопку и результаты.
  • Создайте st.button.
  • Создайте логику, используя условие if, которая извлекает текущие курсы валют и сведения о конвертации с помощью API конвертации валют и показывает результаты при каждом нажатии кнопки.

Подход к стилю:

  • Импортируйте собственный шрифт (в нашем случае Space Grotesk) из Google Fonts.
  • Измените шрифта глобально.
  • Настройте предварительно определенные отступы приложения Streamlit.
  • Настройте CSS st.container, включив в него настраиваемую рамку, радиус границы, отступы и тень блока.
  • Создайте три отдельных класса, чтобы показать различия в текстах результатов.

Следующие два блока кода реализуют описанные выше подходы. Первый блок кода записан в файле App.py, а второй — в style.css.

App.py:

import pandas as pd
import streamlit as st
import requests
import numpy as np
from lightweight_charts.widgets import StreamlitChart

if "currency_list" not in st.session_state:
    st.session_state.currency_list = None

st.set_page_config(
    page_title = 'Currency Converter',
    layout = 'wide'
)

st.markdown(
    """
    <style>
        footer {display: none}
        [data-testid="stHeader"] {display: none}
    </style>
    """, unsafe_allow_html = True
)

with open('style.css') as f:
    st.markdown(f'<style>{f.read()}</style>', unsafe_allow_html = True)

api_key = 'YOUR API KEY'

with st.container():
    from_col, amount_col, emp_col, text_col, emp_col, to_col = st.columns([0.5,0.5,0.05,0.08,0.05,0.5])
    
    with from_col:
        
        if st.session_state.currency_list == None:
            currency_json = requests.get(f'https://marketdata.tradermade.com/api/v1/live_currencies_list?api_key={api_key}').json()
            currencies = []
            for key in currency_json['available_currencies'].keys():
                currency = f'{key}' + ' ' + f'({currency_json["available_currencies"].get(key)})'
                currencies.append(currency)
            st.session_state.currency_list = currencies
            
        from_currency = st.selectbox('From', st.session_state.currency_list, index = 0, key = 'fromcurrency_selectbox')
        
    with amount_col:
        
        amount = st.number_input(f'Amount (in {from_currency[:3]})', min_value = 1, key = 'amount_numberinput')
        
    with text_col:
        
        st.image('to_icon.png')
        #st.markdown(f'<p class="to_text">TO</p>', unsafe_allow_html = True)
        
    with to_col:
        
        to_currency = st.selectbox('To', st.session_state.currency_list, index = 1, key = 'tocurrency_selectbox')
    
    st.markdown('')
    
    currency_col, conversion_col, details_col, emp_col, button_col = st.columns([0.06, 0.16, 0.26, 0.6, 0.1])
    
    with button_col:
        convert = st.button('Convert')
        
if convert:
    converted_details = requests.get(f'https://marketdata.tradermade.com/api/v1/convert?api_key={api_key}&from={from_currency[:3]}&to={to_currency[:3]}&amount={amount}').json()
    
    with currency_col:
        st.markdown(f'<p class="converted_currency">{to_currency[:3]}</p>', unsafe_allow_html = True)
        
    with conversion_col:
        converted_total = round(converted_details['total'],4)
        st.markdown(f'<p class="converted_total">{converted_total}</p>', unsafe_allow_html = True)
        
    with details_col:
        st.markdown(f'<p class="details_text">( 1 {from_currency[:3]}  =  {converted_total} {to_currency[:3]} )</p>', unsafe_allow_html = True)

style.css:

@import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&display=swap');

/* DASHBOARD PADDING */
div.block-container.css-z5fcl4.egzxvld4 {
    width: 100%;
    min-width: auto;
    max-width: initial;
    padding-left: 5rem;
    padding-right: 5rem;
    padding-top: 15px;
    padding-bottom: 40px;
}

/* GLOBAL FONT CHANGE */
html, body, [class*="css"] {
    font-family: 'Space Grotesk'; 
}

.st-ae {
    font-family: 'Space Grotesk';
}

/* CONTAINER CSS */
[data-testid="stVerticalBlock"] > [style*="flex-direction: column;"] > [data-testid="stVerticalBlock"] {
    border: 1px groove #52546a;
    border-radius: 10px;
    padding-left: 45px;
    padding-right: 45px;
    padding-top: 20px;
    padding-bottom: 40px;
    box-shadow: -6px 8px 20px 1px #00000052;
}

/* IMAGE CSS */
[data-testid="stImage"] {
    padding-top: 20px;
}

/* CUSTOM MARKDOWN CLASSES */
.converted_currency {
    font-size: 22px; 
    font-family: 'Space Grotesk';
    font-weight: 700;
    line-height: 1.2;
    text-align: right;
    color: grey;
    padding-top: 10px;
}

.converted_total {
    font-size: 32px; 
    font-family: 'Space Grotesk';
    font-weight: 700;
    line-height: 1.2;
    text-align: left;
}

.details_text {
    font-size: 15px; 
    font-family: 'Space Grotesk';
    font-weight: 400;
    line-height: 1.2;
    text-align: left;
    padding-top: 12px;
}

В App.py обязательно замените YOUR API KEY секретным ключом API TraderMade. В обоих файлах будут некоторые вещи, выходящие за рамки того, что мы обсуждали в подходах к разработке и стилизации, но я не буду вдаваться в подробности, поскольку их объяснение займет много времени.

2. Панель исторических данных

Подобно тому, что мы делали в предыдущем разделе, мы возьмем окончательный результат в качестве справочного материала для изложения подходов к разработке и стилизации. Следующее изображение представляет собой окончательный результат панели исторических данных, которую мы пытаемся построить:

Панель исторических данных
Панель исторических данных

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

Подход к разработке:

  • Создайте два столбца, используя st.columns с неравномерным интервалом.
  • Используйте st.markdown для заголовка фрейма данных.
  • Используйте st.dataframe для отображения 10-дневных исторических данных.
  • Используйте lightweight_charts библиотеку для создания интерактивного графика TradingView.

Подход к стилю:

Для этого раздела не потребуется определять новые классы в style.css. Что касается графика TradingView, lightweight_charts позволяет выполнять глубокую настройку, предоставляя возможность изменять и модифицировать каждую часть графика. Здесь мы изменили всю цветовую тему графика, а также изменили размер фигуры и добавили водяной знак валютной пары.

Следующий блок кода реализует описанные выше подходы. Поскольку стилизация не требуется, в style.css не нужно писать новый код. Следующий код написан в App.py:

    st.markdown('')
    
    last10_col, chart_col = st.columns([0.3,0.7])

    with last10_col:
        
        #st.markdown(f'<p class="converted_currency">1 {from_currency[:3]} to {to_currency[:3]} exchange rate last 10 days</p>', unsafe_allow_html = True)
        st.markdown(f'<p><b>1 {from_currency[:3]} to {to_currency[:3]} exchange rate last 10 days</b></p>', unsafe_allow_html = True)
        st.markdown('')
        
        api_currency = from_currency[:3] + to_currency[:3]
        historical_json = requests.get(f'https://marketdata.tradermade.com/api/v1/timeseries?currency={api_currency}&api_key={api_key}&start_date=2023-06-01&format=records').json()
        historical_df = pd.DataFrame(historical_json['quotes']).dropna().reset_index().drop('index', axis = 1)
        historical_df.date = pd.to_datetime(historical_df.date)
        historical_df = historical_df.set_index('date')
        st.dataframe(historical_df.tail(10), use_container_width = True)
    
    with chart_col:

        chart = StreamlitChart(height = 450, width = 950, volume_enabled = False)
        chart.grid(vert_enabled = True, horz_enabled = True)

        chart.layout(background_color='#131722', font_family='Trebuchet MS', font_size = 16)

        chart.candle_style(up_color='#2962ff', down_color='#e91e63',
                           border_up_color='#2962ffcb', border_down_color='#e91e63cb',
                           wick_up_color='#2962ffcb', wick_down_color='#e91e63cb')
        
        chart.watermark(f'{from_currency[:3]}/{to_currency[:3]} 1D')
                   
        #chart.volume_config(up_color='#2962ffcb', down_color='#e91e63cb')
        chart.legend(visible = True, font_family = 'Trebuchet MS', ohlc = True, percent = True)
        
        chart_df = historical_df.reset_index()
        chart.set(chart_df)
        chart.load()

Обязательно используйте код, не меняя отступы кода, поскольку это может вызвать ошибки или изменить весь макет приложения.

3. Панель статистики

Вот окончательный интерфейс панели статистики:

Панель статистики
Панель статистики

Как и предыдущий, этот раздел также довольно прост в создании, поскольку все статично и требует очень мало усилий для стилизации.

Подход к разработке:

  • Создайте st.container.
  • Используйте st.markdown для названия каждого раздела.
  • Создайте две колонки с неравномерным интервалом.
  • Рассчитайте историческую волатильность, используя исторические данные.
  • Используйте st.dataframe для отображения исторических данных о волатильности.
  • Создайте линейную диаграмму, чтобы показать исторические данные, используя st.line_chart.
  • Создайте еще один набор из двух столбцов с неравномерным интервалом для раздела точек поворота.
  • Рассчитать точки разворота, используя исторические данные.
  • Используйте st.dataframe для отображения данных опорных точек.
  • Создайте интерактивный график TradingView, используя библиотеку lightweight_charts, чтобы нанести точки разворота рядом с историческими данными.

Подход к стилю:

  • Определите класс для заголовка раздела.
  • Настройки графиков TradingView выполняются с помощью библиотеки weight_charts, не требующей дополнительного кода CSS.

Следующие два блока кода реализуют два подхода, которые мы обсуждали выше. Первый блок кода записан в файле App.py, а второй — в style.css.

App.py:

 with st.container():
        
        st.markdown('')
        
        st.markdown(f'<p class="section_title"><b>{from_currency[:3]}/{to_currency[:3]} Historical Volatility</b> (length = 20)</p>', unsafe_allow_html = True)
        st.markdown('')
        
        hv_data_col, hv_chart_col = st.columns([0.4,0.6])
        
        with hv_data_col:
            historical_df['log_ret'] = np.log(historical_df['close'] / historical_df['close'].shift(1))
            window_size = 20
            rolling_volatility = historical_df['log_ret'].rolling(window=window_size).std()
            historical_df['hv'] = rolling_volatility * np.sqrt(365) * 100
            historical_df = historical_df.dropna()
            st.dataframe(historical_df[['close','log_ret','hv']], use_container_width = True)
        with hv_chart_col:
            st.line_chart(historical_df.hv, height = 450)
        
        st.markdown('')
        
        st.markdown(f'<p class="section_title"><b>{from_currency[:3]}/{to_currency[:3]} Pivot Points</b></p>', unsafe_allow_html = True)
        st.markdown('')
        
        pivot_data_col, pivot_chart_col = st.columns([0.4,0.6])
        
        with pivot_data_col:
            historical_df['pivot'] = (historical_df['high'].shift(1) + historical_df['low'].shift(1) + historical_df['close'].shift(1)) / 3

            historical_df['r1'] = 2 * historical_df['pivot'] - historical_df['low'].shift(1)
            historical_df['s1'] = 2 * historical_df['pivot'] - historical_df['high'].shift(1)

            historical_df['r2'] = historical_df['pivot'] + (historical_df['high'].shift(1) - historical_df['low'].shift(1))
            historical_df['s2'] = historical_df['pivot'] - (historical_df['high'].shift(1) - historical_df['low'].shift(1))

            historical_df['r3'] = historical_df['high'].shift(1) + 2 * (historical_df['pivot'] - historical_df['low'].shift(1))
            historical_df['s3'] = historical_df['low'].shift(1) - 2 * (historical_df['high'].shift(1) - historical_df['pivot'])
            
            historical_df = historical_df.dropna()
            st.dataframe(historical_df.iloc[:,-6:], use_container_width = True)

            r1,r2,r3 = historical_df.r1[-1], historical_df.r2[-1], historical_df.r3[-1]
            s1,s2,s3 = historical_df.s1[-1], historical_df.s2[-1], historical_df.s3[-1]
            
        with pivot_chart_col:     
            
            chart = StreamlitChart(height = 450, width = 800, volume_enabled = False)
            chart.grid(vert_enabled = True, horz_enabled = True)

            chart.layout(background_color='#131722', font_family='Trebuchet MS', font_size = 16)

            chart.candle_style(up_color='#2962ff', down_color='#e91e63',
                               border_up_color='#2962ffcb', border_down_color='#e91e63cb',
                               wick_up_color='#2962ffcb', wick_down_color='#e91e63cb')
            
            chart.horizontal_line(price = r1, color = 'darkorange', text = f'R1', style = 'dotted')
            chart.horizontal_line(price = r2, color = 'darkorange', text = f'R2', style = 'dotted')
            chart.horizontal_line(price = r3, color = 'darkorange', text = f'R3', style = 'dotted')
            chart.horizontal_line(price = s1, color = 'darkorange', text = f'S1', style = 'dotted')
            chart.horizontal_line(price = s2, color = 'darkorange', text = f'S2', style = 'dotted')
            chart.horizontal_line(price = s3, color = 'darkorange', text = f'S3', style = 'dotted')

            #chart.volume_config(up_color='#2962ffcb', down_color='#e91e63cb')
            chart.legend(visible = True, font_family = 'Trebuchet MS', ohlc = True, percent = True)
                        
            chart_df = historical_df.reset_index()
            chart.set(chart_df)
            chart.load()

style.css:

.section_title {
    font-size: 20px; 
    font-family: 'Space Grotesk';
    font-weight: 500;
    line-height: 1.2;
    text-align: center;
}

Как я уже говорил в предыдущем разделе, обязательно используйте код, не меняя отступы кода, поскольку это может вызвать ошибки или изменить весь макет приложения.

Конечный результат:

Окончательный интерфейс приложения
Окончательный интерфейс приложения

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

Вот видео, которое лучше демонстрирует функциональность приложения:

Последние мысли

Какая это была захватывающая поездка. Мы начали с изучения конечных точек Currency API TraderMade, затем приступили к увлекательному процессу создания эскиза макета приложения и создания грубого каркаса. После этого мы приступили к созданию приложения с помощью Streamlit.

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

Одной из основных проблем Streamlit является сложность масштабирования. В отличие от приложений React, приложения Streamlit невероятно сложно масштабировать, что затрудняет удовлетворение требований более широкой аудитории. Но опять же, Streamlit — это своего рода новинка, и разумно дать ему немного времени, чтобы справиться с другими крупными игроками.

Источник

#Python
Комментарии
Чтобы оставить комментарий, необходимо авторизоваться

Присоединяйся в тусовку

В этом месте могла бы быть ваша реклама

Разместить рекламу