avatarDiego Degese

Free AI web copilot to create summaries, insights and extended knowledge, download it at here

11489

Abstract

the genetic algorithm</span> <span class="hljs-keyword">with</span> tqdm(total=GENERATIONS) <span class="hljs-keyword">as</span> pbar:

<span class="hljs-comment"># Create Genetic Algorithm</span>
ga_instance = pygad.GA(num_generations=GENERATIONS,
                       num_parents_mating=<span class="hljs-number">5</span>,
                       fitness_func=fitness_func,
                       sol_per_pop=SOLUTIONS,
                       num_genes=BARS,
                       gene_space={<span class="hljs-string">'low'</span>: -<span class="hljs-number">1</span>, <span class="hljs-string">'high'</span>: <span class="hljs-number">1</span>},
                       random_seed=<span class="hljs-number">42</span>,
                       on_generation=<span class="hljs-keyword">lambda</span> _: pbar.update(<span class="hljs-number">1</span>),
                       )

<span class="hljs-comment"># Run the Genetic Algorithm</span>
ga_instance.run()

<span class="hljs-comment"># Get the best solution</span> solution, _, _ = ga_instance.best_solution()</pre></div><p id="33ef"><b>Determining Trend Direction</b></p><p id="3efd">We determine the trend direction for each trading day and associate it with the option open price.</p><div id="9f16"><pre><span class="hljs-comment"># Get first bar (To get the Option Open Price)</span> df_day_open = df_base[(df_base[<span class="hljs-string">'date'</span>].dt.hour == <span class="hljs-number">9</span>) & (df_base[<span class="hljs-string">'date'</span>].dt.minute == <span class="hljs-number">30</span>)]

<span class="hljs-comment"># Get BARS bar (To get Underlying Close Price)</span> df = df_base[(df_base[<span class="hljs-string">'date'</span>].dt.hour == <span class="hljs-number">9</span>) & (df_base[<span class="hljs-string">'date'</span>].dt.minute == <span class="hljs-number">30</span> + BARS - <span class="hljs-number">1</span>)]

<span class="hljs-comment"># Add the Option Open Price</span> df = df.merge(df_day_open[[<span class="hljs-string">'expire_date'</span>,<span class="hljs-string">'strike'</span>,<span class="hljs-string">'kind'</span>,<span class="hljs-string">'open'</span>]], how=<span class="hljs-string">'left'</span>, left_on=[<span class="hljs-string">'expire_date'</span>,<span class="hljs-string">'strike'</span>,<span class="hljs-string">'kind'</span>], right_on=[<span class="hljs-string">'expire_date'</span>,<span class="hljs-string">'strike'</span>,<span class="hljs-string">'kind'</span>], suffixes=(<span class="hljs-string">''</span>,<span class="hljs-string">'_dayopen'</span>))

<span class="hljs-comment"># Keep the first open value for each strike</span> df = df.rename(columns={<span class="hljs-string">'open_dayopen'</span>: <span class="hljs-string">'option_open'</span>})

<span class="hljs-comment"># Predict Trend and add to test df</span> test[<span class="hljs-string">'date'</span>] = test[<span class="hljs-string">'date'</span>].dt.date.astype(<span class="hljs-built_in">str</span>) test = test[[<span class="hljs-string">'date'</span>]].drop_duplicates().reset_index(drop=<span class="hljs-literal">True</span>) test[<span class="hljs-string">'trend'</span>] = get_predicted_values(solution, test_x) test[<span class="hljs-string">'trend'</span>] = np.where(test[<span class="hljs-string">'trend'</span>] == <span class="hljs-number">0</span>, -<span class="hljs-number">1</span>, test[<span class="hljs-string">'trend'</span>])

<span class="hljs-comment"># Add Trend to df</span> df = df.merge(test, how=<span class="hljs-string">'left'</span>, left_on=[<span class="hljs-string">'expire_date'</span>], right_on=[<span class="hljs-string">'date'</span>], suffixes=[<span class="hljs-string">''</span>,<span class="hljs-string">'_ga'</span>])

<span class="hljs-comment"># Remove all previous merged values for trend calculation and rows with NaN values</span> df = df.loc[:,~df.columns.<span class="hljs-built_in">str</span>.endswith(<span class="hljs-string">'_ga'</span>)] df = df.dropna()</pre></div><p id="884a"><b>Selecting the Optimal Option</b></p><p id="2534">We select the closest in-the-money (ITM) options based on trend direction and calculate trading points.</p><div id="b8b4"><pre><span class="hljs-comment"># Filter all puts when trend is going down and calls when trend is going up</span> <span class="hljs-built_in">df</span> = <span class="hljs-built_in">df</span>[((df['kind'] == 'P') & (df['trend'] == -<span class="hljs-number">1</span>)) | ((df['kind'] == 'C') & (df['trend'] == <span class="hljs-number">1</span>))]

