Interactive Brokers Python API (Native) – A Step-by-step Guide

35 min read

Last Updated on

Interactive Brokers (IB) is a trading brokerage used by professional traders and small funds.

If you want to learn how to build automated trading strategies on a platform used by serious traders, this is the guide for you.

Source

Table of Content

  1. What is the Interactive Brokers Python native API?
  2. Why should I learn the IB Python Native API?
  3. Why shouldn’t I learn the IB Python Native API?
  4. IB Python native API vs Third Party Libraries (IBridgePy, IbPy etc)
  5. How to set up the IB native Python API?
  6. How to retrieve the current ask price of Apple’s Stock (AAPL)
  7. Retrieving market data for other assets – EUR/USD, Bitcoin & Gold
  8. How to retrieve the last 10 hourly candlebars using the native Python API?
  9. What’s the best way to store historical data for later use?
  10. 3 ways to calculate the 20 SMA
  11. How to fire an order using the native Python API?
  12. How to implement a stop loss or take profit?
  13. How to fire an order for Apple when Google hits a certain price?
  14. How to fire an order for Apple when Google moves more than 5% within the last 5 minutes?
  15. How to send notifications via telegram?
  16. Common Errors with the IB Python Native API v9.76
  17. Download the full codebase

p.s. If you have no idea what is algorithmic trading, read this first: What is Algorithmic Trading and How Do I Learn It?

What is the Interactive Brokers Python native API?

The Interactive Brokers Python native API is a functionality that allows you to trade automatically via Python code.

In more technical terms, it is a communication protocol that allows for an interchange of information with Interactive Broker’s (IB) servers and custom software applications. Acting as a bridge, the API allows for sending of orders from custom software or scripts, receiving live or historical data, and several other useful applications.

TWS Chart TSLA Option
An example of a chart on the IB platform. It shows a Tesla option rising 30,000% and crashing within days – Circa 2020

Why should I learn the IB Python Native API?

Chances are that if you’re reading this guide, you’ve already done your research and concluded that Interactive Brokers (IB) has great online reviews. The broker is well-known for competitive commission rates and breadth of markets.

Learning to use the Python native API allows you to take things one step further. Here are some of the things you can accomplish:

  • Automate trading – Whether you’re seeking a fully or semi-automated solution, the API is a base point for connecting your automation scripts with Interactive brokers
  • Create a custom trading terminal – Interactive Broker’s TWS is great and packed with a ton of functionality. But if you’re looking for an alternate solution to place trades, a custom terminal can easily be built using the API.
  • Collect historical data – Having access to past data is the starting point for most automated trading systems. IB offers streaming data and is generous with its API rate limits.
  • Easily create custom indicators – TWS has standard built-in technical indicators that are widely used. However, if you’re looking to customize your own indicators, the API is the way to go. Further, Python is known for its vast libraries. If you’re interested in machine learning or sentiment analysis for example, the API offers a bridge to connect to amazing libraries available in Python for these areas.
  • Custom alerts and notifications – Do you have a need for an alert that TWS can’t fulfill? There’s a good chance you can do it Python.

Why shouldn’t I learn the IB Python Native API?

In some cases, there are easier ways to accomplish your goals. Here are a few reasons why IB’s API might not be the right fit:

  • Backtesting – There isn’t a built-in solution for backtesting strategies. There are some great third-party solutions and most even supply the data which simplifies things quite a bit. Check out the following article for more information – Backtesting Systematic Trading Strategies in Python: Considerations and Open Source Frameworks.
  • Ease of use – This detailed guide will attempt to walk you through setting up the IB API step by step. But some steps might seem a bit complicated and if you’re focused on the currency markets or only trading CFD’s, it might be worth checking Metatrader 4 or Metatrader 5. It doesn’t have as large of a learning curve and has several solutions built-in. There is even a third-party open source bridge available if you’d like to use Python with Metatrader.
  • Commissions – The costs of commissions and data fee’s add up, especially for those executing a lot of trades. While IB is known to offer low commissions, this is not the case across all markets. They also charge for data and don’t pay out interest under a certain threshold. It’s worth comparing the cost-effectiveness of trading with IB versus some of the other brokers out there before investing your time into learning the API.

» If you are looking for trading ideas that might work (to some extent at least), check out this guide: 4 Quantitative Trading Strategies that Work Today

IB Python native API vs Third Party Libraries (IBridgePy, IbPy etc)

The IB Python native API is officially developed and maintained by Interactive Brokers. This ensures that it will provide the most stable and error-free connection to the IB servers.

On the other hand, code wrappers and libraries like IBridgePy or IbPy are developed by third-parties and are not officially supported by IB.

How to set up the IB native Python API?

There are four basic steps to setting up a connection to the IB API in Python.

  1. Open an account with IB – IB offers demo accounts which are great for testing. If you decide to connect to a live account, there is a read-only option for the API in TWS which is useful when testing and in the early stages of getting to know the API.
  2. Download the IB Python native API – These are script files written in Python that facilitate the connection and communication with IB’s client which is in turn connected to their server.
  3. Download your IB client (TWS or IB Gateway) –  You might already be familiar with TWS, the default trading client provided by Interactive Brokers. An alternate solution is to use the Interactive Brokers Gateway client. When starting out, it’s a good idea to use TWS while testing your script as it provides a visual confirmation of any activity in your account. It’s simple to switch to the Gateway later on.
  4. Choose your IDE – We code our Python scripts on a IDE of our choice. However, since we require a constant open connection, not all IDEs are suitable.
  5. Test for connectivity – Check out code sample below

Open an account with IB

We have dedicated a separate blog post on how to do this: “How to Sign Up for an Interactive Brokers Paper Trading Account

To learn how to navigate the IB platform, check out this video: IBKR Short Video – TWS for Beginners – Getting Started

Download the IB Python native API

You can download the Python Native API by navigating to the Interactive Brokers website and by going to Technology Trading APIsGet API Software, or by following this link – http://interactivebrokers.github.io/

Make sure to select API version 9.73 or higher as anything prior to that does not have the Python source files needed. Also, you should be using Python version 3.1 or higher.

Run the downloaded msi file and go through the setup wizard. This will copy the required Python source files to your hard drive. Once completed, navigate over to the directory that you specified in the installer and drill down to this directory – /TWS API/source/pythonclient. In this folder, run the python3 setup.py install file to install the API as a package.

