6 min read

Interactive Brokers API and TradingView Charts in Python

Way back in 2020, I created a video on how to visualize real-time price data using TradingView Lightweight Charts and JavaScript. While this library is great, I received many requests on how to create something like this in pure Python. While you can use Python libraries like matplotlib and mplfinance to chart financial data, just look at how ugly this chart is:

an ugly Python candlestick chart

These charts are simply not on the same level as TradingView charts.

💡
Support My Content: If you decide to check out Interactive Brokers, and want to support my content, I have a link to learn more here.

Speaking of TradingView, I also recorded a Python tutorial on how to automatically execute trades on Interactive Brokers using a combination of PineScript, TradingView webhooks, a Python webapp, and redis. While it was fun to build, this approach involved way too many components. It required multiple programming languages and relied on sending webhook messages to a Flask app. Wouldn't it be great if we could implement everything in pure Python without the JavaScript / Pinescript and webhook pieces?

Recently I was browsing reddit and discovered a new open source library called Lightweight Charts Python, which provides a pythonic interface to Lightweight Charts. This means we can take all our existing Python code for fetching and manipulating data, visualize it with TradingView charts, plot markers indicators, react to changes in real-time data, and even execute trades without switching programming languages or juggling data between different systems.

What We're Building

In this series, we will build a few small projects using this library, with increasing levels of complexity:

1) We will visualize historical data on a static chart using free data from Yahoo Finance. This will be a daily timeframe chart with a watermark and smooth panning. We will also integrate Pandas TA, which I also created a video on in 2021. This will allow us to dynamically fetch data and plot moving averages and other technical features:

2) We will learn to use callbacks to switch symbols and dynamically fetch data for shorter timeframes from Interactive Brokers with ib_insync:

3) We will create a grid of 4 charts for different stocks with dynamically fetched data that looks like this:

4) We will learn to do real-time rendering using tick data:

5) We will subscribe to price bar data, detect a simple pattern, and automatically place a live trade when that pattern is detected. We will use a callback function to plot a point on the chart when the order is actually filled, along with the fill price. Disclaimer: This will not make you money and is for demonstration purposes only. I recommend trying this out in a paper trading account. Short term trading is risky and can result in loss of capital, so try this at work own risk.

Below is the source code and video walkthrough.

Source Code

requirements.txt

lightweight-charts
yfinance
pandas_ta
ib_insync
nest_asyncio

Static Chart with Pandas TA Indicator, Yahoo Finance Integration

import pandas as pd
import pandas_ta as ta
import yfinance as yf
from lightweight_charts import Chart

if __name__ == '__main__':
    
    chart = Chart()
    
    msft = yf.Ticker("MSFT")
    df = msft.history(period="1y")

    # prepare indicator values
    sma = df.ta.sma(length=20).to_frame()
    sma = sma.reset_index()
    sma = sma.rename(columns={"Date": "time", "SMA_20": "value"})
    sma = sma.dropna()

    # this library expects lowercase columns for date, open, high, low, close, volume
    df = df.reset_index()
    df.columns = df.columns.str.lower()
    chart.set(df)

    # add sma line
    line = chart.create_line()    
    line.set(sma)

    chart.watermark('MSFT')
    
    chart.show(block=True)

Static Chart with Callbacks, Interactive Brokers Integration

import asyncio
import pandas as pd
from lightweight_charts import Chart

from ib_insync import *
import nest_asyncio
nest_asyncio.apply()


def get_data(symbol, timeframe):
    ib = IB()
    ib.connect('127.0.0.1', 7497, clientId=13)

    contract = Stock(symbol, 'SMART', 'USD')
    bars = ib.reqHistoricalData(
            contract,
            endDateTime='',
            durationStr='90 D',
            barSizeSetting=timeframe,
            whatToShow='TRADES',
            useRTH=True,
            formatDate=1)
    ib.disconnect()
    df = util.df(bars)

    return df

class API:
    def __init__(self):
        self.chart = None

    async def on_search(self, symbol):  # Called when the user searches.
        timeframe = self.chart.topbar['timeframe'].value
        data = get_data(symbol, timeframe)

        self.chart.set(data)  # sets data for the Chart or SubChart in question.
        self.chart.topbar['symbol'].set(symbol)

    async def on_timeframe(self):  # Called when the user changes the timeframe.
        timeframe = self.chart.topbar['timeframe'].value
        symbol = self.chart.topbar['symbol'].value
        data = get_data(symbol, timeframe)

        self.chart.set(data)

    
async def main():
    api = API()
    chart = Chart(api=api, topbar=True, searchbox=True)
    
    symbol = 'AAPL'
    timeframe = '15 mins'
    df = get_data(symbol, timeframe)

    chart.topbar.textbox('symbol', symbol)
    chart.topbar.switcher('timeframe', api.on_timeframe, '15 mins', '1 hour', '1 day', default='15 mins')
    
    chart.set(df)

    await chart.show_async(block=True)