<span class="hljs-comment"># Calculate Strike distance from Underlying price</span> <span class="hljs-built_in">df</span>[<span class="hljs-string">'distance'</span>] = <span class="hljs-built_in">df</span>[<span class="hljs-string">'trend'</span>] * (<span class="hljs-built_in">df</span>[<span class="hljs-string">'close_underlying'</span>] - <span class="hljs-built_in">df</span>[<span class="hljs-string">'strike'</span>])

<span class="hljs-comment"># Remove OTM & ATM Options</span> <span class="hljs-built_in">df</span> = <span class="hljs-built_in">df</span>[<span class="hljs-built_in">df</span>[<span class="hljs-string">'distance'</span>] > 0]

<span class="hljs-comment"># Get the closest ITM options</span> idx = df.groupby([<span class="hljs-string">'expire_date'</span>])[<span class="hljs-string">'distance'</span>].transform(min) == <span class="hljs-built_in">df</span>[<span class="hljs-string">'distance'</span>] <span class="hljs-built_in">df</span> = <span class="hljs-built_in">df</span>[idx]

<span class="hljs-comment"># Remove distance column</span> <span class="hljs-built_in">df</span> = df.drop(<span class="hljs-string">'distance'</span>, axis=1)

<span class="hljs-comment">### Calculate close points ###</span>

<span class="hljs-comment"># Get trade bars</span> df_trade = df_base[((df_base['date'].dt.hour == <span class="hljs-number">9</span>) & (df_base['date'].dt.minute > <span class="hljs-number">30</span> + BARS - <span class="hljs-number">1</span>)) | (df_base[<span class="hljs-string">'date'</span>].dt.hour >= 10)]

<span class="hljs-comment"># Get Option Open and Close Points</span> <span class="hljs-built_in">df</span> = df_trade.merge(<span class="hljs-built_in">df</span>[[<span class="hljs-string">'expire_date'</span>,<span class="hljs-string">'kind'</span>,<span class="hljs-string">'strike'</span>,<span class="hljs-string">'option_open'</span>]], how=<span class="hljs-string">'right'</span>, left_on=[<span class="hljs-string">'expire_date'</span>,<span class="hljs-string">'kind'</span>,<span class="hljs-string">'strike'</span>], right_on=[<span class="hljs-string">'expire_date'</span>,<span class="hljs-string">'kind'</span>,<span class="hljs-string">'strike'</span>])

df.loc[:,<span class="hljs-string">'open_point'</span>] = np.where((df['open'] >= df['option_open'] * (<span class="hljs-number">1</span> + POO)) & ((df['open'].shift() < df['option_open'].shift() * (<span class="hljs-number">1</span> + POO)) | (<span class="hljs-built_in">df</span>[<span class="hljs-string">'expire_date'</span>] != <span class="hljs-built_in">df</span>[<span class="hljs-string">'expire_date'</span>].<span class="hljs-built_in">shift</span>())), 1, 0)

df.loc[:,<span class="hljs-string">'stop_loss'</span>] = <span class="hljs-built_in">df</span>[<span class="hljs-string">'option_open'</span>] * STOP_LOSS df.loc[:,<span class="hljs-string">'last_date'</span>] = df.groupby([<span class="hljs-string">'expire_date'</span>,<span class="hljs-string">'kind'</span>,<span class="hljs-string">'strike'</span>])[<span class="hljs-string">'date'</span>].transform(<span class="hljs-string">'last'</span>)

df.loc[:,<span class="hljs-string">'close_point'</span>] = np.where(((df['close'] <= df['stop_loss']) & ((df['close'].shift() > df['stop_loss'].shift()) | (<span class="hljs-built_in">df</span>[<span class="hljs-string">'expire_date'</span>] != <span class="hljs-built_in">df</span>[<span class="hljs-string">'expire_date'</span>].<span class="hljs-built_in">shift</span>()))) | (<span class="hljs-built_in">df</span>[<span class="hljs-string">'last_date'</span>] == <span class="hljs-built_in">df</span>[<span class="hljs-string">'date'</span>]), 1, 0)

df_tmp = <span class="hljs-built_in">df</span>[(<span class="hljs-built_in">df</span>[<span class="hljs-string">'open_point'</span>] - <span class="hljs-built_in">df</span>[<span class="hljs-string">'close_point'</span>]) == 0] <span class="hljs-built_in">df</span> = <span class="hljs-built_in">df</span>[(<span class="hljs-built_in">df</span>[<span class="hljs-string">'open_point'</span>] - <span class="hljs-built_in">df</span>[<span class="hljs-string">'close_point'</span>]) != 0] df.loc[:,<span class="hljs-string">'open_point'</span>] = np.where((df['open_point'] - df['close_point']) == (df['open_point'].shift(-<span class="hljs-number">1</span>) - df['close_point'].shift(-<span class="hljs-number">1</span>)), 0, <span class="hljs-built_in">df</span>[<span class="hljs-string">'open_point'</span>])