Congratulations! You’ve now installed the IB API. Just to make sure it is installed correctly, go into your Python terminal and type in import ibapi. If no errors appear, the install was successful.

The IB API installer will install a few files that enable compatibility with Excel and also make a registry change in the process. If you’re looking to avoid that, check out the instructions for setting up the API in Linux or on a Mac, the method works just as well for Windows.

Install the IB API in a Mac or Linux

The process is similar to the install described above for Windows. Navigate over to the install page linked above and a ZIP file is available for download under the Mac / Linux column. Unzip the file, and navigate over to IBJts/source/pythonclient and run python3 setup.py install.

IB has written step by step instructions which can be found here – https://ibkb.interactivebrokers.com/article/2484

Final thoughts about installing the IB API

If you choose not to install the IB API Python source as a package, simply place your scripts in the pythonclient folder and run them from there.

Alternatively, take the ibapi folder from within the pythonclient folder and place it in the directory you are creating your scripts to access the API from.

If you’d like to install the IB API Python package in a virtual environment, check out the following link for more details – https://packaging.python.org/tutorials/installing-packages/

Download your IB client (TWS or IB Gateway)

The Native Python API communicates to the IB servers via client software offered by the broker. There are two choices, IB Trader Work Station (TWS) and IB Gateway.

What is TWS?

TWS is the standard client that manual traders use. This client is great when you’re just starting out as it provides visual confirmation of the many commands you can send to IB via Python.

What is IB Gateway

The IB Gateway is a minimal solution that simply allows a connection to be established and requires no configuration out of the box. It’s a great solution if you’re looking to save on resources and it’s the client typically used in application developments.

If you decide to use TWS, navigate over to Trader Workstation Configuration which can be found within the TWS terminal under Edit Global ConfigurationAPI Settings. You should be looking at a screen that looks like this:

Make sure to check off Enable ActiveX and Socket Clients, this is the main requirement for the API.

If you’d like to play it on the safe side, check off Read-Only API to ensure orders don’t get executed accidentally while testing out the API.

Make note of the default Socket port, or optionally change it to another available port if you desire to do so.

IB API socket ports

Lastly, make sure Allow connections from localhost only is checked for security purposes.

The IB gateway is ready to go out of the box so there’s no need to check off the box to enable a connection like in TWS. If you’d like to configure some of the other options described above, go to the configuration page in Gateway by navigating to Configure Settings API Settings.

Choose your IDE

Simply put, an IDE (Integrated development environment) is the software that you code in.

The method used to connect to the IB servers is a rather unique one. There are two common approaches when it comes to communication with trading servers.

The first one involves a direct connection to a server. In such a scenario, a Python script can be coded in your favorite IDE and a connection is made to a server. This is typically done via the requests library or through a websocket.

The second common method is via an IDE provided by the broker which often involves coding in a language proprietary to the broker. TD Ameritrade uses this method. They provide an IDE and code is written in thinkScript which is a proprietary language to TD. Another example is Metatrader, which uses MetaQuotes Language (MQL), and also offers a built-in IDE.

The advantage that IB brings with its API is support for multiple languages and the option to code in your favorite IDE. Supported languages currently include Python, Java, C++, and .NET. There is also support for Microsoft’s ActiveX framework as well as DDE to establish a connection within Excel.

IB-Python-API-Flow-Chart
Establishing a connection to Interactive Brokers’ server

What makes IB unique is that a connection is made to the IB client software which acts as an intermediary to the IB servers. It requires an open, and constant connection which is why we use threading in the examples provided.

This presents a challenge to those that prefer to use an interactive Python development environment such as Jupyter notebooks or Spyder. The EClient functions (outgoing calls) tend to work fine but EWrapper functions (incoming data) present issues due to the lack of an open connection.

IB-insync is a third-party library that utilizes the asyncio library to provide an asynchronous single thread to interact with the API. This might be a solution to explore for those looking to use an interactive environment.

Popular Python IDE’s include IDLE, which is pre-packaged with Python, and PyCharm. VS Code, Sublime Text, and Atom also work great with Python and can be used with other programming languages as well.

If you don’t already have a favorite IDE, Sublime Text is a good option as it offers features such as code completion and syntax highlighting. It’s also easy to customize, compatible with other programming languages, and there are a ton of third-party libraries available to extend functionality.

VS code is also a good option. It offers the same functionality as Sublime Text with the added benefit of embedded Git control. Choosing an IDE comes down to personal preference and there isn’t a clear leader within the Python community when it comes to IDE’s. For this reason it’s worth testing out some of the popular ones to see which one suits your needs best.

Test for connectivity

Here is a simple code snippet to test a connection to the IB API. Make sure you change the socket port number in the function app.connect if needed. The number beside the socket port is a client id used to identify your script to the API. It can be any unique positive integer.

from ibapi.client import EClient
from ibapi.wrapper import EWrapper  

class IBapi(EWrapper, EClient):
     def __init__(self):
         EClient.__init__(self, self) 

app = IBapi()
app.connect('127.0.0.1', 7497, 123)
app.run()

'''
#Uncomment this section if unable to connect
#and to prevent errors on a reconnect
import time
time.sleep(2)
app.disconnect()
'''

Your output should look something like this:

Didn’t get an output? If you’ve tried running the script a few times and you’re not getting an output, change the client id to something unique. Another reason you might not be seeing an output could be because the script ended before a connection was established. In this case, try using a sleep timer at the end of the code snippet to pause the script for a few seconds.

The Importance of EClient and EWrapper

There are several source code files in the IB Python API client folder. The two most important files are EClient and EWrapper.

For the most part, the EClient handles all outgoing requests while the EWrapper handles incoming messages. True to its name, EWrapper acts like a wrapper for incoming messages and in most cases, a function from it will need to be overwritten in your script to redirect the output to where you want it to go.

It’s worthwhile going through some of the source code files to become familiar with the API. And remember, you can always type in help(EClient) or help(EWrapper) in your Python terminal to get more information about the functions contained within them.

All the examples provided here start from the basic script. In it, the EClient and Ewrapper are first imported. A class is then created and both these scripts are passed through into it. At this point, we instantiate the class using the app variable in our examples, and call the app.connect() command to specify the parameters required to create a connection. The app.run() command executes everything while app.disconnect() is used at the end of the script to end the session and close the connection.

We will be adding threading to the basic script. The API connection will run in its own thread to ensure that communication to and from the server is not being blocked by other commands in the main script.

