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:
These charts are simply not on the same level as TradingView charts.
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()