<span class="hljs-built_in">df</span> = pd.concat([<span class="hljs-built_in">df</span>, df_tmp]) <span class="hljs-built_in">df</span> = df.sort_values(by=[<span class="hljs-string">'date'</span>,<span class="hljs-string">'expire_date'</span>,<span class="hljs-string">'kind'</span>,<span class="hljs-string">'strike'</span>])

<span class="hljs-comment"># Get Open Price, Close Price, Open Date, and Close Date</span> <span class="hljs-built_in">df</span> = <span class="hljs-built_in">df</span>[(<span class="hljs-built_in">df</span>[<span class="hljs-string">'open_point'</span>] != 0) | (<span class="hljs-built_in">df</span>[<span class="hljs-string">'close_point'</span>] != 0)]

<span class="hljs-built_in">df</span>[<span class="hljs-string">'open_price'</span>] = np.where(<span class="hljs-built_in">df</span>[<span class="hljs-string">'open_point'</span>] == 1, <span class="hljs-built_in">df</span>[<span class="hljs-string">'open'</span>], np.NaN) <span class="hljs-built_in">df</span>[<span class="hljs-string">'close_price'</span>] = np.where(<span class="hljs-built_in">df</span>[<span class="hljs-string">'close_point'</span>] == 1, <span class="hljs-built_in">df</span>[<span class="hljs-string">'close'</span>], np.NaN) <span class="hljs-built_in">df</span>[<span class="hljs-string">'close_price'</span>] = <span class="hljs-built_in">df</span>[<span class="hljs-string">'close_price'</span>].fillna(method=<span class="hljs-string">'bfill'</span>, <span class="hljs-built_in">limit</span>=1)