How to retrieve the current ask price of Apple’s Stock (AAPL)

To get the latest ask price of a stock, we create a contract object defining the stock’s parameters and overwrite a function in the EWrapper to have the response printed to the screen.

Alternatively, you can save the response to a file or a variable. In a production environment, you’ll likely save it to a variable.

We also make a call to reqMktData which is a function within the EClient.

Here is the code:

from ibapi.client import EClient
from ibapi.wrapper import EWrapper
from ibapi.contract import Contract

import threading
import time



class IBapi(EWrapper, EClient):
	def __init__(self):
		EClient.__init__(self, self)
	def tickPrice(self, reqId, tickType, price, attrib):
		if tickType == 2 and reqId == 1:
			print('The current ask price is: ', price)

def run_loop():
	app.run()

app = IBapi()
app.connect('127.0.0.1', 7497, 123)

#Start the socket in a thread
api_thread = threading.Thread(target=run_loop, daemon=True)
api_thread.start()

time.sleep(1) #Sleep interval to allow time for connection to server

#Create contract object
apple_contract = Contract()
apple_contract.symbol = 'AAPL'
apple_contract.secType = 'STK'
apple_contract.exchange = 'SMART'
apple_contract.currency = 'USD'

#Request Market Data
app.reqMktData(1, apple_contract, '', False, False, [])

time.sleep(10) #Sleep interval to allow time for incoming price data
app.disconnect()

This code will make a call to request a price data stream for AAPL and print the latest price on the screen as it is updated. Let’s take a look at the parameters required for reqMktData

The ReqId is a unique positive integer you assign to your request which will be included in the response. This way, if you make several market data requests at the same time, you’ll know which returned data belongs to which asset.

The tickType, left empty in this example, allows you to specify what kind of data you’re looking for. Since the ask price is part of the default dataset returned, we don’t need to specify a tickType. You can run the code snippet below to get a full list of all the tickTypes available.

from ibapi.ticktype import TickTypeEnum

for i in range(91):
	print(TickTypeEnum.to_str(i), i)

The numerical value for the ask price is 2, hence the if statement in the tickPrice function in our script to filter out only the ask price.

The fourth parameter under reqMktData is if you want snapshot data for an asset that you do not have a subscription to. If you have a market data subscription, or one is not required, set this to False.

The fifth item is to obtain a snapshot rather than streaming data. This is for assets you already have a subscription for, or if a subscription is not required.

Retrieving market data for other assets – EUR/USD, Bitcoin & Gold

If you’d like to pull the latest ask price for other markets, simply change the contract object as necessary. The rest of the script remains unchanged.

To get the details required for the contract object, simply right click on the asset you need data for in your TWS watchlist and select description. A pop-up box will appear which contains the information you need. It looks something like this:

Now that we have the data required for EUR/USD, let’s create a contract object for it.

eurusd_contract = Contract()
eurusd_contract.symbol = 'EUR'
eurusd_contract.secType = 'CASH'
eurusd_contract.exchange = 'IDEALPRO'
eurusd_contract.currency = 'USD'

And there you have it. Simply swap the contract object in your market data request, as shown in the previous example, to get data for the asset you need.

Interested in trading Bitcoin Futures? Here is an example of a contract object to receive market data:

BTC_futures__contract = Contract()
BTC_futures__contract.symbol = 'BRR'
BTC_futures__contract.secType = 'FUT'
BTC_futures__contract.exchange = 'CMECRYPTO'
BTC_futures__contract.lastTradeDateOrContractMonth  = '202003'

There are a few changes in the above code snippet. First, the contract currency is typically not required for a futures contract. Second, the contract expiry will need to be added.

» If you are keen on futures trading, check out our “5 Futures Trading Strategies Guide“.

And lastly, if you’re a commodities trader, check out how to create a contract for spot gold:

XAUUSD_contract = Contract() 
XAUUSD_contract.symbol = 'XAUUSD' 
XAUUSD_contract.secType = 'CMDTY' 
XAUUSD_contract.exchange = 'SMART' 
XAUUSD_contract.currency = 'USD'

Tip: If you find yourself making a lot of requests for instruments within the same asset class, it might easier to create a function that will create a contract object based on pre-defined parameters.

How to retrieve the last 10 hourly candlebars using the IB Python native API?

Obtaining historical data is very similar to retrieving the latest ask price. The difference is that reqHistoricalData is called rather than reqMktData. We overwrite historicalData to handle the response. Make sure to pass in the bar object which contains all of the data.

from ibapi.client import EClient
from ibapi.wrapper import EWrapper
from ibapi.contract import Contract

import threading
import time

class IBapi(EWrapper, EClient):
	def __init__(self):
		EClient.__init__(self, self)
	def historicalData(self, reqId, bar):
		print(f'Time: {bar.date} Close: {bar.close}')
		
def run_loop():
	app.run()

app = IBapi()
app.connect('127.0.0.1', 7497, 123)

#Start the socket in a thread
api_thread = threading.Thread(target=run_loop, daemon=True)
api_thread.start()

time.sleep(1) #Sleep interval to allow time for connection to server

#Create contract object
eurusd_contract = Contract()
eurusd_contract.symbol = 'EUR'
eurusd_contract.secType = 'CASH'
eurusd_contract.exchange = 'IDEALPRO'
eurusd_contract.currency = 'USD'

#Request historical candles
app.reqHistoricalData(1, eurusd_contract, '', '2 D', '1 hour', 'BID', 0, 2, False, [])

time.sleep(5) #sleep to allow enough time for data to be returned
app.disconnect()

reqHistoricalData requires a few more parameters, here is a breakdown.

Since we are looking for the 10 most recent candles, we can leave the End Date blank.

For the Interval, we selected ‘2 D’ which stands for two days. The interval is calculated from the prior day’s close so if you chose ‘1 D’ , depending on the time of day, you might get less than 10 candles.

Time Period is straightforward and we set this to ‘1 hour’ as we are looking for hourly candles.

The Data Type will typically be either BID, ASK, or MIDPOINT. On most charting platforms, the BID price is used.

For a complete list of available Data Types, Time Period’s, and Interval’s, check out – https://interactivebrokers.github.io/tws-api/historical_bars.html

RTH stands for Regular Trading Hours. It’s mostly used for stocks and if you’re looking for pre-market data, set this to 1.

There are two options for the Time Format. Set it to 1 if you want the response data to contain readable time and set it to 2 for Epcoh (Unix) time. The second option makes it much easier to convert to a Python DateTime object.

