top of page
  • Nikhil Adithyan

Building an Advanced Forex app with Python and Streamlit

An exciting side-project to have in your portfolio!



Introduction: A Forex App with Python

Though Python is one of the most trending and hottest languages prevailing in the industry right now, it’s still an overlooked one for developing web applications. But with the introduction of a lot of ground-breaking libraries, Python is shattering boundaries and making waves in the web development community.


Today, we are going to delve into one such awesome library, Streamlit. While Streamlit was initially introduced as an app to just showcase your AI/ML works, it incredibly evolved into an undeniable web app development tool over time.


The aim of this article is to explore this powerful package by developing a full-fledged currency pairs analysis app. The forex app built using Python would show the conversion rate along with details like historical data, historical volatility, and pivot points which are crucial for technical analysis. Since the app heavily relies on reliable Forex data, we’ll be using TraderMade’s Suite of Forex APIs to power the application with the required data.


It’s going to be a very exciting journey as we’ll be discussing APIs, the importance of designing wireframes, customizing app style using CSS, and much more. Without further ado, let’s dive into the article!


Understanding the Data

Before proceeding to the actual development of the app, it’s vital to build an adequate background on the data we are going to work with. To build our app, we’re going to use two different APIs provided by TraderMade. The first is TraderMade’s Currency Convert API which provides real-time currency conversion rates. You can see this API in action and check the data accuracy through TraderMade’s very own currency converter app. The second one is the Timeseries API which can be used for acquiring historical currency rates.


Let’s start with the Currency Convert API. Before extracting any data using this API endpoint, let’s first import all the required packages into our Python environment:



# IMPORTING PACKAGES

import streamlit as st
import requests
import pandas as pd

If you haven’t installed any of the imported packages, make sure to do so using the pip command in your terminal. Now we are all equipped to extract some data.


The following Python code uses the Currency Convert API and gets the currency exchange rate of USD/INR in real-time:



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

Pretty simple, right?! Let’s dive into the API URL to understand the endpoint better. There are totally four parameters that come with the API endpoint: the API key (api_key), the currency to and from which we want to convert (to, from), and the total amount to be converted. In the above code, we are trying to convert 10 USD to INR. The output is a JSON response that looks like this:



{'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}

The next API that we’ll be using for building the app is the Timeseries API which helps in extracting the historical data of a currency pair. The following code uses the API to get the historical data of USD/INR from May 2023 to May 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()

The code is pretty much similar to the previous one except for the change in the API URL. The Timeseries API consists of three mandatory and three optional parameters.


The mandatory parameters are: currency (the currency pair), start_date (the starting date of the dataframe), and end_date (the date till which we want to extract the historical data). The optional parameters are: interval and period (the time interval between the data points), and format (the desired format of the API response).

The output of the code is a Pandas dataframe that looks like this:



Awesome! Now that we have a good background on the two APIs we’re going to use to power the app, let’s start with our actual work of building it.


Sketching the Layout

This step is often overlooked, yet it can be incredibly helpful and save a significant amount of time. Having reference material for designing the dashboard greatly accelerates the development process. Here’s a rough sketch of the dashboard we’re going to create:



This is a very simple layout that embraces minimalism and hierarchy. It’s possible to come up with a complex structure but it might not be the most optimal one since forex analysis itself is complicated.


Now, the wireframe can divided into three parts: The currency converter pane (which includes the input widgets and the converted amount), the historical data pane (the currency pair historical data with an interactive chart to represent the same), and the statistics pane which shows historical volatility and pivot points (both data and interactive chart).


In order to achieve the exact layout that is featured above and to beautify it, we’ll be implementing extensive custom CSS as Streamlit’s preset designs are too generic and basic. With that being said, let’s start building the app in Streamlit.


Building Blocks: Creating App.py, style.css, config.toml

Before jumping into building the Streamlit app, it is highly recommended to keep things in place as it is a much more organized way of development.


To start off, create an App.py (name the file as you like) which will be the core file for running the streamlit app. This is the file where the actual building of the app undergoes.


Secondly, since we are going to build the app with custom CSS, it is mandatory to have a separate CSS file. You can name the file as you like but I prefer a more common naming convention, i.e., style.css. In this file, we will be customizing all the pre-defined streamlit CSS properties.


Finally, in order to change the theme of the streamlit app globally, we have to create a separate directory with the name of .streamlit inside which, we need to have a config.toml file. After creating the TOML file, copy and paste the following code to replicate the theme of the dashboard:



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

The theme is a blend of dark mode with some blues in it for a pleasant and soft-looking feel. You can customize the theme as per your wish.


1. Currency Converter

Let’s take the final currency converter pane as the reference material which will make it easier for me to explain the development process.



To achieve the interface displayed above, here’s the approach we can follow.


Development approach:


  • Specify the layout of the streamlit app to be wide

  • Create four different columns using st.columns

  • Pull the list of available currencies using TraderMade’s “Live Currencies List” API and create a st.selectbox to choose the currency that we want to convert

  • Create a st.number_input to specify the total amount to be converted

  • Place an icon using st.image

  • Create another st.selectbox using the previously extracted list to choose the currency to which we want to convert

  • Create a row of different columns to place the button and the results

  • Create a st.button

  • Create a logic using an if-conditon which pulls the live forex rates and conversion details using the Currency Convert API and shows the results whenever the button is clicked.


Styling approach:


  • Import custom font (Space Grotesk in our case) from Google Fonts

  • Change the font globally

  • Customize the pre-defined padding of the streamlit app

  • Customize the CSS of st.container to include customized border, border radius, padding, and box-shadow.

  • Create three separate classes to show variation in the result’s texts.


The following two blocks of code implement the approaches discussed above. The first code block is written in the App.py file and the second in 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;
}

In the App.py, make sure to replace YOUR API KEY with your TraderMade secret API key. In both files, there will be some stuff that goes beyond what we discussed in the development and styling approaches, but I’m not going to delve into the details as it will take a long time to explain them.


2. Historical Data Pane

Similar to what we did in the previous section, we’ll take the final output as the reference material to lay out the development and styling approaches. The following image is the final result of the historical data pane which we’re trying to build:



The building of this section is relatively simple when compared to the previous one as it involves no functionality and the need for creating new style classes.


Development Approach:


  • Create two columns using st.columns with uneven spacing

  • Use st.markdown for the dataframe title

  • Use st.dataframe to show the 10-day historical data

  • Use the lightweight_charts library to create the interactive TradingView chart


Styling Approach:


For this section, there will be no need to define new classes in style.css. Regarding the TradingView chart, lightweight_charts allows for heavy customization providing options to change and modify every part of the chart. Here, we have changed the whole color theme of the plot along with changing the figure size and adding the currency pair watermark.


The following block of code implements the approaches discussed above. Since there is no styling involved, no new code has to be written in style.css. The following code is written in App.py:


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

    with last10_col:
        
        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.legend(visible = True, font_family = 'Trebuchet MS', ohlc = True, percent = True)
        
        chart_df = historical_df.reset_index()
        chart.set(chart_df)
        chart.load()

Make sure to use the code without changing the code’s indentation as it may cause errors or change the entire layout of the app.


3. Statistics Pane

Here’s the final result interface of the Statistics pane:



Like the previous one, this is also a fairly simple section to build since everything is static and requires very little effort in styling.


Development Approach:


  • Create a st.container

  • Use st.markdown for each section’s title

  • Create two columns with uneven spacing

  • Calculate the historical volatility using the historical data

  • Use st.dataframe to show the historical volatility data

  • Create a line chart to show the historical data using st.line_chart

  • Create another set of two columns with uneven spacing for the pivot points section

  • Calculate pivot points using the historical data

  • Use st.dataframe to show the pivot points data

  • Create an interactive TradingView chart using the lightweight_charts library to plot the pivot points alongside the historical data


Styling Approach:


  • Define a class for the section’s title.

  • The TradingView chart customizations are done with the help of the lightweight_charts library demanding no extra CSS code.


The following two blocks of code implement the two approaches we discussed above. The first code block is written in the App.py file and the second in 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.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;
}

Like I said in the previous section, make sure to use the code without changing the code’s indentation as it may cause errors or change the entire layout of the app.


Final Result:



The reason why I’ve put together the screenshots of the different sections instead of showing the entire interface as one is because of the fact that the app is currently quite unresponsive, i.e., the interface doesn't modify according to the screen size. With the help of some CSS code, it is indeed possible to make the app responsive but that is not the scope of this article.


Here’s a video that better shows the functionality of the app:





Final Thoughts

What an exciting ride it has been. We started off by exploring TraderMade’s Currency API endpoints, then, we underwent the exciting process of sketching the layout for the app and creating a rough wireframe. After that, we went on to build the app with Streamlit.


I was truly amazed at Streamlit’s capability and the features it provides. It really eased the whole process of developing a web app. Though it’s currently limited to building small-scale apps, I could clearly see the vision of Streamlit in assisting developers to create large-scale commercial applications.


One major issue with Streamlit is the difficulty in scaling. Unlike React apps, Streamlit applications are incredibly tough to scale making it hard to cater to the requirements of a larger audience. But again, Streamlit is kinda new and it’s only rational to give it some time to cope with other big players.


With that being said, you’ve reached the end of the article. Hope you learned something new and useful. If you know any other Python libraries that you think are a better alternative to Streamlit, post them in the comments. Thank you very much for your time.

1 comentario


saravanakumaar.a
saravanakumaar.a
30 jun

Good one. Too tech for me but could understand the possibilities

Me gusta
bottom of page