Parameter analysis

This page shows how one might go about varying parameters in a strategy, and calculate the resulting Sharpe from each of the parameter runs.

The example is based around simple momentum and mean reversion strategies on the NY Harbor Heating Oil (HO COMDTY) Rolling Future Strategy.

Learn more: Example notebooks

Environment

This section will import relevant internal and external libraries, as well as setting up the platform environment.

Learn more: Environment setup

import sigtech.framework as sig
from sigtech.framework.analytics.performance.metrics import sharpe_ratio

import datetime as dtm
import pandas as pd
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt

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

if not sig.config.is_initialised():
    sig.config.init()
    sig.config.set(sig.config.HISTORY_SCHEDULE_BUILD_FROM_DATA, True)

Momentum: varying window length

A RollingFutureStrategy object of the NY Harbor Heating Oil contract is created:

ho_rf_usd_strat = sig.RollingFutureStrategy(
    currency='USD',
    start_date=dtm.date(2010, 1, 4),
    contract_code='HO',
    contract_sector='COMDTY',
    rolling_rule='F_0',
    monthly_roll_days='1:10'
)

Define a function to construct a momentum strategy with a custom window length:

@sig.remote
def momentum_strategy_sharpe(underlying_obj_name: str, window_length: int):
    ''' Constructs a SMA-based momentum strategy with a given window length.'''

    # Get return
    signal_ts = sig.obj.get(underlying_obj_name).history().diff(window_length)

    # Create df with column name equal to underlying object name
    signal_ts = pd.DataFrame({underlying_obj_name: signal_ts})

    # Get sign of returns
    signal_ts = np.sign(signal_ts).dropna()

    # Create signal object, can be passed around between researchers
    signal_obj = sig.signal_library.from_ts(signal_ts)

    signal_strategy = sig.SignalStrategy(
        start_date=dtm.date(2010, 1, 4),
        currency='USD',
        signal_name=signal_obj.name,
        rebalance_frequency='EOM',
        allocation_function=sig.signal_library.allocation.identity,
        leverage=1,
    )

    return sharpe_ratio(signal_strategy.history())

Use this function to construct a number of strategies and plot their Sharpe ratios:

plt.style.use('ggplot')

# Generate window lengths as powers of 2
x = [2 ** i for i in range(3, 12)]

# Generate resulting Sharpe ratios
ho_rf_usd_strat.build()
y = sig.calc_remote([momentum_strategy_sharpe(ho_rf_usd_strat.name, w) for w in x])

# Plot results and annotate chart
fig, ax = plt.subplots()
ax.scatter(x, y)
ax.set_title('Sharpe versus Momentum Window Length')
ax.set_xlabel('Momentum Window Length')
ax.set_ylabel('Strategy Sharpe')
ax.axhline(y=0., linestyle='--');

Momentum and Mean Reversion: varying window lengths

A 2D surface from varying both Momentum and Mean Reversion windows is created in the following example.

First, define a function to calculate the Sharpe for a combined Momentum and Mean Reversion strategy:

@sig.remote
def mmr_strategy_sharpe(
        underlying_obj_name: str,
        momentum_window_length: int,
        meanrev_window_length: int):
    """ Constructs a combined Momentum and Mean Reversion strategy on a given underlying. """

    # Identical lengths of windows will cancel out, ignore them
    if momentum_window_length == meanrev_window_length:
        return 0.

    # Get history of underlyer
    obj_history = sig.obj.get(underlying_obj_name).history()

    # Create momentum and mean reversion signals and combine them
    signal_momentum = obj_history.pct_change().rolling(
        window=momentum_window_length).mean()
    signal_meanrev = -obj_history.pct_change().rolling(window=meanrev_window_length).mean()
    signal_ts = signal_momentum + signal_meanrev

    # Create df with column name equal to underlying object name
    signal_ts = pd.DataFrame({underlying_obj_name: signal_ts})

    # Get sign of returns
    signal_ts = np.sign(signal_ts).dropna()

    # Create signal object, can be passed around between researchers
    signal_obj = sig.signal_library.from_ts(signal_ts)

    strategy = sig.SignalStrategy(
        start_date=dtm.date(2010, 1, 4),
        currency='USD',
        signal_name=signal_obj.name,
        rebalance_frequency='EOM',
        allocation_function=sig.signal_library.allocation.identity,
        leverage=1,
    )

    return sharpe_ratio(strategy.history())

Then use this function to construct a number of strategies and plot their Sharpe ratios on a 2D plot:

# Generate window lengths in specific ranges
mom_ws = [2 ** i for i in range(3, 12)]
mr_ws = range(2, 21, 2)

# Generate resulting Sharpe ratios in a 2D array
sharpes = sig.calc_remote(
    [
        mmr_strategy_sharpe(
            underlying_obj_name=ho_rf_usd_strat.name,
            momentum_window_length=w1,
            meanrev_window_length=w2
        )
        for w2 in mr_ws for w1 in mom_ws
    ]
)
z = np.matrix(np.array(sharpes).reshape(len(mr_ws), len(mom_ws)))

# Plot results and annotate chart
fig, ax = plt.subplots()
plot = ax.contourf(mom_ws, mr_ws, z)
ax.set_title('Sharpe versus Momentum and Mean Reversion Window Lengths')
ax.set_xlabel('Momentum Window Length')
ax.set_ylabel('Mean Reversion Window Length')
cbar = fig.colorbar(plot, ax=ax)
cbar.ax.set_ylabel('Sharpe Ratio');

Last updated

© 2023 SIG Technologies Limited