Lastly, if Streaming is set to True, it will keep updating price bars every five seconds (even if the candle has not closed).

Note: Since the last candle sent over by IB has likely not closed, it is a good idea to verify whether it has or not, and discard the last candle if needed to ensure accurate data.

What’s the best way to store historical data for later use?

An easy way to store data is by saving it as a CSV file. This can either be done using the standard write to file method in Python, or by using a built-in method in the Pandas Library.

The Pandas library was designed by traders, to be used for trading. Initially at least, it was later modified to accompany a lot more functionality. This library allows for easy data manipulation as well as storage.

from ibapi.client import EClient
from ibapi.wrapper import EWrapper
from ibapi.contract import Contract

import threading
import time

class IBapi(EWrapper, EClient):
	def __init__(self):
		EClient.__init__(self, self)
		self.data = [] #Initialize variable to store candle

	def historicalData(self, reqId, bar):
		print(f'Time: {bar.date} Close: {bar.close}')
		self.data.append([bar.date, bar.close])
		
def run_loop():
	app.run()

app = IBapi()
app.connect('127.0.0.1', 7497, 123)

#Start the socket in a thread
api_thread = threading.Thread(target=run_loop, daemon=True)
api_thread.start()

time.sleep(1) #Sleep interval to allow time for connection to server

#Create contract object
eurusd_contract = Contract()
eurusd_contract.symbol = 'EUR'
eurusd_contract.secType = 'CASH'
eurusd_contract.exchange = 'IDEALPRO'
eurusd_contract.currency = 'USD'

#Request historical candles
app.reqHistoricalData(1, eurusd_contract, '', '2 D', '1 hour', 'BID', 0, 2, False, [])

time.sleep(5) #sleep to allow enough time for data to be returned

#Working with Pandas DataFrames
import pandas

df = pandas.DataFrame(app.data, columns=['DateTime', 'Close'])
df['DateTime'] = pandas.to_datetime(df['DateTime'],unit='s') 
df.to_csv('EURUSD_Hourly.csv')  

print(df)


app.disconnect()

The above code snippet builds on previous example where we retrieved the 10 last hourly candles for EUR/USD. The changes made so that this can be saved as a CSV file are as follows:

First, we created an empty variable called app.data and direct the historicalData function to append candlestick data to it as it comes in.

Then, in order to export the data using Pandas, we created a dataframe. The pandas.to_datetime function is called to convert the incoming data to a DateTime object so that it will be easier to manipulate later on. The .to_csv is an easy way to save the data to a file. To retrieve it later on, simply call the file by running pandas.read_csv(filename)and saving the response to a variable.

3 ways to calculate the 20 SMA

There are several ways to calculate the value of the 20-period simple moving average, we will discuss three. Using pandas, a manual calculation, and utilizing a third-party library. The beauty of doing this in Pandas is that it can be achieved in just one line.

df['20SMA'] = df['Close'].rolling(20).mean()
print(df.tail(10))

That’s all it takes. Your output should look something like this:

Alternatively, if you’d like to manually calculate a moving average, use the following code snippet:

total = 0
for i in app.data[-20:]:  
    total += float(i[1])

print('20SMA =', round(total/20, 5)) 

The above code totals the last 20 candle closes and divides it by 20 to derive at the 20 SMA.

The last method involves using a third-party library called TA-Lib. Several brokers use this library in their custom charting software and it is quite popular. While the original library is not available in Python, a wrapper is available to allow Python users access.

How to fire a trade using the IB Python native API?

To fire an order, we simply create a contract object with the asset details and an order object with the order details. Then call app.placeOrder to submit the order.

The IB API requires an order id associated with all orders and it needs to be a unique positive integer. It also needs to be larger than the last order id used. Fortunately, there is a built in function which will tell you the next available order id.

We can also use this built in function to confirm a connection as this order id gets sent out as soon as a connection is made.

For this reason, we’ve enabled some error checking that tells the script to wait for an order id early on in our script to ensure we are in fact connected.

from ibapi.client import EClient
from ibapi.wrapper import EWrapper
from ibapi.contract import Contract
from ibapi.order import *

import threading
import time

class IBapi(EWrapper, EClient):
	def __init__(self):
		EClient.__init__(self, self)

	def nextValidId(self, orderId: int):
		super().nextValidId(orderId)
		self.nextorderId = orderId
		print('The next valid order id is: ', self.nextorderId)

	def orderStatus(self, orderId, status, filled, remaining, avgFullPrice, permId, parentId, lastFillPrice, clientId, whyHeld, mktCapPrice):
		print('orderStatus - orderid:', orderId, 'status:', status, 'filled', filled, 'remaining', remaining, 'lastFillPrice', lastFillPrice)
	
	def openOrder(self, orderId, contract, order, orderState):
		print('openOrder id:', orderId, contract.symbol, contract.secType, '@', contract.exchange, ':', order.action, order.orderType, order.totalQuantity, orderState.status)

	def execDetails(self, reqId, contract, execution):
		print('Order Executed: ', reqId, contract.symbol, contract.secType, contract.currency, execution.execId, execution.orderId, execution.shares, execution.lastLiquidity)


def run_loop():
	app.run()

#Function to create FX Order contract
def FX_order(symbol):
	contract = Contract()
	contract.symbol = symbol[:3]
	contract.secType = 'CASH'
	contract.exchange = 'IDEALPRO'
	contract.currency = symbol[3:]
	return contract

app = IBapi()
app.connect('127.0.0.1', 7497, 123)

app.nextorderId = None

#Start the socket in a thread
api_thread = threading.Thread(target=run_loop, daemon=True)
api_thread.start()

#Check if the API is connected via orderid
while True:
	if isinstance(app.nextorderId, int):
		print('connected')
		break
	else:
		print('waiting for connection')
		time.sleep(1)

#Create order object
order = Order()
order.action = 'BUY'
order.totalQuantity = 100000
order.orderType = 'LMT'
order.lmtPrice = '1.10'

#Place order
app.placeOrder(app.nextorderId, FX_order('EURUSD'), order)
#app.nextorderId += 1

time.sleep(3)

#Cancel order 
print('cancelling order')
app.cancelOrder(app.nextorderId)

time.sleep(3)
app.disconnect()

In order to confirm that a connection is established, we are waiting for the API to send over the nextorderid and holding the script in a loop with a sleep timer until it is received. This is to confirm that a connection has been established.

In addition to that, we’ve also created a function to create a contract specific to Forex. This simplifies contract creation as most of the parameters are similar.

To place an order, we create an order object which specifies whether you’re looking to buy or sell. The order size and limit price are also set here. If you’d like to create a market order, set order.orderType to ‘MKT’ and comment out the orderlmtPrice.

Remember to increment your nextorderId after placing an order.

You’ll also notice several additional functions defined near the top of the script. These are all the messages returned by EWrapper associated with placing orders. Similar to before, you might want to save some of these to variables for later use.

Here is what your output should look like after running the above script:

The API treats many items as errors even though they are not. For example, the order cancellation came up as an error even though there were no issues. This can be changed by overriding the EWrapper function for error messages. Here is an example:

def error(self, reqId, errorCode, errorString):
	if errorCode == 202:
		print('order canceled') 

A complete list of API codes can be found here – https://interactivebrokers.github.io/tws-api/message_codes.html

It is a good idea to use the codes associated with market data connections to ensure you have an active data connection and implement error checking when submitting orders to ensure the connection is active and price data is fresh.

How to implement a stop loss or take profit using the IB Python native API?

A stop loss is essentially an order to execute once a certain price is reached. It’s a good idea to ‘group’ stop loss orders with your original order. This way, if you decide to delete your original order, your stop order gets deleted automatically.

IB refers to the grouping of orders as a bracket order. The main order is considered the ‘parent’ and the stop loss, or take profit, is considered a ‘child’ order.

The two orders are tied together by assigning the order number of the parent order as a parentId in the child order. Although these two orders form one bracket order, note that a separate orderId is required for both orders so remember to increment and assign an orderId to your stop loss or take profit orders.

Another important thing to keep in mind is that the parent order has the line order.transmit = False. This is to ensure the first order does not get processed until the rest of the bracket orders are transmitted. The last order sent via placeOrder should have order.transmit = True to process the entire bracket order.

from ibapi.client import EClient
from ibapi.wrapper import EWrapper
from ibapi.contract import Contract
from ibapi.order import *

import threading
import time

class IBapi(EWrapper, EClient):
	
	def __init__(self):
		EClient.__init__(self, self)
	
	def nextValidId(self, orderId: int):
		super().nextValidId(orderId)
		self.nextorderId = orderId
		print('The next valid order id is: ', self.nextorderId)

	def orderStatus(self, orderId, status, filled, remaining, avgFullPrice, permId, parentId, lastFillPrice, clientId, whyHeld, mktCapPrice):
		print('orderStatus - orderid:', orderId, 'status:', status, 'filled', filled, 'remaining', remaining, 'lastFillPrice', lastFillPrice)
	
	def openOrder(self, orderId, contract, order, orderState):
		print('openOrder id:', orderId, contract.symbol, contract.secType, '@', contract.exchange, ':', order.action, order.orderType, order.totalQuantity, orderState.status)

	def execDetails(self, reqId, contract, execution):
		print('Order Executed: ', reqId, contract.symbol, contract.secType, contract.currency, execution.execId, execution.orderId, execution.shares, execution.lastLiquidity)


def run_loop():
	app.run()

def FX_order(symbol):
	contract = Contract()
	contract.symbol = symbol[:3]
	contract.secType = 'CASH'
	contract.exchange = 'IDEALPRO'
	contract.currency = symbol[3:]
	return contract

app = IBapi()
app.connect('127.0.0.1', 7497, 123)

app.nextorderId = None

#Start the socket in a thread
api_thread = threading.Thread(target=run_loop, daemon=True)
api_thread.start()

#Check if the API is connected via orderid
while True:
	if isinstance(app.nextorderId, int):
		print('connected')
		print()
		break
	else:
		print('waiting for connection')
		time.sleep(1)

#Create order object
order = Order()
order.action = 'BUY'
order.totalQuantity = 100000
order.orderType = 'LMT'
order.lmtPrice = '1.10'
order.orderId = app.nextorderId
app.nextorderId += 1
order.transmit = False

#Create stop loss order object
stop_order = Order()
stop_order.action = 'SELL'
stop_order.totalQuantity = 100000
stop_order.orderType = 'STP'
stop_order.auxPrice = '1.09'
stop_order.orderId = app.nextorderId
app.nextorderId += 1
stop_order.parentId = order.orderId
order.transmit = True

#Place orders
app.placeOrder(order.orderId, FX_order('EURUSD'), order)
app.placeOrder(stop_order.orderId, FX_order('EURUSD'), stop_order)

app.disconnect()

A take profit can be added by creating an Order() object similar to how we created the stop loss order above.

The variable for price in a take profit might look something like this take_profit.lmtPrice since the take profit is a limit order. So use that instead of stop_order.auxPrice. Remember, whichever order is sent last should have the transmit=True while the rest should have transmit=False.

How to fire an order for Apple when Google hits a certain price?

A big advantage to Interactive Brokers is that it supports advanced order types, it even has several that most other brokers do not support.

We will highlight an advanced order type in the next example where we will show how to execute a trade in Apple (AAPL) once Google (GOOG) has crossed a certain price point.

With other brokers, you might need to manually track Google’s stock price, and once the condition is met, send in an order. However, there’s a much cleaner solution that allows us to send an order and let IB’s servers track when the conditions are met, so that the trade can be executed.

Since we are using a special order feature, we need to import two classes from ibapi.order_condition.

from ibapi.client import EClient
from ibapi.wrapper import EWrapper
from ibapi.contract import Contract
from ibapi.order_condition import Create, OrderCondition
from ibapi.order import *

import threading
import time

Along with that, we have some of the same imports used in prior examples to create a contract and an order object.

class IBapi(EWrapper, EClient):
	def __init__(self):
		EClient.__init__(self, self)
		self.contract_details = {} #Contract details will be stored here using reqId as a dictionary key

	def nextValidId(self, orderId: int):
		super().nextValidId(orderId)
		self.nextorderId = orderId
		print('The next valid order id is: ', self.nextorderId)

	def orderStatus(self, orderId, status, filled, remaining, avgFullPrice, permId, parentId, lastFillPrice, clientId, whyHeld, mktCapPrice):
		print('orderStatus - orderid:', orderId, 'status:', status, 'filled', filled, 'remaining', remaining, 'lastFillPrice', lastFillPrice)
	
	def openOrder(self, orderId, contract, order, orderState):
		print('openOrder id:', orderId, contract.symbol, contract.secType, '@', contract.exchange, ':', order.action, order.orderType, order.totalQuantity, orderState.status)

	def execDetails(self, reqId, contract, execution):
		print('Order Executed: ', reqId, contract.symbol, contract.secType, contract.currency, execution.execId, execution.orderId, execution.shares, execution.lastLiquidity)