if __name__ == '__main__':
    asyncio.run(main())

Static Grid of 4 Charts

import pandas as pd
import yfinance as yf
from lightweight_charts import Chart
    
if __name__ == '__main__':
    chart = Chart(inner_width=0.5, inner_height=0.5)

    chart2 = chart.create_subchart(position='right', width=0.5, height=0.5)
    chart3 = chart2.create_subchart(position='left', width=0.5, height=0.5)
    chart4 = chart3.create_subchart(position='right', width=0.5, height=0.5)

    chart.watermark('MSFT')
    chart2.watermark('NVDA')
    chart3.watermark('AAPL')
    chart4.watermark('GOOG')

    msft = yf.Ticker("MSFT")
    df = msft.history(period="1y")

    nvda = yf.Ticker("NVDA")
    df2 = nvda.history(period="1y")

    aapl = yf.Ticker("AAPL")
    df3 = aapl.history(period="1y")

    goog = yf.Ticker("GOOG")
    df4 = goog.history(period="1y")

    # this library expects lowercase columns for date, open, high, low, close, volume
    df = df.reset_index()
    df.columns = df.columns.str.lower()
    df2 = df2.reset_index()
    df2.columns = df2.columns.str.lower()
    df3 = df3.reset_index()
    df3.columns = df3.columns.str.lower()
    df4 = df4.reset_index()
    df4.columns = df4.columns.str.lower()

    chart.set(df)
    chart2.set(df2)
    chart3.set(df3)
    chart4.set(df4)

    chart.show(block=True)

Real-Time Chart, Tick Data From Interactive Brokers

import time
from ib_insync import *
from lightweight_charts import Chart


if __name__ == '__main__':
    # connect to Interactive Brokers
    ib = IB()
    ib.connect('127.0.0.1', 7497, clientId=1)

    # request minute bars for a stock
    stock = Stock('AAPL', 'SMART', 'USD')

    bars = ib.reqHistoricalData(
        stock, endDateTime='', durationStr='3000 S',
        barSizeSetting='1 min', whatToShow='MIDPOINT', useRTH=True)

    # convert bars to a pandas dataframe
    df = util.df(bars)

    # show the initial chart with the minute bars
    chart = Chart(volume_enabled=False)
    chart.set(df)
    chart.show()

    # request market data and update the chart with real-time data
    market_data = ib.reqMktData(stock, '233', False, False)

    def onPendingTicker(ticker):
        print("pending ticker event received")
        for tick in ticker:
            ticks = util.df(tick.ticks)
            if ticks is not None:
                last_price  = ticks[ticks['tickType'] == 4]
                if not last_price.empty:
                    print(last_price)
                    chart.update_from_tick(last_price.squeeze())

    ib.pendingTickersEvent += onPendingTicker

    ib.run()

Reacting to Minute Bars and Placing an Order on Interactive Brokers

import pandas as pd
from lightweight_charts import Chart
from ib_insync import *

if __name__ == '__main__':
    ib = IB()
    ib.connect('127.0.0.1', 7497, clientId=13)
    contract = Stock('AAPL', 'SMART', 'USD')

    bars = ib.reqHistoricalData(
            contract,
            endDateTime='',
            durationStr='300 S',
            barSizeSetting='5 secs',
            whatToShow='TRADES',
            useRTH=True,
            formatDate=1)
            
    df = util.df(bars)

    chart = Chart(volume_enabled=False)
    chart.set(df)
    chart.show()

    def orderFilled(trade, fill):
        print("order has been filled")
        print(trade)
        print(fill)
        print(dir(fill))
        chart.marker(text=f"order filled at {fill.execution.avgPrice}")

    def onBarUpdate(bars, hasNewBar):
        last_bar = bars[-1]

        last_bar_series = pd.Series({
            'time': last_bar.time,
            'open': last_bar.open_,
            'high': last_bar.high,
            'low': last_bar.low,
            'close': last_bar.close,
            'volume': last_bar.volume
        })

        chart.update(last_bar_series)

        if len(bars) >= 3:
            if bars[-1].close > bars[-1].open_ and \
                bars[-2].close > bars[-2].open_ and \
                bars[-3].close > bars[-3].open_:
                
                print("3 green bars, let's buy!")

                # buy 10 shares and call orderFilled() when it fills
                order = MarketOrder('BUY', 1)
                trade = ib.placeOrder(contract, order)
                trade.fillEvent += orderFilled

    bars = ib.reqRealTimeBars(contract, 5, 'MIDPOINT', False)
    bars.updateEvent += onBarUpdate

    ib.run()