S&P 500 내 에너지 섹터 주요 기업을 대상으로 선정
티커명으로 명명
HES / XOM / MPC / CVX / EOG / TRGP / WMB / FANG / PSX
움직임이 주요한 기업끼리 짝을 지어 Pair Trading 보고서를 작성 예정.
변곡점을 추종하여 두 기업의 차(괴리율)을 이용한 투자기법.
각 산업별 주가추이는 다음과 같다.
산업별 주가추이에 따른 투자결정 모델의 요약그래프는 다음과 같다. ( phython 모듈 구현 )
The core concept of the pair trading is that when the deviation between the TICKERs is largest , we could be long position for the good firm ( return is higher ) , on the other hand, short position for the bad firm ( return is lower ). On the view of this concept, the pair which shows the largest deviation of return is best trading target. As a result, the HES-XOM will be the best choice for pair trading.
+@ 보고서 직접 참고
코드는 다음과 같음 ( 예시 : HES와 XOM ) _ ANACONDA 환경설정 내 데이터 분석 툴들을 사용 ( Import 내용 참고 )
import pandas as pd
import numpy as np
import matplotlib . pyplot as plt
import seaborn
import yfinance as yf
import statistics
from datetime import datetime
from dateutil . relativedelta import relativedelta
from IPython . display import display , HTML
from subprocess import getoutput
plt . style . use ( 'fivethirtyeight' )
ticker1 = 'HES'
ticker2 = 'XOM'
myTickerList = [ ticker1 , ticker2 ]
mDate = '2019-01-01' # Trading Start Date
pairsFormationDuration = 12 # 12 months prior to trading
sDate = ( datetime . strptime ( mDate , '%Y-%m- %d ' ) + relativedelta ( months =- pairsFormationDuration )). strftime ( '%Y-%m- %d ' )
# Pairs Formation Start Date is 'pairsFormationDuration' months prior to the Trading Start Date
tradingPeriodDuration = 6 # 6 months after Trading Start Date
eDate = ( datetime . strptime ( mDate , '%Y-%m- %d ' ) + relativedelta ( months = tradingPeriodDuration )). strftime ( '%Y-%m- %d ' )
# Pairs Trading End Date is 'tradingPeriodDuration' months after the Trading Start Date
print ( ' {:<26} : {} ' . format ( 'Pairs Formation Start Date' , sDate ))
print ( ' {:<26} : {} ' . format ( 'Pairs Trading Start Date' , sDate ))
print ( ' {:<26} : {} ' . format ( 'Pairs Trading End Date' , sDate ))
formationPeriodData = yf .download( myTickerList , start = sDate , end = mDate )
formationPeriodData = formationPeriodData [ 'Adj Close' ]
formationPeriodData = formationPeriodData .dropna( how = 'all' )
formationPeriodData = formationPeriodData .dropna( axis = 'columns' )
formationPeriodCumRet = pd . DataFrame ()
for ticker in formationPeriodData .keys():
dx = formationPeriodData [ ticker ] / formationPeriodData [ ticker ].iloc[ 0 ]
formationPeriodCumRet = pd . concat ([ formationPeriodCumRet , dx ], axis = 1 )
stDev = statistics . stdev ( formationPeriodCumRet [ ticker1 ] - formationPeriodCumRet [ ticker2 ])
print ( 'Standard Deviation of spread in the Formation Period' , sDate , "to" , mDate , "is:" , stDev )
plt . figure ( figsize = ( 12.5 , 4.5 ))
plt . plot ( formationPeriodCumRet [ ticker1 ], label = ticker1 , linewidth = 0.35 )
plt . plot ( formationPeriodCumRet [ ticker2 ], label = ticker2 , linewidth = 0.35 )
plt . title ( ticker1 + ' vs ' + ticker2 + ' Formation Period Cum Return' )
plt . xlabel ( sDate + " - " + mDate )
plt . ylabel ( 'Cum Return' )
plt . legend ( loc = 'upper left' )
plt . show ()
tradingPeriodData = yf .download( myTickerList , start = mDate , end = eDate )
tradingPeriodData = tradingPeriodData [ 'Adj Close' ]
tradingPeriodData = tradingPeriodData .dropna( how = 'all' )
tradingPeriodData = tradingPeriodData .dropna( axis = 'columns' )
tradingPeriodCumRet = pd . DataFrame ()
for ticker in tradingPeriodData .keys():
dx = tradingPeriodData [ ticker ] / tradingPeriodData [ ticker ].iloc[ 0 ]
tradingPeriodCumRet = pd . concat ([ tradingPeriodCumRet , dx ], axis = 1 )
plt . figure ( figsize = ( 12.5 , 4.5 ))
plt . plot ( tradingPeriodCumRet [ ticker1 ], label = ticker1 , linewidth = 0.35 )
plt . plot ( tradingPeriodCumRet [ ticker2 ], label = ticker2 , linewidth = 0.35 )
plt . title ( ticker1 + ' vs ' + ticker2 + ' Trading Period Cum Return ' )
plt . xlabel ( mDate + " - " + eDate )
plt . ylabel ( 'Cum Return' )
plt . legend ( loc = 'upper left' )
plt . show ()
CONST1 = stDev * 1.5
CONST2 = 0
def buy_sell ( data , tick1 , tick2 ):
sigPriceBuy = []
sigPriceSell = []
flag = 0
S1 = data [ tick1 ]
S2 = data [ tick2 ]
for i in range ( len ( data )):
doNone = False
if i == len ( data ) - 1 : #Last day of Trading Period
if flag == 0 :
doNone = True
elif flag == 1 : # If we had a position open with short on Ticker1 and long on Ticker2 - Thus we must close position by long Ticker1 and short Ticker2
sigPriceBuy . append ([ tick1 , S1 [ i ]])
sigPriceSell . append ([ tick2 , S2 [ i ]])
else : # If we had a position open with long on Ticker1 and short on Ticker2 - Thus we must close position by long Ticker2 and short Ticker1
sigPriceBuy . append ([ tick2 , S2 [ i ]])
sigPriceSell . append ([ tick1 , S1 [ i ]])
else : # Not at the last day of Trading Period
if flag == 0 : # We are not in a position
if ( S1 [ i ] - S2 [ i ] > CONST1 ): # Flag1 means ticker1 is much higher than ticker2 - Thus short Ticker1 and long Ticker2
sigPriceBuy . append ([ tick2 , S2 [ i ]])
sigPriceSell . append ([ tick1 , S1 [ i ]])
flag = 1
elif ( S2 [ i ] - S1 [ i ] > CONST1 ): # Flag2 means ticker2 is much higher than ticker1 - Thus short Ticker2 and long Ticker1
sigPriceBuy . append ([ tick1 , S1 [ i ]])
sigPriceSell . append ([ tick2 , S2 [ i ]])
flag = 2
else :
doNone = True
elif flag == 1 : # We are in flag1 - we have shorted Ticker1 and longed Ticker2
if ( S1 [ i ] - S2 [ i ] < CONST2 ): # If close position signal is received, must long Ticker1 and short Ticker2
sigPriceBuy . append ([ tick1 , S1 [ i ]])
sigPriceSell . append ([ tick2 , S2 [ i ]])
flag = 0
else :
doNone = True
elif flag == 2 : # We are in flag2 - we have shorted Ticker2 and longed Ticker1
if ( S2 [ i ] - S1 [ i ] < CONST2 ): # If close position signal is received, must long Ticker2 and short Ticker1
sigPriceBuy . append ([ tick2 , S2 [ i ]])
sigPriceSell . append ([ tick1 , S1 [ i ]])
flag = 0
else :
doNone = True
if doNone :
sigPriceBuy . append ([ '' , np .nan])
sigPriceSell . append ([ '' , np .nan])
return ( sigPriceBuy , sigPriceSell )
buySellResult = buy_sell ( tradingPeriodCumRet , ticker1 , ticker2 )
tradingPeriodCumRet [ 'Buy_Signal_Price' ] = [ a [ 1 ] for a in buySellResult [ 0 ]]
tradingPeriodCumRet [ 'Sell_Signal_Price' ] = [ a [ 1 ] for a in buySellResult [ 1 ]]
tradingPeriodCumRet [ 'Buy_Signal_Ticker' ] = [ a [ 0 ] for a in buySellResult [ 0 ]]
tradingPeriodCumRet [ 'Sell_Signal_Ticker' ] = [ a [ 0 ] for a in buySellResult [ 1 ]]
tradingPeriodCumRet
totalNumberOfTradingDays = len ( tradingPeriodCumRet )
totalNumberOfBuys = tradingPeriodCumRet [ 'Buy_Signal_Price' ]. count ()
print ( ' {:<28} : {:} ' . format ( 'Total Number of Trading Days' , totalNumberOfTradingDays ))
print ( ' {:<28} : {:} ' . format ( 'Total Number of Buys' , totalNumberOfBuys ))
if totalNumberOfBuys != 0 :
plt . figure ( figsize = ( 12.5 , 4.5 ))
plt . plot ( tradingPeriodCumRet [ ticker1 ], label = ticker1 , linewidth = 0.35 )
plt . plot ( tradingPeriodCumRet [ ticker2 ], label = ticker2 , linewidth = 0.35 )
plt . scatter ( tradingPeriodCumRet . index , tradingPeriodCumRet [ 'Buy_Signal_Price' ], label = 'Buy' , marker = '^' , color = 'green' )
plt . scatter ( tradingPeriodCumRet . index , tradingPeriodCumRet [ 'Sell_Signal_Price' ], label = 'Sell' , marker = 'v' , color = 'red' )
plt . title ( ticker1 + ' vs ' + ticker2 + ' Cum Return History with Buy & Sell Signals' )
plt . xlabel ( mDate + " - " + eDate )
plt . ylabel ( 'Cum Return' )
plt . legend ( frameon = False , loc = 'upper center' , ncol = 3 )
plt . show ()
else :
print ( "No buys in the trading period! Do not proceed to Step 4!" )
def buy_sell_price ( data ):
strategy = []
flag = 0
longPrice = 0
shortPrice = 0
longTicker = ''
shortTicker = ''
asset = 1
multiple = 1
for i in range ( len ( data )):
#print(data.index[i], i, asset, multiple)
if data [ 'Buy_Signal_Ticker' ][ i ] != '' : #New position
if flag == 0 : # Open New Pair
longPrice = data [ 'Buy_Signal_Price' ][ i ]
shortPrice = data [ 'Sell_Signal_Price' ][ i ]
longTicker = data [ 'Buy_Signal_Ticker' ][ i ]
shortTicker = data [ 'Sell_Signal_Ticker' ][ i ]
strategy . append ( asset )
flag = 1
else : # Close Existing Pair
longSideRet = data [ longTicker ][ i ] - longPrice
shortSideRet = shortPrice - data [ shortTicker ][ i ]
totRet = longSideRet + shortSideRet
multiple = multiple * ( 1 + totRet )
asset = multiple
strategy . append ( asset )
longPrice = 0
shortPrice = 0
longTicker = ''
shortTicker = ''
flag = 0
else : # No new position
if flag == 1 : #Update Asset Value
longSideRet = data [ longTicker ][ i ] - longPrice
shortSideRet = shortPrice - data [ shortTicker ][ i ]
totRet = longSideRet + shortSideRet
asset = ( 1 + totRet ) * multiple
strategy . append ( asset )
else : # Nothing Happens
strategy . append ( asset )
return (strategy)
pairs_strategy = buy_sell_price ( tradingPeriodCumRet )
tradingPeriodCumRet [ 'pairs_strategy' ] = pairs_strategy
tradingPeriodCumRet
mask = ~ np .isnan( tradingPeriodCumRet [ 'pairs_strategy' ]. values )
security_name = ticker1
strategy_cret = ( tradingPeriodCumRet [ 'pairs_strategy' ]. values [ mask ])
BH_cret1 = ( tradingPeriodCumRet [ ticker1 ]. values [ mask ]) / ( tradingPeriodCumRet [ ticker1 ]. values [ mask ])[ 0 ]
BH_cret2 = ( tradingPeriodCumRet [ ticker2 ]. values [ mask ]) / ( tradingPeriodCumRet [ ticker2 ]. values [ mask ])[ 0 ]
dates = tradingPeriodCumRet . index . values [ mask ]
plt . figure ( figsize = ( 12.5 , 6.5 ))
plt . plot ( dates , strategy_cret , label = 'Strategy' , linewidth = 0.75 )
plt . plot ( dates , BH_cret1 , label = 'Buy and Hold ' + ticker1 , linewidth = 0.35 )
plt . plot ( dates , BH_cret2 , label = 'Buy and Hold ' + ticker2 , linewidth = 0.35 )
plt . scatter ( tradingPeriodCumRet . index , tradingPeriodCumRet [ 'Buy_Signal_Price' ], label = 'Buy' , marker = '^' , color = 'green' )
plt . scatter ( tradingPeriodCumRet . index , tradingPeriodCumRet [ 'Sell_Signal_Price' ], label = 'Sell' , marker = 'v' , color = 'red' )
plt . title ( 'Pairs Trading Strategy Long/Short Cummulative return vs Buy&Hold Returns ' )
plt . xlabel ( mDate + " - " + eDate )
plt . ylabel ( 'Cummulative return' )
plt . legend ( frameon = False , loc = 'upper left' , ncol = 3 )
plt . show ()
mask = ~ np .isnan( tradingPeriodCumRet [ 'pairs_strategy' ]. values )
pairs_return = ( tradingPeriodCumRet [ 'pairs_strategy' ]. values [ mask ])[ - 1 ] / ( tradingPeriodCumRet [ 'pairs_strategy' ]. values [ mask ])[ 0 ] - 1
BH_return1 = ( tradingPeriodCumRet [ ticker1 ]. values [ mask ])[ - 1 ] / ( tradingPeriodCumRet [ ticker1 ]. values [ mask ])[ 0 ] - 1
BH_return2 = ( tradingPeriodCumRet [ ticker2 ]. values [ mask ])[ - 1 ] / ( tradingPeriodCumRet [ ticker2 ]. values [ mask ])[ 0 ] - 1
print ( "Pairs Trading strategy return is" , " {0:.2%} " . format ( pairs_return ), " \n and buy and hold strategy return is" , " {0:.2%} " . format ( BH_return1 ), "for" , ticker1 , " \n and buy and hold strategy return is" , " {0:.2%} " . format ( BH_return2 ), "for" , ticker2 , "from" , mDate , "to" , eDate + "." )
risk_free_rate = yf .download( '^IRX' , start = mDate , end = eDate )
#risk_free_rate = data.get_data_yahoo('^IRX', start, end)
risk_free_rate = risk_free_rate .reset_index( drop = False )
risk_free_rate = risk_free_rate [[ 'Date' , 'Adj Close' ]]
risk_free_rate [ 'Adj Close' ] = risk_free_rate [ 'Adj Close' ] / ( 250 * 100 )
risk_free_rate .columns = [ 'Date' , 'rf' ]
risk_free_rate .index = risk_free_rate [ 'Date' ]
#print(risk_free_rate)
risk_free_rate [ 'date' ] = risk_free_rate .index
tradingPeriodCumRet [ 'date' ] = tradingPeriodCumRet . index
tradingPeriodCumRetMerged = pd . merge ( tradingPeriodCumRet , risk_free_rate , how = 'left' , on = 'date' , indicator = True )
#print(tradingPeriodCumRet)
rf = ( tradingPeriodCumRetMerged [ 'rf' ]. values [ mask ])[ 1 :]
for i in range ( len ( rf )):
prev_rf = 0
if ~ np .isnan( rf [ i ]):
prev_rf = rf [ i ]
if np .isnan( rf [ i ]):
rf [ i ] = prev_rf
BH_excess_ret1 = BH_ret1 - rf
BH_excess_ret2 = BH_ret2 - rf
#print(BH_excess_ret)
mask2 = ( pairs_ret != 0 )
strategy_excess_ret = pairs_ret
strategy_excess_ret [ mask2 ] = ( pairs_ret - rf )[ mask2 ]
print ( "For" , ticker1 , " vs " , ticker2 , "from" , mDate , "to" , eDate ,\
" \n Pairs Trading strategy excess return:" , "mean is" , " {0:.2%} " . format ( strategy_excess_ret .mean()), \
"std. dev. is" , " {0:.2%} " . format ( strategy_excess_ret .std()), "daily Sharpe ratio is" , " {0:.2} " . format ( strategy_excess_ret .mean() / strategy_excess_ret .std()) + "." ,\
" \n Buy and hold strategy excess return:" , ticker1 , "mean is" , " {0:.2%} " . format ( BH_excess_ret1 .mean()), \
"std. dev. is" , " {0:.2%} " . format ( BH_excess_ret1 .std()), "daily Sharpe ratio is" , " {0:.2} " . format ( BH_excess_ret1 .mean() / BH_excess_ret1 .std()) + "." , \
" \n Buy and hold strategy excess return:" , ticker2 , "mean is" , " {0:.2%} " . format ( BH_excess_ret2 .mean()), \
"std. dev. is" , " {0:.2%} " . format ( BH_excess_ret2 .std()), "daily Sharpe ratio is" , " {0:.2} " . format ( BH_excess_ret2 .mean() / BH_excess_ret2 .std()) + "." )
print ( ' {:<20} : {} ' . format ( 'firm1' , ticker1 ))
print ( ' {:<20} : {} ' . format ( 'firm2' , ticker2 ))
print ( ' {:<20} : {} ' . format ( 'startDate' , mDate ))
print ( ' {:<20} : {} ' . format ( 'formationPeriod' , pairsFormationDuration ))
print ( ' {:<20} : {} ' . format ( 'transactionPeriod' , tradingPeriodDuration ))
print ()
print ( ' {:<20} : {:.5%} ' . format ( 'strategyMean' , strategy_excess_ret .mean()))
print ( ' {:<20} : {:.5%} ' . format ( 'strategyStdDev' , strategy_excess_ret .std()))
print ( ' {:<20} : {:.4} ' . format ( 'strategySharpeRatio' , strategy_excess_ret .mean() / strategy_excess_ret .std()))
print ()
print ( ' {:<20} : {:.5%} ' . format ( 'firm1Mean' , BH_excess_ret1 .mean()))
print ( ' {:<20} : {:.5%} ' . format ( 'firm1StdDev' , BH_excess_ret1 .std()))
print ( ' {:<20} : {:.4} ' . format ( 'firm1SharpeRatio' , BH_excess_ret1 .mean() / BH_excess_ret1 .std()))
print ()
print ( ' {:<20} : {:.5%} ' . format ( 'firm2Mean' , BH_excess_ret2 .mean()))
print ( ' {:<20} : {:.5%} ' . format ( 'firm2StdDev' , BH_excess_ret2 .std()))
print ( ' {:<20} : {:.4} ' . format ( 'firm2SharpeRatio' , BH_excess_ret2 .mean() / BH_excess_ret2 .std()))
통계치를 엑셀표로 출력
완료 보고서
Pairs Trading Project Part 2 (Group 3).pdf
0.75MB