Crude Oil Collar Hedged Manufacturing PMI Strategy#

New user? The best way to learn SigTech is to follow the steps in our user guide.

Restricted data: You will only have access to the data used in this notebook if your organisation has specifically purchased it. To check your current data access, view Data. If you would like to access more data, please contact sales@sigtech.com.

Changes will not be saved: Edits made to SigTech’s example notebooks like this will only persist for the duration of your session. When you restart the research environment, this notebook will have been restored to its original state, and any changes you have made will have been lost. To make permanent changes, copy and paste the content to a new notebook in one of your workspaces.

Introduction#

This notebook showcases how a simple Manufacturing PMI momentum strategy trading brent futures can be hedged with an options collar strategy in the SigTech Platform. The strategy will go long/short when USA PMI is greater/less than its 8 month exponentially weighted moving average.

Environment#

This section imports relevant internal and external libraries, and sets up the platform environment.

[ ]:
import uuid
import numpy as np
import pandas as pd
import seaborn as sns
import datetime as dtm

sns.set(rc={'figure.figsize': (15, 6)})

import sigtech.framework as sig

from sigtech.framework.analytics.performance.performance_report import PerformanceReport, View

start_date = dtm.date(2017, 1, 1)

env = sig.config.init(env_date=dtm.date(2021, 12, 12))

Universe#

Brent Crude Oil Futures#

First, we define our `RollingFutureStrategy <https://guide.sigtech.com/learning-pathways/cross-asset/rolling-future-strategy>`__ to handle the rolling of Brent Crude Oil (contract code CO) futures. The rolling_rule is set to F_3 to specify to roll into the third future out, 6 and 5 days before contract expiry, as defined by the front_offset in python notation. In order to match our options notional to our futures, we fix the initial cash to $100,000 and set_weight_from_initial_cash to not reinvest PNL.

[ ]:
rfs = sig.RollingFutureStrategy(
    currency="USD",
    start_date=start_date,
    contract_code='CO',
    contract_sector='COMDTY',
    rolling_rule="F_3",
    front_offset="-6,-5",
    initial_cash=100000,
    set_weight_from_initial_cash=True
)

Options Collar#

Protective collars are common option strategies employed by oil traders, they are constructed by purchasing out-the-money put options and writing out-the-money call options with the same expiry. This strategy allows traders to hedge downside risk while reducing the cost of the strategy by selling the call. The trader will have limited upside set at the strike price of the call but has partially offset the cost of the hedge.

The `DynamicOptionsStrategy <https://guide.sigtech.com/essentials/strategy-building-blocks>`__ building block allows users to easily define a basket of options to trade. A basket_creation_method is called on each rebalance date and returns a dictionary of options and units to trade. In this example, otions are struck 10% out-of-the money with three months maturities as seen in the get_option method. A fixed SpotNotional target of $100,000 is set to match the exposure taken in our futures.

[ ]:
def basket_creation_method(strategy, dt, positions):
    size_date = strategy.size_date_from_decision_dt(dt)
    return {
        strategy.group.get_option(option_type='Put', strike='ATM-10%', strike_type='Spot',
                                  start_date=size_date, maturity='3M'): 1,
        strategy.group.get_option(option_type='Call', strike='ATM+10%', strike_type='Spot',
                                  start_date=size_date, maturity='3M'): -1
    }
[ ]:
collar = sig.DynamicOptionsStrategy(
    currency='USD',
    start_date=start_date,
    group_name='CO COMDTY OTC OPTION GROUP',
    basket_creation_method=basket_creation_method,
    rolling_frequencies=['1M'],
    target_type='SpotNotionalAsProportionOfNAV',
    target_quantity=1,
    close_out_at_roll=True,
    ticker=f'COLLAR {str(uuid.uuid4())[:4]}'
)

The performance of our instruments are plotted

[ ]:
rfs.history()[start_date:].plot(label='RFS', legend=True)
collar.history().plot(label='Options Collar', legend=True);

Strategy#

USA Manufacturing PMI data is retrieved using the sig.obj.get method

[ ]:
pmi_series = sig.obj.get("ECO USA MANU PMI").data_df()

The signal to trade our rolling brent futures is defined as cross over with the 3 month trailing moving average manufacturing PMI.

[ ]:
pmi_df = pd.DataFrame(pmi_series['actual_clean'])

pmi_df.index = pmi_series.index

pmi_df['ma'] = pmi_df['actual_clean'].rolling(6).mean()

pmi_df = pmi_df.dropna()

pmi_df[rfs.name] = np.select([pmi_df['actual_clean'] > pmi_df['ma']], [1], -1)
[ ]:
pmi_df.plot(y=['actual_clean','ma'])
pmi_df.plot(y=rfs.name, figsize=(15,2))

We then go long/short our protective collar based on the direction on our rolling futures.

[ ]:
pmi_df[collar.name] = pmi_df[rfs.name]

pmi_signal_df = pmi_df[[rfs.name, collar.name]]

pmi_signal_df.head()

Portfolio#

To build our portoflio, the `SignalStrategy <https://guide.sigtech.com/essentials/strategy-building-blocks/signal-strategy>`__ building block is used. By default, the identity allocation function is used, which returns the unchanged allocations as given by the signal strategy output.

[ ]:
signal_strategy_start_date = dtm.date(2017, 1, 4)

collared_pmi_signal_obj = sig.signal_library.from_ts(pmi_signal_df)

collared_pmi_signal_strategy = sig.SignalStrategy(
    currency='USD',
    start_date=signal_strategy_start_date,
    signal_name=collared_pmi_signal_obj.name,
    set_weight_from_initial_cash=True,
    ticker=f'COLLAR HEDGED CO SS {str(uuid.uuid4())[:4]}'
)
[ ]:
collared_pmi_signal_strategy.history().plot()

We create a second unhedged version of our signal strategy that only trades the rolling futures.

[ ]:
unhedged_pmi_signal_obj = sig.signal_library.from_ts(pmi_signal_df[[rfs.name]])

unhedged_pmi_signal_strategy = sig.SignalStrategy(
    currency='USD',
    start_date=signal_strategy_start_date,
    signal_name=unhedged_pmi_signal_obj.name,
    ticker=f'UNHEDGED CO SS {str(uuid.uuid4())[:4]}'
)

Performance#

Our PerformanceReport is a customisable view that shows the statistics and relevant performance metrics for our strategies. This can be used to compare our collar hedged and unhedged strategies.

[ ]:
VIEWS = [View.SUMMARY_SINGLE, View.HISTORY, View.ROLLING_PLOTS, View.MONTHLY_STATS_HEATMAP]
CASH = sig.CashIndex.from_currency('USD').history()

report = PerformanceReport([collared_pmi_signal_strategy, unhedged_pmi_signal_strategy], CASH, views=VIEWS).report()