Next, we’ve overwritten a few more functions that will return data once the order has been sent, and when the order has been executed. All we are doing is directing the API to print this information out to the console, just to illustrate how they work.

The next code snippet is a bit more pertinent to what we are trying to accomplish. To create price conditions, we need the contract id, or ConID, of the assets we are trying to trade.

You can get this id by searching the IB Contract and Symbol Database. But a much easier way is to use the reqContractDetails functions of the API. It will return a contract with the ConID already filled in.

	def contractDetails(self, reqId: int, contractDetails):
		self.contract_details[reqId] = contractDetails

	def get_contract_details(self, reqId, contract):
		self.contract_details[reqId] = None
		self.reqContractDetails(reqId, contract)
		#Error checking loop - breaks from loop once contract details are obtained
		for err_check in range(50):
			if not self.contract_details[reqId]:
				time.sleep(0.1)
			else:
				break
		#Raise if error checking loop count maxed out (contract details not obtained)
		if err_check == 49:
			raise Exception('error getting contract details')
		#Return contract details otherwise
		return app.contract_details[reqId].contract

There are two functions to get the updated contract that includes a ConID. The first is contractDetails which is a function of the EWrapper. When we request contract details, it will get returned here.

We will store whatever is returned here in a dictionary file. The request id, or reqId, that we use to make the request, will be used as the key value for the dictionary.

Next, we have created a custom function for requesting contract details. To access it, we have to pass through a reqId and the contract that we are requesting details for.

This function will send the request to the API, and then wait for a response to be returned.

We want to do some error checking at this point. So a loop has been set to run 50 times. It checks to see if our contract details have been returned, and if so, the loop is broken.

If the loop runs a full 50 times, meaning it didn’t successfully break out, the value of err_check will be 49. In this case, we will raise an exception to alert us that there is a problem getting the contract details.

Now that we’ve finished our class functions, let’s move on to the main script. Here we’ve created two functions.

def run_loop():
	app.run()

def Stock_contract(symbol, secType='STK', exchange='SMART', currency='USD'):
	''' custom function to create stock contract '''
	contract = Contract()
	contract.symbol = symbol
	contract.secType = secType
	contract.exchange = exchange
	contract.currency = currency
	return contract

The first is simply a function that we will later call to run our app in a thread, similar to prior examples. Anything that needs to be declared, or run when this thread starts, can be added to the run_loop function.

The second function is to simplify creating contracts. We’ve passed in some default values as most stocks will fall into the same category.

app = IBapi()
app.connect('127.0.0.1', 7496, 123)

app.nextorderId = None

#Start the socket in a thread
api_thread = threading.Thread(target=run_loop, daemon=True)
api_thread.start()

#Check if the API is connected via orderid
while True:
	if isinstance(app.nextorderId, int):
		print('connected')
		break
	else:
		print('waiting for connection')
		time.sleep(1)

The above code is similar to the prior examples. We’ve connected to the API, started a thread, and checked to see if the nextorderid exists to confirm a connection.

#Create contracts
apple_contract = Stock_contract('AAPL')
google_contract = Stock_contract('GOOG')

Our next step is to create two contracts, one for GOOG and one for AAPL. Since we are only using the price condition function based on the price of GOOG, the ConID for just that contract is needed.

#Update contract ID
google_contract = app.get_contract_details(101, google_contract)

Therefore, we used our custom get_contract_details function to update the Google contract and not the Apple contract.

We are finally ready to create our price condition. The first step is to create an order condition object.

priceCondition = Create(OrderCondition.Price)

Let’s break down the above code. priceCondition is simply the name of the variable that will store our conditions. You can name this anything you want.

Create is a function from the order_condition.py file found within the API. True to its name, it is used to create an object, or rather, instantiate the right class for our needs.

In this case, we need the PriceCondition class, so that’s where OrderCondition.price comes in.

There are several other types of conditions that you can create and this is where you declare which one you are after.

There are six different types of order conditions in total – Price, Time, Margin, Execution, Volume, and PercentChange.

So for example, if you want to create a condition based on the percentage change for the day, you would use priceCondition = Create(OrderCondition.PercentChange) instead.

Next, we pass through the contract ID of the asset we are setting the condition on, and the exchange it trades from.

priceCondition.conId = google_contract.conId
priceCondition.exchange = google_contract.exchange

This info is already within the contract object, so we just point it to the appropriate attribute of the contract.

We’re ready to set some conditions.

#create conditions
priceCondition.isMore = True
priceCondition.triggerMethod = priceCondition.TriggerMethodEnum.Last
priceCondition.price = 1400.00

We want Google’s price to be above $1400 to execute this trade. So we’ve set the .isMore attribute to True, and have added in a float value of 1400.00 to the .price attribute.

The trigger method that we want to use is the last price that GOOG traded at.

The API requires the trigger method to be entered as an integer, but there is a function called TriggerMethodEnum that will convert the value Last into an integer, which is what we’ve done here.

Our price condition is complete and ready to go. All that’s left now is to add the condition to an order and submit it.

#Create order object
order = Order()
order.action = 'BUY'
order.totalQuantity = 100
order.orderType = 'MKT'
#order.lmtPrice = '300' - optional - you can add a buy limit here

In the above code, we’ve created an order in the same way we’ve done in prior examples. Note that we can create a limit order here. Let’s say we set a limit of $300.

In that scenario, the order would get triggered once GOOG crosses above $1400, but the order would be sent to buy AAPL at $300.

That buy order would remain active no matter what GOOG does next, but won’t be triggered unless AAPL falls back down to $300. If AAPL is already trading at $300 or below at that time, it will get triggered right away.

We are going with a market order, but if you do decide on a limit order, make sure to change the orderType to ‘LMT’.

The price condition we created before still needs to be added to the order. Here’s how to do that:

order.conditions.append(priceCondition)

And don’t forget to set the order.transmit to True.

order.transmit = True

The final step is to submit the order.

app.placeOrder(app.nextorderId, apple_contract, order)

At this point, the order is sitting on IB’s server and it will be managed from there. Even if we shut down our script, that order will remain active and IB will execute it when the conditions are met.