<span class="hljs-built_in">df</span>[<span class="hljs-string">'close_date'</span>] = np.where(<span class="hljs-built_in">df</span>[<span class="hljs-string">'open_point'</span>] - <span class="hljs-built_in">df</span>[<span class="hljs-string">'close_point'</span>] == 0, <span class="hljs-built_in">df</span>[<span class="hljs-string">'date'</span>], <span class="hljs-built_in">df</span>[<span class="hljs-string">'date'</span>].<span class="

Options

hljs-built_in">shift</span>(-1)) <span class="hljs-built_in">df</span> = df.rename(columns={<span class="hljs-string">'date'</span>:<span class="hljs-string">'open_date'</span>})

<span class="hljs-comment"># Clean all Rows with NaN values (This is going to remove all invalid closes)</span> <span class="hljs-built_in">df</span> = df.dropna()

<span class="hljs-comment"># Clean all the unneeded columns</span> <span class="hljs-built_in">df</span> = df.drop([<span class="hljs-string">'last_date'</span>,<span class="hljs-string">'open_point'</span>,<span class="hljs-string">'close_point'</span>,<span class="hljs-string">'open'</span>,<span class="hljs-string">'close'</span>], axis=1) <span class="hljs-built_in">df</span> = df.loc[:,~df.columns.str.endswith(<span class="hljs-string">'_underlying'</span>)]

<span class="hljs-comment"># Save the trigger of the closing</span> df.loc[:,<span class="hljs-string">'trigger'</span>] = np.where(<span class="hljs-built_in">df</span>[<span class="hljs-string">'close_price'</span>] <= <span class="hljs-built_in">df</span>[<span class="hljs-string">'stop_loss'</span>], <span class="hljs-string">'SL'</span>, <span class="hljs-string">'EXPIRED'</span>)</pre></div><p id="fbf1"><b>Calculating Trading Results</b></p><p id="6aa0">We calculate trading results, including contracts, fees, gross and net results, and summarize the performance.</p><div id="2772"><pre><span class="hljs-comment"># Calculate the variables required in the result</span> df[<span class="hljs-string">'contracts'</span>] = (CASH // (<span class="hljs-number">100</span> * df[<span class="hljs-string">'open_price'</span>])).astype(<span class="hljs-built_in">int</span>) df[<span class="hljs-string">'fees'</span>] = np.where(df[<span class="hljs-string">'trigger'</span>] == <span class="hljs-string">'EXPIRED'</span>, FEES_PER_CONTRACT, <span class="hljs-number">2</span> * FEES_PER_CONTRACT) * df[<span class="hljs-string">'contracts'</span>] df[<span class="hljs-string">'gross_result'</span>] = df[<span class="hljs-string">'contracts'</span>] * <span class="hljs-number">100</span> * (df[<span class="hljs-string">'close_price'</span>] - df[<span class="hljs-string">'open_price'</span>]) df[<span class="hljs-string">'net_result'</span>] = df[<span class="hljs-string">'gross_result'</span>] - df[<span class="hljs-string">'fees'</span>]

sl = <span class="hljs-built_in">len</span>(df[df[<span class="hljs-string">"trigger"</span>] == <span class="hljs-string">"SL"</span>]) exp = <span class="hljs-built_in">len</span>(df[df[<span class="hljs-string">"trigger"</span>] != <span class="hljs-string">"SL"</span>]) total = <span class="hljs-built_in">len</span>(df)

<span class="hljs-comment"># Configuration</span> <span class="hljs-built_in">print</span>(<span class="hljs-string">f' CONFIGURATION '</span>.center(<span class="hljs-number">70</span>, <span class="hljs-string">''</span>)) <span class="hljs-built_in">print</span>(<span class="hljs-string">f' Bars: <span class="hljs-subst">{BARS}</span>'</span>) <span class="hljs-built_in">print</span>(<span class="hljs-string">f'* Stop Loss: <span class="hljs-subst">{STOP_LOSS * <span class="hljs-number">100</span>:<span class="hljs-number">.0</span>f}</span>%'</span>) <span class="hljs-built_in">print</span>(<span class="hljs-string">f'* Percentage over price: <span class="hljs-subst">{POO * <span class="hljs-number">100</span>:<span class="hljs-number">.0</span>f}</span>%'</span>)

<span class="hljs-comment"># Show the Total Result</span> <span class="hljs-built_in">print</span>(<span class="hljs-string">f' SUMMARIZED RESULT '</span>.center(<span class="hljs-number">70</span>, <span class="hljs-string">''</span>)) <span class="hljs-built_in">print</span>(<span class="hljs-string">f' Trading Days: <span class="hljs-subst">{<span class="hljs-built_in">len</span>(df[<span class="hljs-string">"expire_date"</span>].unique())}</span>'</span>) <span class="hljs-built_in">print</span>(<span class="hljs-string">f'* Operations: <span class="hljs-subst">{<span class="hljs-built_in">len</span>(df)}</span> - Stop Loss: <span class="hljs-subst">{sl}</span> (<span class="hljs-subst">{<span class="hljs-number">100</span> * sl / total:<span class="hljs-number">.2</span>f}</span>%) - Expired: <span class="hljs-subst">{exp}</span> (<span class="hljs-subst">{<span class="hljs-number">100</span> * exp / total:<span class="hljs-number">.2</span>f}</span>%)'</span>) <span class="hljs-built_in">print</span>(<span class="hljs-string">f'* Gross PnL: <span class="hljs-subst">{df[<span class="hljs-string">"gross_result"</span>].<span class="hljs-built_in">sum</span>():<span class="hljs-number">.2</span>f}</span>'</span>) <span class="hljs-built_in">print</span>(<span class="hljs-string">f'* Net PnL: <span class="hljs-subst">{df[<span class="hljs-string">"net_result"</span>].<span class="hljs-built_in">sum</span>():<span class="hljs-number">.2</span>f}</span>'</span>)

<span class="hljs-comment"># Show The Monthly Result</span> <span class="hljs-built_in">print</span>(<span class="hljs-string">f' MONTHLY DETAIL RESULT '</span>.center(<span class="hljs-number">70</span>, <span class="hljs-string">''</span>)) df_monthly = df[[<span class="hljs-string">'expire_date'</span>,<span class="hljs-string">'gross_result'</span>,<span class="hljs-string">'net_result'</span>]] df_monthly[<span class="hljs-string">'year_month'</span>] = df_monthly[<span class="hljs-string">'expire_date'</span>].<span class="hljs-built_in">str</span>[<span class="hljs-number">0</span>:<span class="hljs-number">7</span>] df_monthly = df_monthly.groupby([<span class="hljs-string">'year_month'</span>])[[<span class="hljs-string">'gross_result'</span>,<span class="hljs-string">'net_result'</span>]].<span class="hljs-built_in">sum</span>() <span class="hljs-built_in">print</span>(df_monthly)</pre></div><h1 id="6a72">Results</h1><p id="cf6c">The trading strategy utilizing the genetic algorithm to determine trend direction using the first 15 minutes of each trading day has shown promising results. Below is a summary of the results.</p><div id="2360"><pre>************************** CONFIGURATION ****************************

  • Bars: 15
  • Stop Loss: 70%
  • Percentage over price: 1% ************************* SUMMARIZED RESULT **************************
  • Trading Days: 361
  • Operations: 594 - Stop Loss: 422 (71.04%) - Expired: 172 (28.96%)
  • Gross PnL: $ 51359.00
  • Net PnL: $ 47390.00 *********************** MONTHLY DETAIL RESULT ************************ gross_result net_result year_month
    2021-06 -1006.0000 -1070.8000 2021-07 3980.0000 3779.6000 2021-08 -3689.0000 -3945.2000 2021-09 5590.0000 5467.6000 2021-10 1967.0000 1788.8000 2021-11 -4876.0000 -5076.4000 2021-12 1164.0000 1012.8000 2022-01 4946.0000 4759.4000 2022-02 2229.0000 2149.2000 2022-03 9529.0000 9422.2000 2022-04 3669.0000 3537.0000 2022-05 3238.0000 3160.6000 2022-06 -5825.0000 -6013.4000 2022-07 984.0000 865.8000 2022-08 6390.0000 6263.4000 2022-09 8015.0000 7930.4000 2022-10 3904.0000 3818.8000 2022-11 -4463.0000 -4637.6000 2022-12 1142.0000 979.4000 2023-01 6051.0000 5854.8000 2023-02 -709.0000 -909.4000 2023-03 1128.0000 945.6000 2023-04 8138.0000 7955.0000 2023-05 -4515.0000 -4870.2000 2023-06 4378.0000 4222.6000</pre></div><h1 id="6af0">Comparison & Conclusion</h1><p id="8ec4">Comparing the strategy of the first article and this one,</p><div id="ecd7" class="link-block"> <a href="https://wire.insiderfinance.io/spy-options-trading-with-a-profitable-1-minute-strategy-to-multiply-returns-and-limit-risk-3ea0c2d05ae5"> <div> <div> <h2>SPY Options Trading with a Profitable 1-Minute Strategy to Multiply Returns and Limit Risk</h2> <div><h3>undefined</h3></div> <div><p>undefined</p></div> </div> <div> <div style="background-image: url(https://miro.readmedium.com/v2/resize:fit:320/1*5OFNiqwEtRZSnxfhcnC2eg.png)"></div> </div> </div> </a> </div><p id="99ec">the genetic algorithm-based approach demonstrates significantly higher gross and net profits. The strategy’s use of genetic algorithms to determine trend direction proves effective in optimizing trading decisions and achieving greater profitability.</p><p id="e4f7">The strategy that utilizes only the first and last bars of each 15-minute period of each day for trend detection shows relatively lower profits compared to the genetic algorithm approach. This highlights the potential benefits of employing a more sophisticated algorithmic approach that considers more data points and optimizes trend detection using the genetic algorithm.</p><p id="9ae7">It’s important to note that while these results are promising, trading strategies should always be thoroughly backtested and evaluated under various market conditions before being implemented with real capital. Additionally, the comparison underscores the significance of selecting the right algorithmic approach to maximize profits and minimize losses in the dynamic world of options trading.</p><p id="ee55">If you enjoy my work, please support me on Medium by becoming a member through my <a href="https://medium.com/@diegodegese/membership">referral link</a>, and consider giving it a clap as a small gesture of motivation. Thank you!</p><p id="e383">Download the <a href="https://github.com/crapher/medium/blob/main/16.OptionsGATrend/code/spy_options_1m_dte_0_ga.py">full source code</a> and the <a href="https://github.com/crapher/medium/blob/main/16.OptionsGATrend/colab/spy_options_1m_dte_0_ga.ipynb">colab notebook</a> of this article from <a href="https://github.com/crapher/medium/blob/main/16.OptionsGATrend">here</a></p><p id="4666">Twitter / X: <a href="https://twitter.com/diegodegese">https://twitter.com/diegodegese</a> LinkedIn: <a href="https://www.linkedin.com/in/ddegese/">https://www.linkedin.com/in/ddegese</a> Github: <a href="https://github.com/crapher">https://github.com/crapher</a></p><p id="66b3"><i>Disclaimer: Investing in the stock market involves risk and may not be suitable for all investors. The information provided in this article is for educational purposes only and should not be construed as investment advice or a recommendation to buy or sell any particular security. Always do your own research and consult with a licensed financial advisor before making any investment decisions. Past performance is not indicative of future results.</i></p><h2 id="fe71">A Message from InsiderFinance</h2><figure id="2f53"><img src="https://cdn-images-1.readmedium.com/v2/resize:fit:800/0*10x5_2smmKq8oIlf.png"><figcaption></figcaption></figure><p id="dfe6">Thanks for being a part of our community! Before you go:</p><ul><li>👏 Clap for the story and follow the author 👉</li><li>📰 View more content in the <a href="https://wire.insiderfinance.io/">InsiderFinance Wire</a></li><li>📚 Take our <a href="https://learn.insiderfinance.io/p/mastering-the-flow">FREE Masterclass</a></li><li><b>📈 Discover <a href="https://insiderfinance.io/?utm_source=wire&amp;utm_medium=message">Powerful Trading Tools</a></b></li></ul></article></body>

Maximizing Profits with SPY Options Trading Using Genetic Algorithm-Based 1-Minute Trend Detection

In the fast-paced world of options trading, where every second counts, employing an algorithmic approach can be a game-changer.

Image from investopedia.com

In this article, we will explore a unique trading strategy that leverages genetic algorithms to determine trend direction in the SPY options market using a 1-minute timeframe. By optimizing trend identification and minimizing losses, we aim to achieve higher profitability.

Understanding the Genetic Algorithm Approach

The genetic algorithm approach is chosen due to its effectiveness in optimizing solutions for complex problems. By mimicking the principles of natural selection, genetic algorithms iteratively evolve potential solutions to find the most optimal one. In the context of SPY options trading, this approach proves valuable as it prioritizes minimizing losses while maximizing gains.

Exploring the Code

Let’s explore the code step by step and check how this strategy is implemented:

Importing Required Libraries

We begin by importing essential libraries such as tqdm for progress tracking, numpy for numerical computations, pandas for data manipulation, and pygad for the genetic algorithm implementation. Additionally, we suppress warnings for cleaner output.

# Import necessary libraries
from tqdm import tqdm
import numpy as np
import pandas as pd
import pygad

import warnings
warnings.filterwarnings("ignore")

Setting Configuration and Constants

We configure our environment and set constants that will govern our trading strategy. These constants include the number of bars to consider (BARS), stop-loss threshold (STOP_LOSS), and percentage over option price (POO), among others.

# Configuration
pd.set_option('display.float_format', lambda x: '%.4f' % x)

# Constants
BARS = 15        # Range: 0 - 30
STOP_LOSS = 0.7  # Range: 0 - 1 (0 -> 0% | 1 -> 100%)
POO = 0.01       # Range: 0 - 1 (0 -> 0% | 1 -> 100%)

OPTIONS_FILE='../data/spy_dte_0.csv.gz'
TRAIN_FILE='../data/spy.2008.2021.csv.gz'

FEES_PER_CONTRACT = 0.6
CASH = 1000

DATE_SPLIT = '2019-06-01'

SOLUTIONS = 30
GENERATIONS = 50

Data Processing Methods

We define helper methods to process the data, extract features and targets, and prepare the dataset for model training.

# Helper method to process data and return features and targets
def get_features_targets(df, scale_obs=True):

    feature_result = []
    dates = []

    # Remove duplicated dates
    df = df.groupby(by='date').mean().reset_index()

    # Get Features based on BARS configuration
    features = df[((df['date'].dt.hour == 9) & (df['date'].dt.minute >= 30)) &
                   (df['date'].dt.hour == 9) & (df['date'].dt.minute < 30 + BARS)]
    features = features.groupby(features['date'].dt.date)

    for dt, feature in features:

        if len(feature) != BARS:
            feature = feature.set_index('date')
            feature = feature.resample('1T').asfreq().reindex(pd.date_range(str(dt) + ' 09:30:00', str(dt) + f' 09:{30+BARS-1}:00', freq='1T'))
            feature = feature.reset_index()
            feature['close'] = feature['close'].fillna(method='ffill')
            feature['open'] = feature['open'].fillna(feature['close'])
            feature = feature.dropna()

        if len(feature) == BARS:
            feature = feature['close'].values

            if scale_obs:
                feature -= np.min(feature)
                feature /= np.max(np.abs(feature))
                feature = np.nan_to_num(feature, nan=0.0, posinf=0.0, neginf=0.0)

            feature_result.append(feature)
            dates.append(dt)

    # Get Targets Trend based on first and last value / day (0: DOWN - 1: UP)
    targets = df.set_index('date')
    targets = targets.resample('1D').agg({'open':'first', 'close':'last'})
    targets = targets.loc[dates].reset_index().sort_values(by='date')
    targets['trend'] = np.where(targets['open'] < targets['close'], 1, 0)

    return np.array(feature_result), np.array(targets['trend'].values)

# Helper method to return predicted values
def get_predicted_values(solution, features):

    pred_y = np.clip(np.dot(features, solution), 0, 1)
    pred_y = np.where(pred_y > 0.5, 1, 0)
    return pred_y

Defining the Fitness Function

We define the fitness function that quantifies how well a given solution (set of genetic parameters) performs. This function uses the F1-score as a measure of a solution’s accuracy in predicting trends.

# Define fitness function to be used by the PyGAD instance
def fitness_func(self, solution, sol_idx):

    global train_x, train_y

    pred_y = get_predicted_values(solution, train_x)
    result = f1_score(train_y, pred_y, average='binary', pos_label=1) + \
             f1_score(train_y, pred_y, average='binary', pos_label=0)

    return result

Reading and Preparing Data

We read and prepare the data from the provided files for both options and training.

# Read options and training data
df_base = pd.read_csv(OPTIONS_FILE, header=0)
df_base['date'] = pd.to_datetime(df_base['date'])

train = pd.read_csv(TRAIN_FILE, header=0)
train['date'] = pd.to_datetime(train['date'])
train = train[train['date'] <= DATE_SPLIT]

test = df_base[['date', 'open_underlying', 'close_underlying']]
test.columns = ['date', 'open', 'close']
test = test.drop_duplicates().reset_index(drop=True)

# Get Features and Targets
train_x, train_y = get_features_targets(train)
test_x, _ = get_features_targets(test)

Training the Genetic Algorithm

We utilize the genetic algorithm to find the optimal solution that best predicts trend directions.

# Train the genetic algorithm
with tqdm(total=GENERATIONS) as pbar:

    # Create Genetic Algorithm
    ga_instance = pygad.GA(num_generations=GENERATIONS,
                           num_parents_mating=5,
                           fitness_func=fitness_func,
                           sol_per_pop=SOLUTIONS,
                           num_genes=BARS,
                           gene_space={'low': -1, 'high': 1},
                           random_seed=42,
                           on_generation=lambda _: pbar.update(1),
                           )

    # Run the Genetic Algorithm
    ga_instance.run()

# Get the best solution
solution, _, _ = ga_instance.best_solution()

Determining Trend Direction

We determine the trend direction for each trading day and associate it with the option open price.

# Get first bar (To get the Option Open Price)
df_day_open = df_base[(df_base['date'].dt.hour == 9) & (df_base['date'].dt.minute == 30)]

# Get *BARS* bar (To get Underlying Close Price)
df = df_base[(df_base['date'].dt.hour == 9) & (df_base['date'].dt.minute == 30 + BARS - 1)]

# Add the Option Open Price
df = df.merge(df_day_open[['expire_date','strike','kind','open']],
              how='left',
              left_on=['expire_date','strike','kind'],
              right_on=['expire_date','strike','kind'],
              suffixes=('','_dayopen'))

# Keep the first open value for each strike
df = df.rename(columns={'open_dayopen': 'option_open'})

# Predict Trend and add to test df
test['date'] = test['date'].dt.date.astype(str)
test = test[['date']].drop_duplicates().reset_index(drop=True)
test['trend'] = get_predicted_values(solution, test_x)
test['trend'] = np.where(test['trend'] == 0, -1, test['trend']) 

# Add Trend to df
df = df.merge(test,
              how='left',
              left_on=['expire_date'],
              right_on=['date'],
              suffixes=['','_ga'])

# Remove all previous merged values for trend calculation and rows with NaN values
df = df.loc[:,~df.columns.str.endswith('_ga')]
df = df.dropna()

Selecting the Optimal Option

We select the closest in-the-money (ITM) options based on trend direction and calculate trading points.

# Filter all puts when trend is going down and calls when trend is going up
df = df[((df['kind'] == 'P') & (df['trend'] == -1)) |
        ((df['kind'] == 'C') & (df['trend'] == 1))]

# Calculate Strike distance from Underlying price
df['distance'] = df['trend'] * (df['close_underlying'] - df['strike'])

# Remove OTM & ATM Options
df = df[df['distance'] > 0]

# Get the closest ITM options
idx = df.groupby(['expire_date'])['distance'].transform(min) == df['distance']
df = df[idx]

# Remove distance column
df = df.drop('distance', axis=1)

### Calculate close points ###

# Get trade bars
df_trade = df_base[((df_base['date'].dt.hour == 9) & (df_base['date'].dt.minute > 30 + BARS - 1)) |
                    (df_base['date'].dt.hour >= 10)]

# Get Option Open and Close Points
df = df_trade.merge(df[['expire_date','kind','strike','option_open']],
                    how='right',
                    left_on=['expire_date','kind','strike'],
                    right_on=['expire_date','kind','strike'])

df.loc[:,'open_point'] = np.where((df['open'] >= df['option_open'] * (1 + POO)) &
                                  ((df['open'].shift() < df['option_open'].shift() * (1 + POO)) |
                                   (df['expire_date'] != df['expire_date'].shift())), 1, 0)

df.loc[:,'stop_loss'] = df['option_open'] * STOP_LOSS
df.loc[:,'last_date'] = df.groupby(['expire_date','kind','strike'])['date'].transform('last')

df.loc[:,'close_point'] = np.where(((df['close'] <= df['stop_loss']) &
                                    ((df['close'].shift() > df['stop_loss'].shift()) | (df['expire_date'] != df['expire_date'].shift()))) |
                                   (df['last_date'] == df['date']), 1, 0)

df_tmp = df[(df['open_point'] - df['close_point']) == 0]
df = df[(df['open_point'] - df['close_point']) != 0]
df.loc[:,'open_point'] = np.where((df['open_point'] - df['close_point']) == (df['open_point'].shift(-1) - df['close_point'].shift(-1)), 0, df['open_point'])

df = pd.concat([df, df_tmp])
df = df.sort_values(by=['date','expire_date','kind','strike'])

# Get Open Price, Close Price, Open Date, and Close Date
df = df[(df['open_point'] != 0) | (df['close_point'] != 0)]

df['open_price'] = np.where(df['open_point'] == 1, df['open'], np.NaN)
df['close_price'] = np.where(df['close_point'] == 1, df['close'], np.NaN)
df['close_price'] = df['close_price'].fillna(method='bfill', limit=1)

df['close_date'] = np.where(df['open_point'] - df['close_point'] == 0, df['date'], df['date'].shift(-1))
df = df.rename(columns={'date':'open_date'})

# Clean all Rows with NaN values (This is going to remove all invalid closes)
df = df.dropna()

# Clean all the unneeded columns
df = df.drop(['last_date','open_point','close_point','open','close'], axis=1)
df = df.loc[:,~df.columns.str.endswith('_underlying')]

# Save the trigger of the closing
df.loc[:,'trigger'] = np.where(df['close_price'] <= df['stop_loss'], 'SL', 'EXPIRED')

Calculating Trading Results

We calculate trading results, including contracts, fees, gross and net results, and summarize the performance.

# Calculate the variables required in the result
df['contracts'] = (CASH // (100 * df['open_price'])).astype(int)
df['fees'] = np.where(df['trigger'] == 'EXPIRED', FEES_PER_CONTRACT, 2 * FEES_PER_CONTRACT) * df['contracts']
df['gross_result'] = df['contracts'] * 100 * (df['close_price'] - df['open_price'])
df['net_result'] = df['gross_result'] - df['fees']

sl = len(df[df["trigger"] == "SL"])
exp = len(df[df["trigger"] != "SL"])
total = len(df)

# Configuration
print(f' CONFIGURATION '.center(70, '*'))
print(f'* Bars: {BARS}')
print(f'* Stop Loss: {STOP_LOSS * 100:.0f}%')
print(f'* Percentage over price: {POO * 100:.0f}%')

# Show the Total Result
print(f' SUMMARIZED RESULT '.center(70, '*'))
print(f'* Trading Days: {len(df["expire_date"].unique())}')
print(f'* Operations: {len(df)} - Stop Loss: {sl} ({100 * sl / total:.2f}%) - Expired: {exp} ({100 * exp / total:.2f}%)')
print(f'* Gross PnL: $ {df["gross_result"].sum():.2f}')
print(f'* Net PnL: $ {df["net_result"].sum():.2f}')

# Show The Monthly Result
print(f' MONTHLY DETAIL RESULT '.center(70, '*'))
df_monthly = df[['expire_date','gross_result','net_result']]
df_monthly['year_month'] = df_monthly['expire_date'].str[0:7]
df_monthly = df_monthly.groupby(['year_month'])[['gross_result','net_result']].sum()
print(df_monthly)

Results

The trading strategy utilizing the genetic algorithm to determine trend direction using the first 15 minutes of each trading day has shown promising results. Below is a summary of the results.

*************************** CONFIGURATION ****************************
* Bars: 15
* Stop Loss: 70%
* Percentage over price: 1%
************************* SUMMARIZED RESULT **************************
* Trading Days: 361
* Operations: 594 - Stop Loss: 422 (71.04%) - Expired: 172 (28.96%)
* Gross PnL: $ 51359.00
* Net PnL: $ 47390.00
*********************** MONTHLY DETAIL RESULT ************************
            gross_result  net_result
year_month                          
2021-06       -1006.0000  -1070.8000
2021-07        3980.0000   3779.6000
2021-08       -3689.0000  -3945.2000
2021-09        5590.0000   5467.6000
2021-10        1967.0000   1788.8000
2021-11       -4876.0000  -5076.4000
2021-12        1164.0000   1012.8000
2022-01        4946.0000   4759.4000
2022-02        2229.0000   2149.2000
2022-03        9529.0000   9422.2000
2022-04        3669.0000   3537.0000
2022-05        3238.0000   3160.6000
2022-06       -5825.0000  -6013.4000
2022-07         984.0000    865.8000
2022-08        6390.0000   6263.4000
2022-09        8015.0000   7930.4000
2022-10        3904.0000   3818.8000
2022-11       -4463.0000  -4637.6000
2022-12        1142.0000    979.4000
2023-01        6051.0000   5854.8000
2023-02        -709.0000   -909.4000
2023-03        1128.0000    945.6000
2023-04        8138.0000   7955.0000
2023-05       -4515.0000  -4870.2000
2023-06        4378.0000   4222.6000

Comparison & Conclusion

Comparing the strategy of the first article and this one,

the genetic algorithm-based approach demonstrates significantly higher gross and net profits. The strategy’s use of genetic algorithms to determine trend direction proves effective in optimizing trading decisions and achieving greater profitability.

The strategy that utilizes only the first and last bars of each 15-minute period of each day for trend detection shows relatively lower profits compared to the genetic algorithm approach. This highlights the potential benefits of employing a more sophisticated algorithmic approach that considers more data points and optimizes trend detection using the genetic algorithm.

It’s important to note that while these results are promising, trading strategies should always be thoroughly backtested and evaluated under various market conditions before being implemented with real capital. Additionally, the comparison underscores the significance of selecting the right algorithmic approach to maximize profits and minimize losses in the dynamic world of options trading.

If you enjoy my work, please support me on Medium by becoming a member through my referral link, and consider giving it a clap as a small gesture of motivation. Thank you!

Download the full source code and the colab notebook of this article from here

Twitter / X: https://twitter.com/diegodegese LinkedIn: https://www.linkedin.com/in/ddegese Github: https://github.com/crapher

Disclaimer: Investing in the stock market involves risk and may not be suitable for all investors. The information provided in this article is for educational purposes only and should not be construed as investment advice or a recommendation to buy or sell any particular security. Always do your own research and consult with a licensed financial advisor before making any investment decisions. Past performance is not indicative of future results.

A Message from InsiderFinance

Thanks for being a part of our community! Before you go:

Stock Market
Options
Day Trading
Trending
Options Trading
Recommended from ReadMedium