Getting error while backtesting the Option Trading strategy

I have purshased the course on Machine Lerning for Options Trading. They have used the data of SPY. For the same program I have used the data of RELIANCE from yfinance and NSE Options data. I am getting error in MTM function. Below are the code snippet.

 

# Function to get the premium for an option contract
def get_premium(options_strategy, options_data):

    # Create to condition to assure we choose the correct option strike price
    strike = options_strategy['Strike Price']

    # Create to condition to assure we choose the correct option type
    option_type = options_strategy['Option Type']

    # Return the last price of the option that complies with the above conditions
    if option_type == 'CE':
        return options_data[options_data['STRIKE'] == strike].C_LAST
    if option_type == 'PE':
        return options_data[options_data['STRIKE'] == strike].P_LAST
    return 0

def setup_call_spread(options_data, strike_difference=20):
    
    # Create a dataframe
    call_spread = pd.DataFrame(columns=['Option Type', 'Strike Price', 'position', 'premium'])

    underlying_price = options_data['UNDERLYING_LAST'][0]

    # Calculate ATM strike price
    atm_strike_price = strike_difference * (round(underlying_price / strike_difference))

    # Set up first leg of the spread
    call_spread.loc['0'] = ['CE', atm_strike_price, 1, np.nan]
    
    # Append premium for the leg
    call_spread['premium'] = call_spread.apply(lambda r: get_premium(r, options_data), axis=1)
    
    # Define price deviation for next leg of spread
    deviation = round(call_spread.premium.sum()*4 / strike_difference) * strike_difference

    # Set up next leg of the spread
    call_spread.loc['1'] = ['CE', atm_strike_price + deviation, -1, np.nan]
    
    # Append respective premiums for the legs
    call_spread['premium'] = call_spread.apply(lambda r: get_premium(r, options_data), axis=1)

    return call_spread


# Create dataframes for round trips, storing trades, and mtm
round_trips_details = pd.DataFrame()
trades = pd.DataFrame()
mark_to_market = pd.DataFrame()

# Function for calculating mtm


def add_to_mtm(mark_to_market, option_strategy, trading_date):
    option_strategy['Date'] = trading_date
    mark_to_market = pd.concat([mark_to_market, option_strategy])
    return mark_to_market


# Initialise current position, number of trades and cumulative pnl to 0
current_position = 0
trade_num = 0
cum_pnl = 0

# Set exit flag to False
exit_flag = False

# Set start date for backtesting
start_date = data.index[0] #.strftime('%Y-%m-%d')
#start_date = start_date.date() #.strftime('%Y-%m-%d')

for i in data.loc[start_date:].index:

    if (current_position == 0) & (data.loc[i, 'signal'] != 0):

        # Set up option spread strategy
        try:
            options_data_daily = options_data.loc[i]
        except:
            continue

        # If signal is 1 we will set up bull call spread
        if data.loc[i, 'signal'] == 1:
            spread = setup_call_spread(options_data_daily, 10)

        else:
            continue

        # Check that the last price of any of the leg of the spread should be greater than 0
        if (spread.premium.isna().sum() > 0) or ((spread.premium == 0).sum() > 0):
            print(
                f"\x1b[31mStrike price not liquid so we will ignore this trading opportunity {i}\x1b[0m")
            continue

        # Populate the trades dataframe
        trades = spread.copy()
        trades['entry_date'] = i
        trades.rename(columns={'premium': 'entry_price'}, inplace=True)

        # Calculate net premium
        net_premium = round((spread.position * spread.premium).sum(), 1)

        # Compute SL and TP for the trade
        premium_sign = np.sign(net_premium)
        sl = net_premium * \
            (1 - config['stop_loss_percentage']*premium_sign/100)
        tp = net_premium * \
            (1 + config['take_profit_percentage']*premium_sign/100)

        # Update current position
        current_position = data.loc[i, 'signal']

        # Update mark_to_market dataframe
        mark_to_market = add_to_mtm(mark_to_market, spread, i)

        # Increase number of trades by 1
        trade_num += 1
        print("-"*30)

        # Print trade details
        print(
            f"Trade No: {trade_num} | Entry | Date: {i} | Premium: {net_premium*-1} | Position: {current_position}")

    elif current_position != 0:

        # Update net premium
        try:
            options_data_daily = options_data.loc[i]
        except:
            continue

        spread['premium'] = spread.apply(
            lambda r: get_premium(r, options_data_daily), axis=1)
        net_premium = (spread.position * spread.premium).sum()

        # Update mark_to_market dataframe
        mark_to_market = add_to_mtm(mark_to_market, spread, i)

        # Exit the trade if any of the exit condition is met
        if data.loc[i, 'signal'] != current_position:
            exit_type = 'Expiry or Signal Based'
            exit_flag = True

        elif net_premium < sl:
            exit_type = 'SL'
            exit_flag = True

        elif net_premium > tp:
            exit_type = 'TP'
            exit_flag = True

        if exit_flag:

            # Check that the data is present for all strike prices on the exit date
            if spread.premium.isna().sum() > 0:
                print(
                    f"Data missing for the required strike prices on {i}, Not adding to trade logs.")
                current_position = 0
                continue

            # Append the trades dataframe
            trades['exit_date'] = i
            trades['exit_type'] = exit_type
            trades['exit_price'] = spread.premium

            # Add the trade logs to round trip details
            round_trips_details = pd.concat([round_trips_details, trades])

            # Calculate net premium at exit
            net_premium = round((spread.position * spread.premium).sum(), 1)

            # Calculate net premium on entry
            entry_net_premium = (trades.position * trades.entry_price).sum()

            # Calculate pnl for the trade
            trade_pnl = round(net_premium - entry_net_premium, 1)

            # Calculate cumulative pnl
            cum_pnl += trade_pnl
            cum_pnl = round(cum_pnl, 1)

            # Print trade details
            print(
                f"Trade No: {trade_num} | Exit Type: {exit_type} | Date: {i} | Premium: {net_premium} | PnL: {trade_pnl} | Cum PnL: {cum_pnl}")

            # Update current position to 0
            current_position = 0

            # Set exit flag to false
            exit_flag = False

# Calculate net premium based on positions
mark_to_market['net_premium'] = mark_to_market.position * \
    mark_to_market.premium

# Strategy analytics
analytics = pd.DataFrame()
analytics['change_in_pnl'] = mark_to_market.groupby(
    'Date').net_premium.sum().diff()
ana

Hi Jasmin,



From the error message, it looks like there is some issue with the "position" column in the mark_to_maket dataframe. Can you print the mark_to_market dataframe and check if the position column is filled correctly? If not, you can check if the "spread" dataframe is generated correctly. If you are still facing the issue, please share the notebook and the sample data either by uploading it to Google Drive and sharing the link here or by emailing quantra@quantinsti.com, and we can check the same.



Hope this helps!