How to fire an order for Apple when Google moves more than 5% within the last 5 minutes?

This strategy has some similarities to the last one, although we need to take an entirely different approach and code this manually.

The price condition function does allow us to submit orders based on a percentage price change, however, it calculates this change from the start of the day.

What we are after, is a price change that occurred in the last 5 minutes.

We will subscribe to tick data and store it in a Panda’s DataFrame. This will allow us to check for a 5% change at which point we can submit an order.

from ibapi.client import EClient
from ibapi.wrapper import EWrapper
from ibapi.contract import Contract
from ibapi.order import *

import pandas as pd
import threading
import time

We start with our imports, the only thing new here is that we’ve imported pandas.

class IBapi(EWrapper, EClient):
	def __init__(self):
		EClient.__init__(self, self)
		self.bardata = {} #Initialize dictionary to store bar data
	
	def nextValidId(self, orderId: int):
		super().nextValidId(orderId)
		self.nextorderId = orderId
		print('The next valid order id is: ', self.nextorderId)

The class functions so far should look familiar as well. The only thing different here is that we’ve created a dictionary file named bardata. We will use this later to store our price DataFrame.

	def tick_df(self, reqId, contract):
		''' custom function to init DataFrame and request Tick Data '''
		self.bardata[reqId] = pd.DataFrame(columns=['time', 'price'])
		self.bardata[reqId].set_index('time', inplace=True)
		self.reqTickByTickData(reqId, contract, "Last", 0, True)
		return self.bardata[reqId]

Here we’ve created a custom function. It will create an empty DataFrame and set the index to the time column. This way, we will have a time-series indexed DataFrame which simplifies things later when we have to narrow our data down to a 5-minute window.

At the same time, we’ve used the reqTickByTickData function, which is from the EClient, to start the data stream.

The reason this is set up as a custom function, is so that several data feeds can be started, each with its own separate DataFrame. Once again, the reqId will be used as the key so all the data can be accessed from the variable bardata that we declared in our __init__ function earlier.

Next, we will overwrite the tickByTickAllLast function of the EWrapper.

	def tickByTickAllLast(self, reqId, tickType, time, price, size, tickAtrribLast, exchange, specialConditions):
		if tickType == 1:
			self.bardata[reqId].loc[pd.to_datetime(time, unit='s')] = price

This function will return the last price. The tick type for that is 1. The function should not return any other type of data, but we are checking to make sure the tick type is in fact 1 before adding to our DataFrame, just to be sure.

Let’s break down the next line of code. self.bardata[reqId] is the bardata dictionary file with the reqId as the key. In other words, this is our pandas DataFrame.

The .loc function comes from pandas and it allows us to specify the row and column that we want to insert data into.

We are creating a new row, using the time as an index. In that row, we insert the last price under the price column.

Panda’s will often recognize when a timestamp is being passed through and automatically convert it to a DateTime value. In this case, it didn’t. That is why we’ve used pd.to_datetime(time, unit='s') to convert out time value to a DateTime value using a built-in function of Pandas.

	def Stock_contract(self, symbol, secType='STK', exchange='SMART', currency='USD'):
		''' custom function to create contract '''
		contract = Contract()
		contract.symbol = symbol
		contract.secType = secType
		contract.exchange = exchange
		contract.currency = currency
		return contract

The last thing we’ve done is created a custom function to make it easier to create contracts for stocks.

Note that it is created within the class where in the last example we created it outside the class.

Both methods work and will deliver the same end result. If you plan to create multiple scripts and think you will use a particular function in each one of them, it makes sense to write it within the class.

This way, you can import the class into another script without having to rewrite the same functions.

We’ve created a few functions outside of our class.

def run_loop():
	app.run()

def submit_order(contract, direction, qty=100, ordertype='MKT', transmit=True):
	#Create order object
	order = Order()
	order.action = direction
	order.totalQuantity = qty
	order.orderType = ordertype
	order.transmit = transmit
	#submit order
	app.placeOrder(app.nextorderId, contract, order)
	app.nextorderId += 1

The second function simplifies the process of submitting orders. This is a good example of something that could have been included in the class.

However, we’ve gone over a few different order types such as bracket orders that include stop-loss levels or take profit levels, and price condition orders. Due to the complexity of order processing, it made more sense to not include it in the class.

Next, we have our strategy function. This is where the decision making happens on whether we should execute a trade or not.

def check_for_trade(df, contract):
	start_time = df.index[-1] - pd.Timedelta(minutes=5)
	min_value = df[start_time:].price.min()
	max_value = df[start_time:].price.max()

	if df.price.iloc[-1] < max_value * 0.95:
		submit_order(contract, 'SELL')
		return True

	elif df.price.iloc[-1] > min_value * 1.05:
		submit_order(contract, 'BUY')
		return True

The first line is taking the very last index value in our DataFrame, which is the time value of the last data we received. We subtract 5 minutes from that time value using the Timedelta method built-in to Pandas.

Now we know how far back to look by using start_time. In the following line of code, we’ve used df[start_time:] which returns all the data from 5 minutes ago until now.

We can then use the min() and max() functions from Pandas to determine the high and low over the last five minutes.

With those values, we can check to see if the current price, the very last price value in our data frame, is 5% greater than or less than the min or max.

If the condition is met we submit an order. The function will also return a boolean value of True. This way we know an order has been submitted.

We can move onto our main script at this point.

#Main
app = IBapi()
app.nextorderId = None
app.connect('127.0.0.1', 7496, 123)

#Start the socket in a thread
api_thread = threading.Thread(target=run_loop)
api_thread.start()

#Check if the API is connected via orderid
while True:
	if isinstance(app.nextorderId, int):
		print('connected')
		break
	else:
		print('waiting for connection')
		time.sleep(1)

#Create contract object
google_contract = app.Stock_contract('GOOG')
apple_contract = app.Stock_contract('AAPL')

The above script is unchanged from the prior example. It connects to the API, starts a thread, and makes sure a connection is established by checking for the next valid order id. We’ve also created two stock contracts.

#Request tick data for google using custom function
df = app.tick_df(401, google_contract)

Here we are starting out data stream for GOOG. Recall that we made a function for this within our class. We just need to pass through a reqId, which can be any unique integer, and the contract.

#Verify data stream
time.sleep(10)
for i in range(100):
	if len(df) > 0:
		break
	time.sleep(0.3)
	
