FX Momentum & Mean reversion#

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#

The purpose of this notebook is to showcase how to construct simple carry, momentum and mean reversion strategies with FX instruments.

Environment#

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

[ ]:
import sigtech.framework as sig

import uuid
import pandas as pd
import numpy as np
import seaborn as sns
import datetime as dtm

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

env = sig.init(env_date=dtm.date(2024, 2, 22))

Boiler plate code#

Boiler plate code which is relevant for below strategies.

[ ]:
ccy_to_fut = {
    "AUD": "AD CURNCY",
    "GBP": "BP CURNCY",
    "CAD": "CD CURNCY",
    "EUR": "EC CURNCY",
    "JPY": "JY CURNCY",
    "NOK": "NO CURNCY",
    "NZD": "NV CURNCY",
    "SEK": "SE CURNCY",
    "CHF": "SF CURNCY",
}
[ ]:
def create_sub_strategy(
    asset: str,
    start_date: pd.Timestamp,
    base_ccy: str = "USD",
) -> str:

    sub_strat = sig.RollingFutureStrategy(
        currency=base_ccy,
        start_date=start_date,
        contract_code=ccy_to_fut[asset][:2],
        contract_sector='CURNCY',
        rolling_rule='front',
        front_offset='-6,-5',
        ticker=f'{asset} {str(uuid.uuid4())[:5]}'
    )

    return sub_strat.name

FX Mean Reversion Strategy#

This section will create a mean reversion strategy in chosen currencies and expressed via a user-defined instrument type. The mean reversion signal will be positive/negative, i.e. +1 or -1, for a given date if the average percent change over a backward-looking window is negative/positive - i.e. the reverse direction of th percentage change. The long/short positions in the strategy will be a result of the positive/negative signals and the chosen rebalancing rate of the strategy, i.e. it’s only the positive/negative signal on the rebalancing date that is relevant for the actual long/short exposure.

Universe#

The strategy’s universe consists of CHF, CAD, JPY and NOK vs USD exposures and the chosen implementation of the strategy is the futures market from 2010-01-04.

[ ]:
assets = ['CHF', 'CAD', 'JPY', 'NOK']
start_date = pd.Timestamp(2010,1,4)

universe = {
    asset: create_sub_strategy(
        asset = asset,
        start_date = start_date
    ) for asset in assets
}

Strategy#

The definition of a signal in this mean reversion strategy is that the future price direction of an instrument will the reversed of the current direction of a given backward-looking window.

[ ]:
def generate_signals_fx_mean_reversion(
    assets: list[str],
    window: int = 68
):
    frames = []
    for asset in assets:
        mean_reversion_signal = - \
            np.sign(sig.obj.get(asset).history(
            ).pct_change().rolling(window=window).mean())
        frames.append(mean_reversion_signal.dropna().to_frame(asset))
    return pd.concat(frames, axis=1)

Create a dataframe holding the signals for the defined universe and signal factors.

[ ]:
signal_obj = generate_signals_fx_mean_reversion(
    assets = list(universe.values())
)
signal_obj.head()

Portfolio#

Converts a dataframe of signals into a strategy where the previously defined universe holds information on how the strategy will be expressed, e.g. via futures, forwards etc.

[ ]:
fx_mean_rev_strat = sig.SignalStrategy(
    start_date = start_date,
    currency = 'USD',
    signal_name = sig.signal_library.from_ts(signal_obj).name,
    rebalance_frequency = 'EOM',
    allocation_function = sig.signal_library.allocation.normalize_weights,
    ticker = f'FX Mean Reversion {str(uuid.uuid4())[:5]}'
)

Performance#

Shows some of the statistics and relevant performance metrics available on the platform for the strategy.

[ ]:
sig.PerformanceReport(fx_mean_rev_strat.history()).report()

FX Momentum Strategy#

This section will create a momentum strategy in chosen currencies and expressed via a user-defined instrument type. The momentum signal will be positive/negative, i.e. +1 or -1, for a given date if the average percent change over a backward-looking window is positive/negative. The long/short positions in the strategy will be a result of the positive/negative signals and the chosen rebalancing rate of the strategy, i.e. it’s only the positive/negative signal on the rebalancing date that is relevant for the actual long/short exposure.

Universe#

