Algorithmic Options Trading, Part 1

Despite the many interesting features of options, private traders rarely take advantage of them (of course I’m talking here of serious options, not binary options). Maybe options are unpopular due to their reputation of being complex. Or due to their lack of support by most trading software tools. Or due to the price tags of the few tools that support them and of the historical data that you need for algorithmic trading. Whatever – we recently did several programming contracts for options trading systems, and I was surprised that even simple systems seemed to produce relatively consistent profit. Especially selling options appears more lucrative than trading ‘conventional’ instruments. This article is the first one of a mini-series about earning money with algorithmic options trading.  

Options 101

Options are explained on many websites and in many trading books, so here’s just a quick overview. An option is a contract that gives its owner the right to buy (call option) or sell (put option) a financial asset (the underlying) at a fixed price (the strike price) at or before a fixed date (the expiry date). If you sell short (write) an option, you’re taking the other side of the trade. So you can enter a position in 4 different ways: buy a call, buy a put, sell short a call, sell short a put. And this with all possible combinations of strike prices and expiry dates.

The premium is the price that you pay or collect for buying or selling an option. It is far less than the price of the underlying stock. Major option markets are usually liquid, so you can anytime buy, write, or sell an option with any reasonable strike price and expiry date. If the current underlying price (the spot price) of a call option lies above the strike price, the option is in the money; otherwise it’s out of the money. The opposite is true for put options. In-the-money is good for the buyer and bad for the seller. Options in the money can be exercised and are then exchanged for the underlying at the strike price. The difference of spot and strike is the buyer’s profit and the seller’s loss.

Out-of-the-money options can not be exercised, at least not at a profit. But they are not worthless, since they have still a chance to walk into the money before expiration. The value of an option depends on that chance, and can be calculated for certain option types from spot price, strike, expiry, riskless yield rate, dividend rate, and underlying volatility with the famous Black-Scholes formula. This value is the basis of the option premium. The real premium might deviate slightly due to supply, demand, and attempts to foretell the underlying’s price trend.

That’s all basic info needed for trading options. Anything else, such as the ‘greeks’, the ‘implied volatility’, or other options paraphernalia are derived from it. By the way, it’s interesting to compare the performances of strategies from trading books. While the forex or stock trading systems described in those books are mostly garbage and lose already in a simple backtest, it is not so with option systems. They often win in backtests. Are options trading book authors more intelligent than other trading book authors? Maybe, but we’ll see soon that there is an alternative explanation.

Why trading options at all?

Even though they are more complex and more difficult to trade, and even though you need a Nobel prize winning formula to calculate a value that otherwise would simply be a difference of entry and exit price, options offer many wonderful advantages over other financial instruments:

  • High leverage. With $100 you can buy only a few shares, but options of several hundred shares.
  • Controlled risk. A short position in a stock can wipe your account; positions in options can be clever combined to limit the risk in any desired way. And unlike a stop loss it’s a real risk limit.
  • Additional dimensions. Stock profits just depend on rising or falling prices. Option profits can be achieved with rising volatility, falling volatility, prices moving in a range, out of a range, or almost any other imaginable price behavior.
  • Fire and forget. Options expire, so you don’t need an algorithm for closing them (unless you want to sell or exercise them on special conditions). And you pay no exit commission for an expired option.
  • Seller advantage. Due to the premium, options can still produce a profit to their seller even if the underlying moves in the wrong direction.

Hacker ethics requires that you not just claim something, but prove it. For getting familiar with options, let’s put the last claim, the seller advantage, to the test:

