Notebook
In [15]:
import numpy as np
import pandas as pd
import datetime as dt
import matplotlib.pyplot as plt
from quantopian.research.experimental import history
from quantopian.research.experimental import continuous_future

"""
this function takes an argument df (of type DataFrame) and inserts a "cost of carry" column that contains
the cost of carry for a given futures contract. the rows of the DataFrame should be as follows:

- column 1: current date
- column 2: current futures contract's price
- column 3: current futures contract's maturity date
- column 4: spot price of the futures contract's underlying
"""
def calc_cost_of_carry(df):
    # iterate over the rows of df and for each row, calculate the value of cost of carry
    for row in df.itertuples():
        # the formula used for cost of carry is c = ln(current price / spot price) / (maturity date - current date)
        # where c is cost of carry
        current_date = row[0]
        current_price = row[1]
        maturity_date = row[3].expiration_date
        spot_price = row[4]
        cost_of_carry = np.log(current_price / spot_price) / (maturity_date - current_date).days
        # after calculating cost of carry, insert the value into a new column at the given row
        df.loc[row[0], "cost of carry"] = cost_of_carry

Strategy

Take advantage of the spread between a commodity's futures contracts with different delivery dates. One of these commodities is natural gas (or related fossil fuels). The price of natural gas is lower in summer months than it is in winter months. The price of futures contracts should have a direct relationship with their underlying. Therefore, ordering futures contracts in summer months and selling them in winter months should result in profit.

If you'd like to get started on your own futures research and algorithm development, check out the many tutorials available on Quantopian's site.

Objective

Identify futures contracts that have cyclical price behavior.

  • This should be done by looking at pricing history and trying to find trends and also calculating cost of carry for each contract.

Identify futures contracts that have cyclical price behavior.

Hypothesis

Natural gas has a predictable demand cylce. Using that information, the price should also have a noticeable trend that can be applied to a strategy. With respect to the calendar cycle mentioned above, a potential strategy is a calendar spread strategy that buys futures contracts with a winter expiry date in summer months and exits these positions in winter months when the relative price of these contracts is highest.

Results

A raw analysis of the price trend of natural gas oil does not confirm the hypothesis. Rather, what is most visible is the overall bearish trend. This is likely because of the unexpected decline of crude oil prices.

Now, let's move into exploring natural gas contracts.

In [2]:
natural_gas_future = continuous_future("NG", offset=0, roll="calendar", adjustment=None)
In [3]:
active_natural_gas_future = history(
    natural_gas_future,
    fields=["contract", "price", "volume"],
    frequency="daily",
    start_date="2014-01-01",
    end_date="2017-01-01"
)

Below, we plot the price of natural gas futures contracts over the past two years. Unfortunately, there is no observable pattern in the pricing of these contracts besides a general decline, which is likely related to the decline in the price of the underlying.

In [4]:
active_natural_gas_future.price.plot()
Out[4]:
<matplotlib.axes._subplots.AxesSubplot at 0x7fea4ea59090>

Hoever, volume has remained relatively consistent, with the exception of early 2014 when there was a significant spike in trading volume of these contracts.

In [5]:
active_natural_gas_future.volume.plot()
Out[5]:
<matplotlib.axes._subplots.AxesSubplot at 0x7fea4df58390>

There aren't any apparent price or volume trends. So, perhaps calculating cost of carry will show some visible trend.

$$ F(t, T) = S(t) \times e^{c(T - t)} $$

Because spot price data is not available yet, we'll use a natural gas ETF as a proxy of the spot price.

In [6]:
natural_gas_etf = symbols("FCG")
In [7]:
natural_gas_etf_price_data = get_pricing(symbols=natural_gas_etf, start_date="2014-01-01", end_date="2017-01-01", frequency="daily")

The price of the ETF follows a somewhat similar bearish trend. Still, the recovery seen in the price of the futures contracts is not as pronounced in the price of the ETF. This is something worth exploring.

In [8]:
natural_gas_etf_price_data.price.plot()
Out[8]:
<matplotlib.axes._subplots.AxesSubplot at 0x7fea4c288a50>

So, when plotting the price of the natural gas ETF against a normalized natural gas futures contract price, we can begin confirming the pattern alluded to above. The prices do indeed start moving closer to each other in 2016. Calculating the mean square error between these two values further confirms this. However, this still does not move us in the direction of confirming a signal.

In [9]:
(10 * active_natural_gas_future["price"]).plot()
natural_gas_etf_price_data["price"].plot()
Out[9]:
<matplotlib.axes._subplots.AxesSubplot at 0x7fea3e2dacd0>

The plot below shows the normalized prices of natural gas and an ETF tracking the natural gas industry. It seems that before January 2016, there was backwardation between the price of the futures contract compared to the price of the underlying.

After plotting the mean squared error of the normalized prices of natural gas and a natural gas ETF, the difference between the two prices begins deminishing after May 2015.

In [10]:
mse_price = (10 * active_natural_gas_future["price"] - natural_gas_etf_price_data["price"]) ** 2
mse_price.plot()
Out[10]:
<matplotlib.axes._subplots.AxesSubplot at 0x7fea3e98de90>
In [11]:
active_natural_gas_future['spot price'] = natural_gas_etf_price_data['price']
In [12]:
active_natural_gas_future.head()
Out[12]:
price volume contract spot price
2014-01-02 00:00:00+00:00 4.306 63268.0 Future(1061201402 [NGG14]) 91.066
2014-01-03 00:00:00+00:00 4.326 114467.0 Future(1061201402 [NGG14]) 90.640
2014-01-06 00:00:00+00:00 4.310 70564.0 Future(1061201402 [NGG14]) 89.884
2014-01-07 00:00:00+00:00 4.320 120183.0 Future(1061201402 [NGG14]) 90.830
2014-01-08 00:00:00+00:00 4.186 97779.0 Future(1061201402 [NGG14]) 91.066
In [13]:
calc_cost_of_carry(active_natural_gas_future)
In [16]:
active_natural_gas_future['cost of carry'].plot()
plt.title("Cost of Carry")
plt.ylabel("Value")
plt.xlabel("Time")
Out[16]:
<matplotlib.text.Text at 0x7fea3e6f1c50>

Using the above plot. One strategy that comes to mind would be to use the Optimize API's MaximizeAlpha ability to maximize the cost of carry plotted above multiplied by negative 1. Before doing this however, contracts with an expiration date within approximately 15 days of the current trading day would need to be exited.

In [ ]: