Skip to content
All library documents

Testing a Crude Oil and Currency Relationship for COP Trading

Code quant-trading

Summary

This notebook explores relationships between Colombian peso exchange-rate data and crude oil blends, currencies, and gold. It ranks simple ordinary least squares regressions by R-squared, compares the Vasconia crude relationship with the peso before and after 2017, and plots fitted values with one- and two-standard-deviation residual bands. It then imports a separate trading script to generate signals and assess portfolio returns.

The analysis varies holding periods and stop thresholds, plots return distributions, and uses a heatmap to inspect sensitivity. Its stated interpretation is that holding period appears more related to return than the stop setting, with a preferred holding period of 17 trading days. This is exploratory evidence, not a robust validation: the underlying dataset and strategy implementation are absent, and the notebook does not report transaction costs, out-of-sample results, or statistical uncertainty. Its regression and parameter-selection results may therefore be sensitive to the sample and prone to overfitting.

Key ideas

  • The notebook compares OLS fit between COP and several market series, including crude blends.
  • It splits the Vasconia-COP regression around 2017 and visualizes residual bands.
  • A separate strategy script is used to generate COP trading signals and portfolio returns.
  • A parameter sweep examines holding periods and stop thresholds, but does not establish out-of-sample performance.

Tags

Full text
# Oil Money COP.py


```py

# coding: utf-8

# In[1]:


import pandas as pd
import os
import matplotlib.pyplot as plt
import statsmodels.api as sm
import seaborn as sns
import numpy as np
from sklearn.model_selection import train_test_split
os.chdir('k:/')


# In[2]:

#plot two curves on same x axis, different y axis
def dual_axis_plot(xaxis,data1,data2,fst_color='r',
                    sec_color='b',fig_size=(10,5),
                   x_label='',y_label1='',y_label2='',
                   legend1='',legend2='',grid=False,title=''):
    
    fig=plt.figure(figsize=fig_size)
    ax=fig.add_subplot(111)
    

    ax.set_xlabel(x_label)
    ax.set_ylabel(y_label1, color=fst_color)
    ax.plot(xaxis, data1, color=fst_color,label=legend1)
    ax.tick_params(axis='y',labelcolor=fst_color)
    ax.yaxis.labelpad=15

    plt.legend(loc=3)
    ax2=ax.twinx()

    ax2.set_ylabel(y_label2, color=sec_color,rotation=270)
    ax2.plot(xaxis, data2, color=sec_color,label=legend2)
    ax2.tick_params(axis='y',labelcolor=sec_color)
    ax2.yaxis.labelpad=15

    fig.tight_layout()
    plt.legend(loc=4)
    plt.grid(grid)
    plt.title(title)
    plt.show()



# In[3]:

#read dataframe    
df=pd.read_csv('vas crude copaud.csv',encoding='utf-8')
df.set_index('date',inplace=True)
df.index=pd.to_datetime(df.index)


# In[4]:

#run regression on each input

D={}

for i in df.columns:
    if i!='cop':
            x=sm.add_constant(df[i])
            y=df['cop']
            m=sm.OLS(y,x).fit()
            D[i]=m.rsquared
            
D=dict(sorted(D.items(),key=lambda x:x[1],reverse=True))


# In[5]:

#create r squared bar charts

colorlist=[]

for i in D:
    if i =='wti':
        colorlist.append('#447294')
    elif i=='brent':
        colorlist.append('#8fbcdb')
    elif i=='vasconia':
        colorlist.append('#f4d6bc')
    else:
        colorlist.append('#cdc8c8')
        
ax=plt.figure(figsize=(10,5)).add_subplot(111)
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)

width=0.7

for i in D:
    plt.bar(list(D.keys()).index(i)+width,            
            D[i],width=width,label=i,
            color=colorlist[list(D.keys()).index(i)])
 
plt.title('Regressions on COP')
plt.ylabel('R Squared\n')
plt.xlabel('\nRegressors')
plt.xticks(np.arange(len(D))+width,
           [i.upper() for i in D.keys()],fontsize=8)
plt.show()


# In[6]:

#normalized value of wti,brent and vasconia
ax=plt.figure(figsize=(10,5)).add_subplot(111)
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)

(df['vasconia']/df['vasconia'].iloc[0]).plot(c='#6f6ff4',
                                             label='Vasconia',alpha=0.5)
(df['brent']/df['brent'].iloc[0]).plot(c='#e264c0',
                                   label='Brent',alpha=0.5)
(df['wti']/df['wti'].iloc[0]).plot(c='#fb6630',
                                   label='WTI',alpha=0.5)
plt.legend(loc=0)
plt.xlabel('Date')
plt.ylabel('Normalized Value by 100')
plt.title('Crude Oil Blends')
plt.show()


# In[7]:

#cop vs gold
dual_axis_plot(df.index,df['cop'],df['gold'],
               x_label='Date',y_label1='Colombian Peso',
               y_label2='Gold LBMA',
               legend1='COP',
               legend2='Gold',
               title='COP VS Gold',
               fst_color='#96CEB4',sec_color='#FFA633')

#cop vs usd
dual_axis_plot(df.index,df['cop'],df['usd'],
               x_label='Date',y_label1='Colombian Peso',
               y_label2='US Dollar',
               legend1='COP',
               legend2='USD',
               title='COP VS USD',
               fst_color='#9DE0AD',sec_color='#5C4E5F')


# In[8]:

#cop vs brl
dual_axis_plot(df.index,df['cop'],df['brl'],
               x_label='Date',y_label1='Colombian Peso',
               y_label2='Brazilian Real',
               legend1='COP',
               legend2='BRL',
               title='COP VS BRL',
               fst_color='#a4c100',sec_color='#f7db4f')

#usd vs mxn
dual_axis_plot(df.index,df['usd'],df['mxn'],
               x_label='Date',y_label1='US Dollar',
               y_label2='Mexican Peso',
               legend1='USD',
               legend2='MXN',
               title='USD VS MXN',
               fst_color='#F4A688',sec_color='#A2836E')

#cop vs mxn
dual_axis_plot(df.index,df['cop'],df['mxn'],
               x_label='Date',y_label1='Colombian Peso',
               y_label2='Mexican Peso',
               legend1='COP',
               legend2='MXN',
               title='COP VS MXN',
               fst_color='#F26B38',sec_color='#B2AD7F')


# In[9]:

#cop vs vasconia
dual_axis_plot(df.index,df['cop'],df['vasconia'],
               x_label='Date',y_label1='Colombian Peso',
               y_label2='Vasconia Crude',
               legend1='COP',
               legend2='Vasconia',
               title='COP VS Vasconia',
               fst_color='#346830',sec_color='#BBAB9B')


# In[10]:

#create before/after regression comparison
m=sm.OLS(df['cop'][:'2016'],sm.add_constant(df['vasconia'][:'2016'])).fit()
before=m.rsquared
m=sm.OLS(df['cop']['2017':],sm.add_constant(df['vasconia']['2017':])).fit()
after=m.rsquared

ax=plt.figure(figsize=(10,5)).add_subplot(111)
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
plt.bar(['Before 2017',
         'After 2017'],
        [before,after],color=['#82b74b', '#5DD39E'])
plt.ylabel('R Squared')
plt.title('Before/After Regression')
plt.show()


# In[11]:


#create 1 std, 2 std band before 2017
x_train,x_test,y_train,y_test=train_test_split(
        sm.add_constant(df['vasconia'][:'2016']),
        df['cop'][:'2016'],test_size=0.5,shuffle=False)
    
m=sm.OLS(y_test,x_test).fit()
    
forecast=m.predict(x_test)
    
ax=plt.figure(figsize=(10,5)).add_subplot(111)
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
forecast.plot(label='Fitted',c='#FEFBD8')
y_test.plot(label='Actual',c='#ffd604')
ax.fill_between(y_test.index,
                    forecast+np.std(m.resid),
                    forecast-np.std(m.resid),
                    color='#F4A688', 
                    alpha=0.6, 
                    label='1 Sigma')
    
ax.fill_between(y_test.index,
                    forecast+2*np.std(m.resid),
                    forecast-2*np.std(m.resid),
                    color='#8c7544', 
                    alpha=0.8, 
                    label='2 Sigma')
    
plt.legend(loc=0)
plt.title(f'Colombian Peso Positions\nR Squared {round(m.rsquared*100,2)}%\n')
plt.xlabel('\nDate')
plt.ylabel('COPAUD')
plt.show()


# In[12]:


#create 1 std, 2 std band after 2017
x_train,x_test,y_train,y_test=train_test_split(
        sm.add_constant(df['vasconia']['2017':]),
        df['cop']['2017':],test_size=0.5,shuffle=False)
    
m=sm.OLS(y_test,x_test).fit()
    
forecast=m.predict(x_test)
    
ax=plt.figure(figsize=(10,5)).add_subplot(111)
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
forecast.plot(label='Fitted',c='#FEFBD8')
y_test.plot(label='Actual',c='#ffd604')
ax.fill_between(y_test.index,
                    forecast+np.std(m.resid),
                    forecast-np.std(m.resid),
                    color='#F4A688', 
                    alpha=0.6, 
                    label='1 Sigma')
    
ax.fill_between(y_test.index,
                    forecast+2*np.std(m.resid),
                    forecast-2*np.std(m.resid),
                    color='#8c7544', \
                    alpha=0.8, \
                    label='2 Sigma')
    
plt.legend(loc=0)
plt.title(f'Colombian Peso Positions\nR Squared {round(m.rsquared*100,2)}%\n')
plt.xlabel('\nDate')
plt.ylabel('COPAUD')
plt.show()


# In[13]:

#shrink data size for better viz
dataset=df['2016':]
dataset.reset_index(inplace=True)


# In[14]:

#import the strategy script as this is a script for analytics and visualization
#the official trading strategy script is in the following link
# https://github.com/je-suis-tm/quant-trading/blob/master/Oil%20Money%20project/Oil%20Money%20Trading%20backtest.py
import oil_money_trading_backtest as om

#generate signals,monitor portfolio performance
#plot positions and total asset
signals=om.signal_generation(dataset,'vasconia','cop',om.oil_money,stop=0.001)
p=om.portfolio(signals,'cop')
om.plot(signals,'cop')
om.profit(p,'cop')


# In[15]:


#try different holding period and stop loss/profit point
dic={}
for holdingt in range(5,20):
    for stopp in np.arange(0.001,0.005,0.0005):
        signals=om.signal_generation(dataset,'vasconia','cop',om.oil_money,
                                     holding_threshold=holdingt,
                                     stop=round(stopp,4))
        
        p=om.portfolio(signals,'cop')
        dic[holdingt,round(stopp,4)]=p['asset'].iloc[-1]/p['asset'].iloc[0]-1
     
profile=pd.DataFrame({'params':list(dic.keys()),'return':list(dic.values())})


# In[16]:


#plotting the distribution of return
ax=plt.figure(figsize=(10,5)).add_subplot(111)
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
profile['return'].apply(lambda x:x*100).hist(histtype='bar',
                                             color='#b2660e',
                                             width=0.45,bins=20)
plt.title('Distribution of Return on COP Trading')
plt.grid(False)
plt.ylabel('Frequency')
plt.xlabel('Return (%)')
plt.show()


# In[17]:


#plotting the heatmap of return under different parameters
#try to find the optimal parameters to maximize the return

#convert the dataframe into a matrix format first
matrix=pd.DataFrame(columns=[round(i,4) for i in np.arange(0.001,0.005,0.0005)])

matrix['index']=np.arange(5,20)
matrix.set_index('index',inplace=True)

for i,j in profile['params']:
    matrix.at[i,round(j,4)]=     profile['return'][profile['params']==(i,j)].item()*100

for i in matrix.columns:
    matrix[i]=matrix[i].apply(float)


#plotting
fig=plt.figure(figsize=(10,5))
ax=fig.add_subplot(111)
sns.heatmap(matrix,cmap=plt.cm.viridis,
            xticklabels=3,yticklabels=3)
ax.collections[0].colorbar.set_label('Return(%)\n',
                                      rotation=270)
plt.xlabel('\nStop Loss/Profit (points)')
plt.ylabel('Position Holding Period (days)\n')
plt.title('Profit Heatmap\n',fontsize=10)
plt.style.use('default')

#it seems like the return doesnt depend on the stop profit/loss point
#it is correlated with the length of holding period
#the ideal one should be 17 trading days
#as for stop loss/profit point could range from 0.002 to 0.005


```

Shown in full with attribution under the source's licence. Licence: Apache-2.0

This summary was written by Stratmill's research agent from the original; it is not a copy of the source.