Defines the universe relevant for the strategy in terms of assets, start date and instrument type.

[ ]:
assets = ['EUR', 'AUD', 'GBP', 'JPY', 'CAD', 'SEK', 'NOK', 'CHF']
start_date = pd.Timestamp(2010,1,4)

universe = {
    asset: create_sub_strategy(
        asset = asset,
        start_date = start_date
    ) for asset in assets
}

Strategy#

Defines the factors and parameters which leads to the strategy entering and exiting positions.

[ ]:
def generate_signals_fx_momentum(
    assets: list[str],
    window: int = 63
):
    frames = []
    for asset in assets:
        mom_signal = np.sign(sig.obj.get(asset).history().pct_change().rolling(window).mean())
        frames.append(mom_signal.dropna().to_frame(asset))
    return pd.concat(frames, axis=1)

Create a dataframe holding the signals for the defined universe and signal factors.

[ ]:
signal_mom = generate_signals_fx_momentum(
    assets = list(universe.values())
)

Portfolio#

Converts a dataframe of signals into a strategy where the previously defined universe holds information on how the strategy will be expressed, e.g. via futures, forwards etc.

[ ]:
fx_mom_strat = sig.SignalStrategy(
    start_date = start_date,
    currency = 'USD',
    signal_name = sig.signal_library.from_ts(signal_mom).name,
    rebalance_frequency = 'EOM',
    allocation_function = sig.signal_library.allocation.normalize_weights,
    ticker = f'FX Momentum {str(uuid.uuid4())[:5]}'
)

Performance#

Shows some of the statistics and relevant performance metrics available on the platform for the strategy.

[ ]:
sig.PerformanceReport(fx_mom_strat.history()).report()

FX Basket Strategy#

Create a basket of FX strategies including mean reversion, carry and momentum fx strategies. The basket strategy will introduce a layer on top of these pre-built strategies, where the user can modify the allocation and weight to the different sub-strategies.

Universe#

Defines the universe relevant for the strategy in terms of assets, start date and instrument type.

[ ]:
fx_strategy_names = [fx_mom_strat.name, fx_mean_rev_strat.name]
fx_strategies = pd.concat({fx_strat: sig.obj.get(fx_strat).history() for fx_strat in fx_strategy_names}, axis=1).dropna()
fx_strategies.head()

Strategy#

Defines the factors and parameters which leads to the strategy entering and exiting positions.

[ ]:
def generate_signals_basket(
    allocation_type: str,
    strategies: pd.DataFrame,
    window: int = 252
):
    if allocation_type == 'volatility scaled':
        df = strategies.pct_change().rolling(window).std().dropna() ** -1

    elif allocation_type == 'equally weighted':
        df = pd.DataFrame(index = strategies.index,
                         columns = strategies.columns,
                         data = 1)
    return sig.signal_library.from_ts(df)

Create a dataframe holding the signals for the defined universe and signal factors.

[ ]:
vol_scaled_signals = generate_signals_basket(
    allocation_type = 'volatility scaled',
    strategies = fx_strategies
)
equal_weight_signals = generate_signals_basket(
    allocation_type = 'equally weighted',
    strategies = fx_strategies
)
vol_scaled_signals.history().head()

Portfolio#

Converts a dataframe of signals into a strategy where the previously defined universe holds information on how the strategy will be expressed, e.g. via futures, forwards etc.

[ ]:
vol_scaled_st = sig.SignalStrategy(
    currency = 'USD',
    start_date = fx_strategies.index[0],
    signal_name = vol_scaled_signals.name,
    rebalance_frequency = '3M',
    allocation_function = sig.signal_library.allocation.normalize_weights,
)

equally_weighted_st = sig.SignalStrategy(
    currency = 'USD',
    start_date = fx_strategies.index[0],
    signal_name = equal_weight_signals.name,
    rebalance_frequency = '3M',
    allocation_function = sig.signal_library.allocation.normalize_weights,
)

Performance#

Shows some of the statistics and relevant performance metrics available on the platform for the strategy.

[ ]:
sig.PerformanceReport(pd.concat({
    'Inverse vol scaled':  vol_scaled_st.history(),
    'Equally weighted':  equally_weighted_st.history()
}, axis=1), cash=sig.CashIndex.from_currency('USD')).report()