Overview

(PCAR, IP) perform consistently out-of-sample at daily frequency with Sharpe > 2, Alpha > 1.4 for a maximally invested portfolio. Graph of its performance upon one testing period with z_threshold = 1.5, roll = 2 9, initial_cash = 1000:

Hypothesis/Approach

Implementation


def cointegration_filter(cur_stock,graphs=False):
    cur_pair=get_time_period(cur_stock['stock_list'],True, freq=cur_stock['freq'], num_data_points=cur_stock['num_p'],shift=int(cur_stock['shift_parameter'])+1)
    cur_stock = cur_stock['stock_list']

    model = m.OLS((cur_pair[cur_stock[0]] ), m.add_constant( (cur_pair[cur_stock[1]]))).fit()
    results = coint(np.log(cur_pair[cur_stock[0]] ),np.log(cur_pair[cur_stock[1]]))[1]
    if graphs:
        return model.resid.rolling(28).mean().vbt.plot(title=tuple(cur_stock[0:2]).__str__()). to_html(include_plotlyjs='cdn',include_mathjax=False,auto_play =False,full_html=False)
    arr = np.array([False])
    if results < .05 + .001 +0:
        arr = np.array([True])
    return arr

def port_sim(pa,graphs=False):
    stock_one,stock_two = pa['stock_list']
    init_money = pa['init_money']
    args=pa['parameters_']
    outer=pa['outer']
    freq = pa['freq']
    shift_parameter = pa['shift_parameter'] - 500
    va_ = get_time_period(pa['stock_list']+['SPY'], custom_data=True, num_data_points=pa['num_p'] ,shift=shift_parameter + 501,freq= pa['freq'])
    va = va_[pa['stock_list']]
    rolling = args[1]
    r= va[stock_one].rolling(rolling).\text{Cov} (va[stock_two])
    Var = va[stock_two].rolling(rolling).Var()
    beta = r/Var
    pa =  pa['stock_list']

    va_diff =  va[stock_one] - beta * va[pa[1]]

    list_r = va_diff.rolling(rolling)
    z_score = (va_diff - list_r.mean()) / list_r.std()

    z_score = z_score.dropna()

    z_threshold = args[0]
    exits = ((z_score > z_threshold) & (z_score.shift(1) < z_threshold)) + 0
    entries = ((z_score < -1 * z_threshold) & (z_score.shift(1) > -1 * z_threshold))+0
    init_cash = init_money
    a_1 = (init_cash/va[pa[0]])
    a_2 = ((a_1*(va[pa[0]]/va[pa[1]])*(1/beta)) + 0)

    entries_exits = a_1*(entries - exits) + 0
    entries_exits_ = -a_2*(entries - exits) + 0
    entries_exits = pd.concat([entries_exits,entries_exits_],axis=1)

    entries_exits.columns = pa[0:2]
    entries_exits = entries_exits.replace(0,np.nan).ffill().fillna(0,)
    data_close = va[pa].loc[entries_exits.index]
    sold_ideal = (1/data_close * init_cash).astype(int)
    quantities_practical = (entries_exits/entries_exits.abs())*(sold_ideal * ((sold_ideal < entries_exits.abs()) + 0) + ( entries_exits.abs()* (entries_exits.abs() <= sold_ideal) + 0))
    entries_exits = quantities_practical
    benchmark = va_['SPY'].pct_change()
    benchmark_ = (1+benchmark).cumprod()

    p = v.Portfolio.from_orders(close=data_close, log= True, size=entries_exits,size_type='TargetAmount',
    init_cash=init_cash, freq=freq,cash_sharing=True)
    metrics = [x for x in p.stats().index if 'Trade' not in x ]
    metrics.remove('Benchmark Return [%]')
    metrics.remove('Win Rate [%]')
    metrics_values = pd.concat([p.stats()[metrics].to_frame(),p.returns_stats(benchmark_rets=benchmark).iloc[-7:].to_frame()]).squeeze()

    r = p.value().pct_change().rolling(rolling).\text{Cov} (benchmark)/benchmark.rolling(rolling).\text{Cov} ()
    beta_ = p.value().pct_change().\text{Cov} (benchmark)/benchmark.\text{Cov} (benchmark)
    if bol:
    if not graphs:
        return  [metrics_values[x] for x in metrics_values.keys() if any([y in x for y in outer])] + [len(p.positions.records_readable)]
There was attempted optimization with optuna
def objective(trial:optuna.trial.Trial):
        pairs = pd.read_parquet('Cointegration7periods017 50.parquet')
        pairs = pairs.drop(columns=pairs.columns[-4])
        pairs =     pairs[pairs.sum(axis=1) > 2].index
        parameters =    list(range(1750, 2500 - 200*2, 250 ))

        name = 'cidt.parquet'
        z_threshold = trial.suggest_float('z_threshold', 1.1, 1.8)
        roll = trial.suggest_int('roll', 20,40,step=10)
        results = runner_multiple(pd.DataFrame(index=[tuple(x) for x in pairs if 'SPY' not in x]), parameters,port_sim,init_money=1000,inner=None,num_p=  500,outer=['Total Return' , 'Sharpe', 'Alpha', 'Num'],freq='d', parameters_=[z_threshold,roll])

        results = results[[x for x in list(results.columns) if 'Alpha' in x]].mean(axis=1).mean()
        return results
    
Condition: Maximize the average Alpha of the pairs. The defined best strat which results from the validation: \(\color{#D7A84B}(\text{Coint},a_1,a_2,r=25,r'={} 2 5,z_{\text{cutoff}} =1. 5 9)\). Using this strategy with \(\color{#D7A84B}(1750,2150,2158,2515) \), the condition for a successful test \(\color{#D7A84B}SR>1.7, TR > 0, Alpha > 1\) shows the results. There are 5 pairs which satisfy whose graphs are shownLink

Conclusion