void run() 
	BarPeriod = 1440;
	if(is(INITRUN)) dataLoad(1,"SPY_Options.t8",9);

	Multiplier = 100;
	int Type = ifelse(random() > 0,CALL,PUT);
	static int LastExpiry = 0;
	if(ContractType && LastExpiry != ContractExpiry) {
		LastExpiry = ContractExpiry;

This is a very simple option trading system. It randomly writes call or put options and keeps the positions open until they expire. Due to the put/call randomness it is trend agnostic. Before looking into code details, just run it in [Test] mode a couple times (you’ll need Zorro version 1.53 or above). You’ll notice that the result is different any time, but it is more often positive than negative, even though commission is subtracted from the profit. A typical outcome:

You can see that most trades win, but when they lose, they lose big. Now reverse the strategy and buy the options instead of selling them: Replace enterShort() by enterLong(). Run it again a couple times (the script needs about 3 seconds for a backtest). You will now see that the result is more often negative – in fact almost any time.

It seems that options, at least the tested SPY contracts, indeed favor the seller. This is somewhat similar to the positive expectancy of long positions in stocks, ETFs, or index futures, but the options seller advantage is stronger and independent of the market direction. It might explain a large part of the positive results of option systems in trading books. Why are there then option buyers at all? Options are often purchased not for profit, but as an insurance against unfavorable price trends of the underlying. And why is the seller advantage not arbitraged away by the market sharks? Maybe because there’s yet not much algorithmic trading with options, and because there are anyway more whales than sharks in the financial markets.

Functions for options 

We can see that options trading and backtesting requires a couple more functions than just trading the underlying. Without options, the same random trading system would be reduced to this short script:

void run() 
	BarPeriod = 1440;

	if(random() > 0)

Options require (at least) three additional functions:

  • dataLoad(1,”SPY_Options.t8″,9) loads historical options data from the file “SPY_Options.t8” into a data set. Options data includes not only the ask and bid prices, but also the strike price, the expiration date, the type – put or call, American or European – of any option, and some rarely used additional data such as the open interest. Unlike historical price data, options data is usually expensive. You can purchase it from vendors such as iVolatility. But there’s an alternative way to get it for free, which I’ll describe below. 
  • contractUpdate(“SPY”,1,CALL|PUT) loads the current option chain. In backtest mode it’s loaded from the above data set, in live trading mode it’s loaded from the broker. The option chain is a list of all available option contracts of the selected underlying, with all available strike prices and all expiration dates. If you open it manually in the IB trading platform, it looks like this:

The center column lists different strike prices and expiry dates, the right and left parts are the ask and bid prices and order book sizes for their assigned call (left) and put options (right). The prices are per share; an option contract always covers a certain number of shares, normally 100. So you can see in the list above that you’ll collect $15 premium when you write a SPY call option expiring next week (Feb 03, 2017) with a $230 strike price. If SPY won’t rise above $230 until that date, the $15 are your profit. If it rised to $230 and 10 cents and the option is exercised (happens automatically when it expires in the money), you still keep $5. But if it suddenly soared to $300 (maybe Trump announced new walls all around the US, all paid by himself), you have to bear a $6985 loss.

The image displays 54 contracts, but this is only a small part of the option chain, since there are many more expiry dates and strike prices available. The SPY option chain can contain up to 10,000 different options. They all are downloaded to the PC with the above contractUpdate function, which can thus take a couple seconds to complete.

  • contract(Type,30,priceClose()) selects a particular option from the previously downloaded option chain. The type (PUT or CALL), the days until expiration (30), and the strike (priceClose() is the current price of the underlying) are enough information to select the best fitting option. Note that for getting correct strike prices in the backtest, we downloaded the underlying price data with the UNADJUSTED flag. Strike prices are always unadjusted. 

Once a contract is selected, the next enterLong() or enterShort() buys or sells the option at market. The if() clause checks that the contract is available and its expiry date is different to the previous one (for ensuring that only different contracts are traded). Entry, stop, or profit limits would work as usual, they now only apply to the option value, the premium, instead of the underlying price. The backtest assumes that when an option is exercised or expires in the money, the underlying is immediately sold, and the profit is booked into the buyer’s account and deducted from the seller’s account. If the option expires out of the money, the position just vanishes. So we don’t care about exiting positions in this strategy. Apart from those differences, trading options works just as trading any other financial instrument.

Backtesting option strategies

Here’s an easy way to get rich. Open an IB account and run a software that records the options chains and contract prices in one-minute intervals. That’s what some data vendors did in the last 5 years, and now they are dear selling their data treasures. Although you can easily pay several thousand dollars for a few year’s option chains of major stocks, I am not sure who really owns the copyright of this data – the vendor, the broker, the exchange, or the market participants? This might be a legal grey area. Anyway, you need historical data for developing options strategies, otherwise you could not backtest them.

Here’s a method to get it for free and without any legal issues:

#include <contract.c>

string Code = "SPY"; 
string FileName = "History\\SPY_Options.t8";
var StrikeMax[3] = { 5,25,100 }; // strike ranges
var StrikeStep[3] = { 1,5,10 }; // strike step widths
int DaysMax = 180;  // max contract life in days
var BidAskSpread = 2.5; // bid/ask spread in percent
var Dividend = 2.0; // annual dividend in percent
int Type = 0; // or EUROPEAN, or FUTURE 


int N = 0;

void run() 
	BarPeriod = 1440;
	StartDate = 2011;
	EndDate = 2017;
	LookBack = 21;
	if(is(INITRUN)) {
		int MaxContractsPerDay = 2*(1+2*(StrikeMax[0]/StrikeStep[0] + StrikeMax[1]/StrikeStep[1] + StrikeMax[2]/StrikeStep[2])) * (1+DaysMax/7);
		int TotalContracts = (1+EndDate-StartDate)*252*MaxContractsPerDay;
		printf("\nAllocating %d Contracts",TotalContracts);
		c = (CONTRACT*)dataNew(1,TotalContracts,9);
		N = 0;

	vars Close = series(priceClose());
	var HistVolOV = VolatilityOV(20);
	var Unl = Close[0];
	var Interest = yield();

	if(!is(LOOKBACK)) {
		var Strike;
		int i, Days = 1;
		while((dow()+Days)%7 != FRIDAY) Days++;
		for(; Days <= DaysMax; Days += 7)
		for(Strike = max(10,round(Unl-StrikeMax[2],StrikeStep[2])); Strike <= Unl+StrikeMax[2]; Strike)
		for(i = 0; i < 2; i++)
			c->time = wdate();
			c->fUnl = Unl;
			c->fStrike = Strike;
			c->Type = Type | ifelse(i,PUT,CALL);
			c->Expiry = ymd(c->time+Days);
			c->fBid = contractVal(c,Unl,HistVolOV,0.01*Dividend,0.01*Interest);
			if(c->fBid < 0.01) continue; // probably no liquidity
			c->fAsk = (1.0+BidAskSpread/100)*c->fBid;

			if(abs(Unl-Strike) < StrikeMax[0]) Strike += StrikeStep[0];
			else if(abs(Unl-Strike) < StrikeMax[1]) Strike += StrikeStep[1];
			else Strike += StrikeStep[2];
			N++; c++;
	if(is(EXITRUN)) {
		printf("\nSaving %d Records",N);
		dataSort(1);	// reverse the entry order

This script is a bit longer than the usual Zorro scripts that I post here, so I won’t explain it in detail. It generates artificial option chains for any day from 2011-2017, and stores them in a historical data file. The option prices are calculated from the underlying price, the volatility, the current risk free interest rate, and the dividend rate of the underlying. It uses three ranges of strike prices, and expiry dates at any Friday of the next 180 days. You need R installed for running it, and also the RQuantlib package for calculating option values. All functions are described in the Zorro manual. The yield() function returns the current yield rate of US treasury bills, and contractVal() calculates the premium by solving a differential equation with all option parameters. The source code of both functions can be found in the contract.c include file. 

Due to the slow differential equation solver and the huge number of options, the script needs several hours to complete. Here’s a comparison of the generated data with real SPY options data:

The blue line are the artificial option prices, the black line are the real prices purchased from an options data vendor, both for 3-weeks SPY contracts with 10 points spot-strike distance. You can see that the prices match quite well. There are some tiny differences that might be partially random, partially caused by anomalies in supply and demand. For strategies that exploit those anomalies you’ll need real historical data. For other option strategies that exploit price or volatility changes of the underlying, the artificial data will most likely do. See, reading this article up to the end already saved you a couple thousand dollars.


  • Options and option combinations can be used to create artificial financial instruments with very interesting properties.
  • Option strategies, especially options selling, are more likely to be profitable than other strategies.
  • Algorithmic option strategies are a bit, but not much more complex than strategies with other financial instruments.

I’ve included all scripts in the 2017 script repository, and also a historical data set with the yield rates (otherwise you needed the Quandl bridge or Zorro S for downloading them). You’ll need Zorro 1.53 or above, which is currently available under the “Beta” link of the Zorro download page. The error message from the free Zorro version about the not supported Quandl bridge can be ignored, due to the included yield rates the script will run nevertheless.

In the next article we’ll look more closely into option values and into methods to combine options for limiting risk or trading arbitrary price ranges. Those combinations with funny names like “Iron Condor” or “Butterfly” are often referred to as option strategies, but they are not – they are just artificial financial instruments. How you trade them is up to the real strategy. Some simple, but consistently profitable option strategies will be the topic of the third article of this mini-series.

26 thoughts on “Algorithmic Options Trading, Part 1”

  1. Very interesting article! I have one option automatic trading system created by Zorro developers (great job by the way) and it’s quite interesting to see, that my strategy generates similar results as your strategy “random”. I am looking forward for the next articles of this mini-series.
    I would like to ask, do you have any idea if your book will be translated into English anytime soon? Would love to read the book.

  2. Thanks – yes, an English book version is planned, I just must find some time for reviewing the raw translation. Andrés: you can enter your email in the subscribe field on the right.

  3. Hi jcl,
    Nice article, I would like to ask you what are good books or where I can learn to trade with options. Thanks

  4. Am I right, rhat those artificial and real prices relate to a kind of a “synthetic” option made as a rolled-over series of real options with nearest expiration date and dynamically changed strike (depending on the underlying price)?

  5. Investopedia and Tastytrade have some tutorials and videos about options. -It’s no rolled over series, but an option chain with different strikes and expiry dates, just as in real life. Otherwise the backtest would not be realistic.

  6. When you’re comparing the artificial prices with the real prices, are you using ATM strike? The whole point, for me, of backtesting an option trading strategy vs. real option data is that at the wings the implied vols will be much much higher than those generated artificially.

  7. Hi jcl,

    Thanks for publishing this interesting article. May I know when the other two articles of this mini-series will be published?

    Many thanks!


  8. What a nice article! The results of the random trading system look similar to CBOE S&P 500 PutWrite Index and it makes sense.

  9. Hi jcl,

    I like this blog’s articles very much. I am currently trading 1 year expiry call options of specific stocks.

    My biggest problem with “seller advantage” that it contradicts to “controlled risk” statement.
    “Something that often confuses investors is whether or not being short a call and long a put are the same. Intuitively, this might make some sense, since calls and puts are almost opposite contracts, but being short a call and long a put are not the same. When you are long a put, you have to pay the premium and the worst case will result in a loss of only the premium. However, when you are short a call, you collect the option premium, but you are exposed to a large amount of risk”

    So when you write (naked) calls your risk is unlimited. The short expiry time period(30 days) is saves you in most cases, but this is a self-delusion. This method is very similar to scam trading bots, where 99,5% of the time bots are winning little(e. g. call premium) amount of money, however when you loose, you risk large amount of your money.

    Long call or put traders risk is limited and they choose out-of-the-money options to multiply their winnings and parallel they reduce their winning chance.

    I would be interested in LEAPS (1+ year expiry long/put options) backtest.


  10. Just do it. Download Zorro 1.54 from the user forum, and backtest a system with LEAPS. For this you need to increase the “DaysMax” variable in the options data generating script above to 1 year (365) or 2 years (2*365) for including long-term contracts. The script will then need a bit more time for the data generation.

  11. Hi jcl,

    Since trading options is a new Zorro feature, I’m wondering if the Broker API part of the manual ( has been sufficiently updated to account for handling options.

    I’m asking because I’m trying to write a DLL plugin for TradeKing (soon to be renamed to Ally Invest). They have stocks, ETFs, and options contracts. Very low barrier-to-entry broker as well ($0 required to get API access).


  12. For options, implement the basic API functions plus 5 BrokerCommand functions: GET_POSITION, GET_OPTIONS, GET_UNDERLYING, SET_SYMBOL, and SET_MULTIPLIER.

  13. Johann,

    Fantastic Article, thanks for sharing, I tried out the code and downloaded the options data via the script, it all seemed to download OK and make me a 48mb T8 file for SPY but when I run the random script I don’t get any trades. Its the first time I have ran zorro (I’m on the latest version downloaded 2-3 days ago) so really unsure what I’m doing wrong.

    Any help would be appreciated and I really look forward to the next episode in this enthralling series 😉



    here is the log output:

    Test OptionsSellRandom SPY

    Simulated account AssetsIB
    Bar period 24 hours (avg 2233 min)
    Test period 12.01.2011-01.06.2016 (1270 bars)
    Lookback period 80 bars (16 weeks)
    Simulation mode Realistic (slippage 5.0 sec)
    Spread 2.0 pips (roll 0.00/0.00)
    Commission 0.02
    Contracts per lot 1.0

    Gross win/loss 0.00$ / -0.00$ (-1p)
    Average profit 0.00$/year, 0.00$/month, 0.00$/day
    Max drawdown -0.00$ -1% (MAE -0.00$ -1%)
    Total down time 0% (TAE 0%)
    Max down time 0 minutes from Sep 2010
    Max open margin 0.00$
    Max open risk 0.00$
    Trade volume 0.00$ (0.00$/year)
    Transaction costs 0.00$ spr, 0.00$ slp, 0.00$ rol
    Capital required 0$

    Number of trades 279 (52/year, 1/week, 1/day)
    Percent winning 0.0%
    Max win/loss 0.00$ / –0.00$
    Avg trade profit 0.00$ -1.$p (+0.0p / -1.$p)
    Avg trade slippage 0.00$ 1.$p (+0.0p / -1.$p)
    Avg trade bars 23 (+0 / -23)
    Max trade bars 26 (5 weeks)
    Time in market 506%
    Max open trades 6
    Max loss streak 279 (uncorrelated 279)

    Annual return 0%
    Sharpe ratio 0.00
    Kelly criterion 0.00
    R2 coefficient 1.000
    Ulcer index 0.0%

    Confidence level AR DDMax Capital

    10% 0% 1$ 1$
    20% 0% 1$ 1$
    30% 0% 1$ 1$
    40% 0% 1$ 1$
    50% 0% 1$ 1$
    60% 0% 1$ 1$
    70% 0% 1$ 1$
    80% 0% 1$ 1$
    90% 0% 1$ 1$
    95% 0% 1$ 1$
    100% 0% 1$ 1$

    Portfolio analysis OptF ProF Win/Loss Wgt%

    SPY .000 —- 0/279 -1.$
    SPY:L .000 —- 0/0 -1.$
    SPY:S .000 —- 0/279 -1.$

    and a snippet of the log file…

    [1338: Fri 13.05.16 19:00] +0 +0 6/271 (206.21)
    [SPY::SC1272] Call 20160513 204.0 0@3.5713 not traded today!
    [SPY::SC1272] Expired 1 Call 20160513 204.0 0@207: +0.00 at 19:00:00

    [1339: Mon 16.05.16 19:00] +0 +0 5/272 (204.96)
    [1340: Tue 17.05.16 19:00] +0 +0 5/272 (206.46)
    [1341: Wed 18.05.16 19:00] +0 +0 5/272 (204.44)
    [1342: Thu 19.05.16 19:00] +0 +0 5/272 (204.06)
    [SPY::SC4278] Write 1 Call 20160624 205.0 0@3.4913 at 19:00:00

    [1343: Fri 20.05.16 19:00] +0 +0 6/272 (204.92)
    [SPY::SP1773] Put 20160520 208.0 0@4.2851 not traded today!
    [SPY::SP1773] Expired 1 Put 20160520 208.0 0@204: +0.00 at 19:00:00

    [1344: Mon 23.05.16 19:00] +0 +0 5/273 (205.51)
    [1345: Tue 24.05.16 19:00] +0 +0 5/273 (206.17)
    [1346: Wed 25.05.16 19:00] +0 +0 5/273 (208.67)
    [1347: Thu 26.05.16 19:00] +0 +0 5/273 (209.44)
    [SPY::SC4779] Write 1 Call 20160701 209.0 0@3.7358 at 19:00:00

    [1348: Fri 27.05.16 19:00] +0 +0 6/273 (209.53)
    [SPY::SP2274] Put 20160527 208.0 0@3.3622 not traded today!
    [SPY::SP2274] Expired 1 Put 20160527 208.0 0@209: +0.00 at 19:00:00

    [1349: Tue 31.05.16 19:00] +0 +0 5/274 (210.56)
    [SPY::SC2775] Cover 1 Call 20160531 207.0 0@2.2309: +0.00 at 19:00:00
    [SPY::SC3276] Cover 1 Call 20160531 205.0 0@5.1843: +0.00 at 19:00:00
    [SPY::SP3777] Cover 1 Put 20160531 206.0 0@0.8602: +0.00 at 19:00:00
    [SPY::SC4278] Cover 1 Call 20160531 205.0 0@4.9463: +0.00 at 19:00:00
    [SPY::SC4779] Cover 1 Call 20160531 209.0 0@2.8347: +0.00 at 19:00:00

    [1350: Wed 01.06.16 19:00] +0 +0 0/279 (209.12)

  14. I see that the positions are all opened with zero volume, as if you had set the number of contracts to 0. Have you used the unmodified script from the repository?

  15. I’m using the OptionsSimulate.c file straight from the Zip file.

    I installed R and the Quantlib libraries and the R bridge seemed to work fine as well.

    The top of the file

    string Code = “SPY”;
    string FileName = “History\\SPY_SimOptions.t8”;
    var StrikeMax[3] = { 5,30,80 }; // 3 strike ranges with different steps
    var StrikeStep[3] = { 1,5,10 }; // stepwidths for the 3 ranges
    int DaysMax = 180;
    var BidAskSpread = 2.5; // Bid/Ask spread in percent
    var Dividend = 0.02;
    int Type = 0; // or EUROPEAN, or FUTURE


    CONTRACT* c;
    int N = 0;

    void run()
    BarPeriod = 1440;
    StartDate = 2011;
    EndDate = 2017;
    LookBack = 21; // for volatility

    I’m sorry for the n00b questions, its really interesting tools and systems and I was wanting to try out some vertical credit spreads using this code as a basis on the SPY and perhaps some other instruments!



  16. It is not a noob question, it is in fact my fault. I just see that I’ve forgotten to set the options multiplier in the script. That did not matter with the previous Zorro version since the multiplier was 100 by default, but it must now be set because options can have very different multipliers.

    Multiplier = 100;

    I’ve corrected the script above. Thanks for notifying me!

  17. Yes that was it!

    Getting back results now, thanks so much for your help jcl.

    I’m now off to put $1mm in an account and trade this baby 😉

    Do you have any idea when you will get to work on the rest of the articles in this series?

  18. Hi,
    Looks like the code below is not working anymore
    assetHistory(Code,FROM_YAHOO|UNADJUSTED); i

    The CSV file SPY.csv get filled with this content:
    QECx05,The url you requested is incorrect. Please use the following url instead: /api/v3/datasets/:database_code/:dataset_code.

  19. Sorry, actually that file was from Quandl, and need a paid subscription.
    From Yahoo I get the error Can’t download SPY from Yahoo.
    Anyone having the same problem ?

Leave a Reply

Your email address will not be published. Required fields are marked *