Part 9: Coding the Multi-Strategy Automation Bot
Now, let's write the core Python trading script that executes our options strategies. We will structure this bot to be parameterized, allowing us to run Vertical Spreads, Iron Condors, or Iron Butterflies simply by changing our configuration parameters.
We will use the Shoonya (Finvasia) or Flattrade API SDK. Both offer free API access.
1. Authentication and Option Chain Processing
Options trading APIs require a daily token generation using your username, password, API key, and a TOTP (Time-based One-Time Password) code generated from your 2FA secret key.
Here is the initialization script (src/bot.py):
import os
import time
import pyotp
import pandas as pd
from NorenRestApiPy.NorenApi import NorenApi
class TradingBot(NorenApi):
def __init__(self):
NorenApi.__init__(self)
def login_to_broker(self):
user = os.getenv("BROKER_USER_ID")
password = os.getenv("BROKER_PASSWORD")
api_key = os.getenv("BROKER_API_KEY")
totp_key = os.getenv("BROKER_TOTP_KEY")
# Generate TOTP dynamically
totp = pyotp.TOTP(totp_key).now()
response = self.login(
userid=user,
password=password,
twoFA=totp,
private_key=api_key,
vendor_code=os.getenv("VENDOR_CODE"),
imei="mac_address"
)
return response
bot = TradingBot()
bot.login_to_broker()
2. Parameterized Strike Selection
To execute strategies programmatically, we must parse the live option chain. We fetch the Spot index price, calculate strike levels, and filter options based on their Delta value.
Here is how we query the option chain and select strikes:
def select_strategy_strikes(bot, underlying="NSE:NIFTY50", strategy="IRON_CONDOR"):
# Get spot price
quote = bot.get_quotes(exchange="NSE", token="26000") # Nifty 50 token
spot_price = float(quote["lp"])
# Calculate ATM Strike (rounded to nearest 50 points)
atm_strike = round(spot_price / 50) * 50
# Fetch Option Chain
chain = bot.get_option_chain(
exchange="NFO",
tradingsymbol="NIFTY",
strikeprice=atm_strike,
count=15 # Get 15 strikes above and below
)
df = pd.DataFrame(chain["options"])
if strategy == "IRON_CONDOR":
# Strategy: Sell OTM strikes (e.g. Spot +- 300), Buy further OTM (Spot +- 400)
short_ce = df[df["strike"] == atm_strike + 300].iloc[0]
long_ce = df[df["strike"] == atm_strike + 400].iloc[0]
short_pe = df[df["strike"] == atm_strike - 300].iloc[0]
long_pe = df[df["strike"] == atm_strike - 400].iloc[0]
return {
"sell_legs": [short_ce, short_pe],
"buy_legs": [long_ce, long_pe]
}
elif strategy == "IRON_BUTTERFLY":
# Strategy: Sell ATM CE/PE, Buy OTM CE/PE protection (Spot +- 150)
short_ce = df[df["strike"] == atm_strike].iloc[0]
short_pe = df[df["strike"] == atm_strike].iloc[0]
long_ce = df[df["strike"] == atm_strike + 150].iloc[0]
long_pe = df[df["strike"] == atm_strike - 150].iloc[0]
return {
"sell_legs": [short_ce, short_pe],
"buy_legs": [long_ce, long_pe]
}
3. Placing Multi-Leg Orders
To minimize execution delay, we must place our buying protection legs before our selling credit legs. Placing buying legs first:
- Reduces Margin Requirements: Exchanges credit your margin benefit immediately. If you place short legs first, you need ₹1.5 Lakhs per leg. If you place long legs first, the margin drops to ~₹50,000 for the entire basket.
- Prevents Margin Rejections: Avoids order failures due to insufficient balance.
def place_basket_order(bot, legs):
# 1. Place BUY orders first for margin benefit
for leg in legs["buy_legs"]:
bot.place_order(
buy_or_sell="B",
product_type="M", # Margin order
exchange="NFO",
tradingsymbol=leg["tradingsymbol"],
quantity=50, # 1 Lot Nifty
price_type="LMT", # Limit order to avoid slippage
price=float(leg["ask_price"])
)
# Wait 500ms to ensure fills
time.sleep(0.5)
# 2. Place SELL orders
for leg in legs["sell_legs"]:
bot.place_order(
buy_or_sell="S",
product_type="M",
exchange="NFO",
tradingsymbol=leg["tradingsymbol"],
quantity=50,
price_type="LMT",
price=float(leg["bid_price"])
)
In the next part, we will look at how to implement risk management and adjustments to defend our positions if the market moves against us.
Proceed to Part 10: Algorithmic Risk Management & Position Adjustments →
Comments
Comments are powered by giscus. Set
PUBLIC_GISCUS_REPO_IDandPUBLIC_GISCUS_CATEGORY_IDin your environment to enable them.