if i == 99:
	app.disconnect()
	raise Exception ('Error with Tick data stream')

Next, we just want to verify that data is coming into our DataFrame from the stream. We give this some time, but if it fails, an exception will be raised. We accomplish this by checking to make sure the length of the DataFrame is greater than 0.

At this point, we know the data streaming is working and we are capturing it in our DataFrame. But we still need five minutes’ worth of data before we can start executing trades.

#Check if there is enough data
data_length = df.index[-1] - df.index[0]
if data_length.seconds < 300:
	time.sleep(300 - data_length.seconds)

In the above code, we check how many seconds have already passed by subtracting the very last time value in the DataFrame by the very first.

We need at least 5 minutes, or 300 seconds, worth of data. So we will put the script to sleep for 300 seconds minus whatever time has already elapsed.

Now that everything is set, we are ready to start searching for a trade.

#Main loop
while True:
	if check_for_trade(df, apple_contract): break
	time.sleep(0.1)

app.disconnect()

The above code is an infinite loop that calls the check_for_trade function to see if a 5% deviation has taken place, and execute a trade if it has.

Recall that the function returns a True boolean value if a trade is executed? If that happens, the script will break out of the infinite loop and end.

If you want to keep the script running continuously, you can remove the if and : break from the above code snippet. If you go that route, it’s a good idea to implement a 5-minute sleep if a trade was executed.

Otherwise, the script will send several consecutive orders once the conditions are met since it is running in an infinite loop.

Lastly, we’ve added a 0.1 second sleep to very briefly pause the script after each check. This is to avoid our CPU’s going into overdrive while executing an infinite loop.

There are a few different ways to stream data with the API. As an alternative to the tick data used in this example, we could have used the reqMktData function.

Both methods have their caveats. The reqMktData function sends out tick data every 250 ms (for Stocks and Futures). Therefore, the data is not as accurate as reqTickByTickData.

Interestingly, reqMktData does not return the time the trade took place, which is the main reason it wasn’t used in this example.

The reqTickByTickData is more accurate but will either return the last price or the bid and ask. And, separate EWrapper functions are used to manage these.

When using reqTickByTickData, there is the possibility of several trades coming in rapidly with the same timestamp. This can cause data loss since we are storing our data based on the time value.

Perhaps the IB developers will consider these inconsistencies in their future releases. For now, it might be worthwhile checking out both of these endpoints to determine which one works best for your system.

How to send notifications via telegram and the IB Python native API?

Now that you’re able to get market data and create orders, you might want to implement some kind of an alert system. Perhaps when an order gets triggered, or a certain price point is reached.

Telegram allows for an easy way to create a live alert and it is also capable of two way communication.

To setup a bot in the Telegram:

  1. Open a chat with the ‘BotFather’ from within Telegram.
  2. Type in the command /newbot
  3. It will prompt you to enter a bot name and send you a access token.
  4. Open a new chat with your newly created bot.
  5. Type something in to activate the chat.
  6. Go to the following URL – https://api.telegram.org/botxxxxxxxxxxxxxxxx/getUpdates – replacing the XXX with your access token. Here you should see a JSON structure. Note down the id (not to be confused with update_id or message_id).

At this point, the bot is created and messages can be sent to it. Here is a code snippet to test if everything is working:

import requests
def send(text):
	token = 'your_token_goes_here'
	params = {'chat_id': xxx, 'text': text, 'parse_mode': 'HTML'}
	resp = requests.post('https://api.telegram.org/bot{}/sendMessage'.format(token), params)
	resp.raise_for_status()
send('hello')

Remember to update the script with your own access token and chat id.

You should have received a ‘hello’ message in your Telegram chat. You can now use this script to send several different types of useful messages from your Python script.

For example, you might want to get a Telegram alert every time your script fires off an order. Here is a way you might do that:

def send(pair, order, stop_order):
	#Replace token, chat_id & text variables
	text = f'A new trade has been placed in {pair} at {order.lmitPrice} with a stop at {stop_order.auxPrice}'
    
    token = 'xxx'
    params = {'chat_id': xxx, 'text': text, 'parse_mode': 'HTML'}
    resp = requests.post('https://api.telegram.org/bot{}/sendMessage'.format(token), params)
    resp.raise_for_status()

send('EURUSD', order, stop_order)

This provides an easy way to keep on top of any orders executed. You can also utilize the alert system in a try/except block to pick up any errors that the script might be picking up on. While logging is often used in such scenario’s, there is a higher sense of urgency in algo trading when it comes to script problems which Telegram can address.

Common Errors with the IB Python Native API v9.76

All the code examples in this article utilized version 9.76 of the IB Python native API, which is the most recent stable version as of June 01, 2020.

We have come across a couple of errors with this version of the API. First, there is an issue with running the disconnect() command. The API is not handling a particular error correctly and therefore ends without properly disconnecting the socket connection.

This should not cause any problems when it comes to trade execution unless your script often disconnects and reconnects. In our examples, we only disconnected once the script was finished.

Nevertheless, it can become troublesome as the API considers the last connection still active, and therefore won’t allow subsequent connections. The workaround is to change your client ID but this can become tedious quick.

We’ve found a solution created by Thane Booker and have uploaded the code on to GitHub. It’s specific to the reader.py file and it essentially wraps the affected portion of the code in a try/except block to catch the error and allow a proper disconnect.

The second error is similar. It also involves a socket error and a particular script within the API not catching an error. We’ve had a few readers report that they were unable to get the test for connectivity example to work on their systems because of this error.

It usually returns an error related to this line – _recvAllMsg buf = self.socket.recv(4096) which is from the connection.py file.

The script is not handling a socket error. Oddly, this was being handled in version 9.74 and is once again implemented in the latest version, 9.79.

We have uploaded the connection.py file from v9.79 to GitHub for those that want to remain on the stable version but are facing this error.

If you’ve installed the API on your system, these files can be replaced by navigating over to your Python directory. To find out where that is, use the following code in your terminal.

import sys
sys.executable

This should give you the path to the Python executable. From there, navigate to the Lib folder, and then the ibapi folder. You should see both reader.py and connection.py under this folder.

Download the full codebase

Github link: https://github.com/PythonForForex/Interactive-brokers-python-api-guide (Click the green button on the right “Clone or download” to download or clone the code)

What’s next?

Now that you have learnt some programming. Learn some trading from our sentiment analysis or futures trading guides!

Jignesh Davda