├── data ├── cgold.pkl ├── sgold.pkl ├── ccopper.pkl ├── csilver.pkl ├── scopper.pkl ├── ssilver.pkl └── README.md ├── docs ├── The-intraday-directional-predictability-of-large-Australian-_2017_Economic-M.pdf ├── Directional predictability from stock market sector indices to gold A Cross-Quantilogram analysis.pdf ├── The Cross-Quantilogram Measuring quantile dependence and testing directional predictability between time series.pdf ├── Spillovers and Directional Predictability with a Cross-Quantilogram Analysis The Case of US and Chinese Agricultural Futures.pdf └── Does international oil volatility have directional predictability for stock returns Evidence from BRICS countries based on cross-quantilogram analysis.pdf ├── src └── CrossQuantilogram │ ├── qtests.py │ ├── __init__.py │ ├── utils.py │ ├── stationarybootstrap.py │ ├── crossquantilogram.py │ ├── plot.py │ └── api.py ├── LICENSE ├── setup.py ├── .gitignore └── README.md /data/cgold.pkl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangys96/Cross-Quantilogram/HEAD/data/cgold.pkl -------------------------------------------------------------------------------- /data/sgold.pkl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangys96/Cross-Quantilogram/HEAD/data/sgold.pkl -------------------------------------------------------------------------------- /data/ccopper.pkl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangys96/Cross-Quantilogram/HEAD/data/ccopper.pkl -------------------------------------------------------------------------------- /data/csilver.pkl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangys96/Cross-Quantilogram/HEAD/data/csilver.pkl -------------------------------------------------------------------------------- /data/scopper.pkl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangys96/Cross-Quantilogram/HEAD/data/scopper.pkl -------------------------------------------------------------------------------- /data/ssilver.pkl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangys96/Cross-Quantilogram/HEAD/data/ssilver.pkl -------------------------------------------------------------------------------- /docs/The-intraday-directional-predictability-of-large-Australian-_2017_Economic-M.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangys96/Cross-Quantilogram/HEAD/docs/The-intraday-directional-predictability-of-large-Australian-_2017_Economic-M.pdf -------------------------------------------------------------------------------- /docs/Directional predictability from stock market sector indices to gold A Cross-Quantilogram analysis.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangys96/Cross-Quantilogram/HEAD/docs/Directional predictability from stock market sector indices to gold A Cross-Quantilogram analysis.pdf -------------------------------------------------------------------------------- /docs/The Cross-Quantilogram Measuring quantile dependence and testing directional predictability between time series.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangys96/Cross-Quantilogram/HEAD/docs/The Cross-Quantilogram Measuring quantile dependence and testing directional predictability between time series.pdf -------------------------------------------------------------------------------- /src/CrossQuantilogram/qtests.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | def BoxPierceQ(cqlist,maxp,T): 4 | cq = np.array(cqlist[:maxp]) 5 | return T*np.cumsum(np.power(cq,2)) 6 | 7 | def LjungBoxQ(cqlist,maxp,T): 8 | cq = np.array(cqlist[:maxp]) 9 | return T*(T+2)*np.cumsum(np.power(cq,2)/np.arange(T-1,T-maxp-1,-1)) -------------------------------------------------------------------------------- /docs/Spillovers and Directional Predictability with a Cross-Quantilogram Analysis The Case of US and Chinese Agricultural Futures.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangys96/Cross-Quantilogram/HEAD/docs/Spillovers and Directional Predictability with a Cross-Quantilogram Analysis The Case of US and Chinese Agricultural Futures.pdf -------------------------------------------------------------------------------- /docs/Does international oil volatility have directional predictability for stock returns Evidence from BRICS countries based on cross-quantilogram analysis.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangys96/Cross-Quantilogram/HEAD/docs/Does international oil volatility have directional predictability for stock returns Evidence from BRICS countries based on cross-quantilogram analysis.pdf -------------------------------------------------------------------------------- /data/README.md: -------------------------------------------------------------------------------- 1 | The pickled data are daily return of gold future, silver future and copper future from US exchanges(with prefix s) and CN exchanges(with prefix c) respectively. 2 | Each pickle file contains a pandas.DataFrame with 3 columns: day, intraday and overnight(day = intraday + overnight). 3 | The return data was generated from market quotes, with labourious data cleaning. The data came from the most active contract at each day. 4 | 5 | 数据来源于wind,分别是中国市场(前缀c)和美国市场(前缀s)的黄金期货、白银期货、铜期货每日收益数据。 6 | 每个pickle文件是一个pandas.DataFrame,有3列:day, intraday和overnight,分别代表整日收益,交易时段收益和隔夜收益(day = intraday + overnight)。 7 | 收益数据由每天最活跃合约(主力合约)编制而成。 8 | -------------------------------------------------------------------------------- /src/CrossQuantilogram/__init__.py: -------------------------------------------------------------------------------- 1 | from .stationarybootstrap import Bootstrap 2 | from .crossquantilogram import CrossQuantilogram 3 | from .qtests import BoxPierceQ,LjungBoxQ 4 | from .utils import DescriptiveStatistics 5 | from .api import CQBS,CQBS_alphas,CQBS_years 6 | from .plot import bar_example,heatmap_example,rolling_example 7 | 8 | __doc__ = """The `Cross-Quantilogram`(CQ) is a correlation statistics that measures the quantile dependence between two time series. It can test the hypothesis that one time series has no directional predictability to another. Stationary bootstrap method helps establish the asymptotic distribution for CQ statistics and other corresponding test statistics.""" -------------------------------------------------------------------------------- /src/CrossQuantilogram/utils.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import statsmodels.tsa.stattools as ts 3 | import pickle 4 | 5 | def DescriptiveStatistics(data,maxlag): 6 | adf = ts.adfuller(data,maxlag) 7 | return {"mean":data.mean(), 8 | "median":data.median(), 9 | "min":data.min(), 10 | "max":data.max(), 11 | "std":data.std(), 12 | "skew":data.skew(), 13 | "kurt":data.kurt(), 14 | "adfs":adf[0], 15 | "adfpv":adf[1]} 16 | 17 | def save(data,path): 18 | with open(path, 'wb+') as f: 19 | pickle.dump(data, f) 20 | 21 | def load(data,path): 22 | with open(path, 'rb+') as f: 23 | return pickle.load(f) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 wangys96 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | setup( 3 | name = "CrossQuantilogram", 4 | author = "Yisong WANG", 5 | author_email = "richardwang96@qq.com", 6 | url = "https://github.com/wangys96/Cross-Quantilogram", 7 | description = "Python3 implementation of Cross-Quantilogram statistics and analysis", 8 | version = "1.0.0", 9 | license = "MIT", 10 | 11 | packages = find_packages("src"), 12 | package_dir = {'':'src'}, 13 | 14 | python_requires = '>=3', 15 | install_requires = [ 16 | 'numpy>=1.16.0', 17 | "pandas>=0.23.0", 18 | "statsmodels>=0.9.0", 19 | "matplotlib>=3.0.2" 20 | ], 21 | 22 | classifiers = [ 23 | # How mature is this project? Common values are 24 | # 3 - Alpha 25 | # 4 - Beta 26 | # 5 - Production/Stable 27 | "Development Status :: 5 - Production/Stable", 28 | 29 | # Indicate who your project is intended for 30 | "Intended Audience :: Science/Research", 31 | 32 | # Pick your license as you wish (should match "license" above) 33 | 'License :: OSI Approved :: MIT License', 34 | 35 | # Specify the Python versions you support here. In particular, ensure 36 | # that you indicate whether you support Python 2, Python 3 or both. 37 | 'Programming Language :: Python :: 3', 38 | ], 39 | ) -------------------------------------------------------------------------------- /src/CrossQuantilogram/stationarybootstrap.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import numpy as np 3 | 4 | def Bootstrap(x1,x2,lag,bslength,verbose=True): 5 | ''' 6 | Generate bootstrapped data 7 | 8 | Input 9 | ----- 10 | x1: array-like, serie-1, 11 | x2: array-like, serie-2, 12 | lag: integer, x2's lag, 13 | bslength: integer, output length, 14 | verbose: boolean, 15 | 16 | Output 17 | ------ 18 | A tuple including 2 bootstrapped series x1,x2 19 | 20 | ''' 21 | 22 | total,dtlen = 0,x1.shape[0]-lag 23 | K,L = [],[] 24 | while total=bslength:break 41 | if verbose: 42 | print("Generating samples:{}/{}(100%) ".format(total,bslength)) 43 | return np.concatenate(x1output)[:bslength],np.concatenate(x2output)[:bslength] 44 | 45 | -------------------------------------------------------------------------------- /src/CrossQuantilogram/crossquantilogram.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import numpy as np 3 | 4 | def CrossQuantilogram(x1,alpha1,x2,alpha2,k): 5 | ''' 6 | Calculate and return Cross-Quantilogram ρ. 7 | 8 | Input 9 | ----- 10 | x1:array-like, x1's quantile level. 11 | alpha1: float between (0,1), quantile of serie-1. 12 | x2:array-like, x2's quantile level (k lagged). 13 | alpha2: float between (0,1), quantile of serie-2. 14 | k: non-negative integer, the serie-2's lag. 15 | 16 | Output 17 | ------ 18 | The Cross-Quantilogram statistics, a float number. 19 | 20 | ''' 21 | if k==0: 22 | array_x1 = np.array(x1) 23 | array_x2 = np.array(x2) 24 | elif k > 0: 25 | array_x1 = np.array(x1[k:]) 26 | array_x2 = np.array(x2[:-k]) 27 | elif k < 0: 28 | array_x1 = np.array(x1[:k]) 29 | array_x2 = np.array(x2[-k:]) 30 | 31 | if len(array_x2.shape)>1: 32 | raise ValueError("x2 must be 1D array") 33 | 34 | if len(array_x1.shape)==1: 35 | array_x1=array_x1.reshape(array_x1.shape[0],1) 36 | 37 | q1 = np.percentile(array_x1, alpha1*100, axis=0, interpolation='higher') 38 | q2 = np.percentile(array_x2, alpha2*100, axis=0, interpolation='higher') 39 | 40 | psi1 = (array_x1 < q1) - alpha1 41 | psi2 = (array_x2 < q2) - alpha2 42 | 43 | numerator = np.sum(np.multiply(psi1, psi2.reshape(psi2.shape[0],1)),axis=0) 44 | denominator = np.multiply(np.sqrt(np.sum(np.square(psi1),axis=0)), 45 | np.sqrt(np.sum(np.square(psi2)))) 46 | if numerator.shape[0]==1: 47 | return np.divide(numerator, denominator)[0] 48 | else: 49 | return np.divide(numerator, denominator) -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | db.sqlite3 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Cross-Quantilogram 2 | =========================== 3 | 4 | This is a Python3 implementation of econometric method `Cross-Quantilogram` invented by [Han et al.(2016)](https://github.com/wangys96/Cross-Quantilogram/blob/master/docs/The%20Cross-Quantilogram%20Measuring%20quantile%20dependence%20and%20testing%20directional%20predictability%20between%20time%20series.pdf). 5 | 6 | 7 | The `Cross-Quantilogram`(CQ) is a correlation statistics that measures the quantile dependence between two time series. It can test the hypothesis that one time series has no directional predictability to another. Stationary bootstrap method helps establish the asymptotic distribution for CQ statistics and other corresponding test statistics. 8 | 9 | This repo includes: 10 | * `Cross-Quantilogram` statistics; 11 | * Stationary Bootstrap method; 12 | * Portmenteau test(Ljung-Box or Box-Pierce); 13 | * APIs for 3 Typical CQ methodologies'. 14 | * Matplotlib results plotting for 3 typical methods. 15 | 16 | 17 | # Installation 18 | 19 | For python environment, I recommand you to install [Anaconda 3](https://www.anaconda.com/) which already includes the linear algebra libs. If you want to install `numpy` manually, for Windows+Intel user I recommanded Numpy+MKL ([you can get it here](https://www.lfd.uci.edu/~gohlke/pythonlibs/)) 20 | 21 | To install `Cross-Quantilogram` : 22 | ```shell 23 | python setup.py install 24 | ``` 25 | then try: 26 | ```python 27 | import CrossQuantilogram as cq 28 | ``` 29 | 30 | 31 | # Documents 32 | 33 | The User Guide is a Jupyter Notebook where I introduced the APIs and research methodologies: 34 | 35 | [**User Guide**](https://nbviewer.jupyter.org/github/wangys96/Cross-Quantilogram/blob/master/docs/User%20Guide.ipynb) 36 | 37 | 38 | To fully understand CQ and its methodology, you can refer to these papers: 39 | 40 | * [The intraday directional predictability of large Australian stocks: A cross-quantilogram analysis](https://github.com/wangys96/Cross-Quantilogram/blob/master/docs/The-intraday-directional-predictability-of-large-Australian-_2017_Economic-M.pdf) 41 | * [Spillovers and Directional Predictability with a Cross-Quantilogram Analysis The Case of US and Chinese Agricultural Futures](https://github.com/wangys96/Cross-Quantilogram/blob/master/docs/Spillovers%20and%20Directional%20Predictability%20with%20a%20Cross-Quantilogram%20Analysis%20The%20Case%20of%20US%20and%20Chinese%20Agricultural%20Futures.pdf) 42 | * [Directional predictability from stock market sector indices to gold: A cross-quantilogram analysis](https://github.com/wangys96/Cross-Quantilogram/blob/master/docs/Directional%20predictability%20from%20stock%20market%20sector%20indices%20to%20gold%20A%20Cross-Quantilogram%20analysis.pdf) 43 | * [Does international oil volatility have directional predictability for stock returns Evidence from BRICS countries based on cross-quantilogram analysis](https://github.com/wangys96/Cross-Quantilogram/blob/master/docs/Does%20international%20oil%20volatility%20have%20directional%20predictability%20for%20stock%20returns%20Evidence%20from%20BRICS%20countries%20based%20on%20cross-quantilogram%20analysis.pdf) 44 | 45 | 46 | # Dependencies 47 | 48 | * Python 3 49 | * Numpy >= 1.16 50 | * Panadas >= 0.23 51 | * statsmodels >= 0.9 52 | * matplotlib >= 3.0.2 53 | 54 | 55 | # References 56 | 57 | Han H, Linton O, Oka T, et al. The cross-quantilogram: measuring quantile dependence and testing directional predictability between time series[J]. Journal of Econometrics, 2016, 193(1): 251-270. 58 | 59 | # Contacts 60 | 61 | If you have any question or idea, please create issues or contact me: 62 | * Email: richardwang96@qq.com 63 | * WeChat: 89516821 64 | -------------------------------------------------------------------------------- /src/CrossQuantilogram/plot.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import numpy as np 3 | import matplotlib 4 | import matplotlib.pyplot as plt 5 | import matplotlib.ticker as ticker 6 | from .api import CQBS,CQBS_alphas,CQBS_years 7 | 8 | def bar_example(data1,data2,picname="",show=True): 9 | alist=[0.1,0.5,0.9] 10 | dataset=[CQBS(data1,alist[i],data2,alist[i],20,verbose=True) for i in range(3)] 11 | 12 | fig1,ax1=plt.subplots(2,3,figsize=(12,4)) 13 | plt.subplots_adjust(wspace=0.3,hspace=0.8) 14 | xaxis=[str(x) for x in dataset[0].index] 15 | ax1[0][0].set_ylabel("Cross-Quantilogram",fontsize="x-large",labelpad=0.1) 16 | 17 | for i in range(3): 18 | ax1[0][i].set_title("α={}".format(alist[i]),fontsize="xx-large") 19 | ax1[0][i].tick_params(labelsize="large") 20 | ax1[0][i].axhline(color="black",linewidth=1) 21 | ax1[0][i].bar(xaxis,dataset[i]["cq"],width=0.2,color="black") 22 | ax1[0][i].plot(xaxis,dataset[i]["cq_upper"],color='red',linestyle="dashed") 23 | ax1[0][i].plot(xaxis,dataset[i]["cq_lower"],color='red',linestyle="dashed") 24 | ax1[0][i].xaxis.set_major_locator(ticker.MultipleLocator(2)) 25 | m = (abs(dataset[i]["cq"]).max()//0.05)*0.05+0.1 26 | ax1[0][i].set_ylim(-m,m) 27 | 28 | ax1[1][0].set_ylabel("Portmanteau",fontsize="x-large") 29 | for i in range(3): 30 | ax1[1][i].set_title("α={}".format(alist[i]),fontsize="xx-large") 31 | ax1[1][i].set_xlabel("lag",fontsize="xx-large") 32 | ax1[1][i].tick_params(labelsize="large") 33 | ax1[1][i].plot(xaxis,dataset[i]["q"],color='black') 34 | ax1[1][i].plot(xaxis,dataset[i]["qc"],color='red',linestyle="dotted") 35 | ax1[1][i].xaxis.set_major_locator(ticker.MultipleLocator(2)) 36 | fig1.align_labels() 37 | if picname: 38 | fig1.savefig(picname+".png",dpi=200,quality=95,bbox_inches="tight") 39 | if show: 40 | print(str(picname)+":") 41 | plt.show() 42 | 43 | def heatmap_example(data1,data2,picname="",show=True): 44 | alist=[0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9] 45 | dataset = CQBS_alphas(data1,alist,data2,alist,1,verbose=True) 46 | data_mat=np.array([[d["cq"] for d in row] for row in dataset]) 47 | data_txt=[["*" if ((d["cq"]>d["cq_upper"] or d["cq"]d["qc"])\ 48 | else "" for d in row] for row in dataset] 49 | 50 | fig, ax = plt.subplots(figsize=(4,5)) 51 | im = ax.imshow(data_mat, cmap="Greys") 52 | ax.set_ylabel("US lag=1",fontsize="xx-large",verticalalignment="center",labelpad=5) 53 | ax.set_xlabel("CN",fontsize="xx-large",labelpad=35) 54 | cbar = ax.figure.colorbar(im,ax=ax,fraction=0.046,pad=0.02,orientation="horizontal",) 55 | cbar.ax.tick_params(labelsize="large") 56 | cbar.ax.set_ylabel("", rotation=-90, va="bottom") 57 | ax.set_xticks(np.arange(data_mat.shape[1])) 58 | ax.set_yticks(np.arange(data_mat.shape[0])) 59 | ax.set_yticklabels(alist,horizontalalignment="right",fontsize="large") 60 | ax.set_xticklabels(alist,fontsize="large") 61 | ax.tick_params(top=True, bottom=False,labeltop=True, labelbottom=False) 62 | plt.setp(ax.get_xticklabels(), rotation=-60, ha="right",rotation_mode="anchor") 63 | ax.set_xticks(np.arange(data_mat.shape[1]+1)-.5, minor=True) 64 | ax.set_yticks(np.arange(data_mat.shape[0]+1)-.5, minor=True) 65 | ax.tick_params(which="minor", bottom=False, left=False) 66 | 67 | data = im.get_array() 68 | valfmt="{x}" 69 | threshold = im.norm(data.max())/2.0 70 | textcolors=["black", "white"] 71 | kw = dict(horizontalalignment="center",verticalalignment="center") 72 | valfmt = matplotlib.ticker.StrMethodFormatter(valfmt) 73 | 74 | for i in range(data.shape[0]): 75 | for j in range(data.shape[1]): 76 | kw.update(color=textcolors[im.norm(data[i, j]) > threshold]) 77 | text = im.axes.text(j, i, valfmt(data_txt[i][j], None),fontsize="large",**kw) 78 | 79 | fig.tight_layout() 80 | if picname: 81 | fig.savefig(picname+".png",dpi=200,quality=95,bbox_inches="tight") 82 | if show: 83 | print(picname+":") 84 | plt.show() 85 | 86 | def rolling_example(data1,data2,picname="",show=True): 87 | alist=[0.1,0.5,0.9] 88 | dataset = [CQBS_years(data1,alist[i],data2,alist[i],verbose=True) for i in range(3)] 89 | fig1,ax1=plt.subplots(2,3,figsize=(12,6)) 90 | plt.subplots_adjust(wspace=0.3,hspace=0.5) 91 | xaxis=[x for x in dataset[0].index] 92 | 93 | ax1[0][0].set_ylabel("Rolling\nCross-Quantilogram",fontsize="x-large",labelpad=0.1) 94 | for i in range(3): 95 | ax1[0][i].set_title("α={}".format(alist[i]),fontsize="xx-large") 96 | ax1[0][i].tick_params(labelsize="large") 97 | ax1[0][i].axhline(color="black",linewidth=1) 98 | ax1[0][i].plot(xaxis,dataset[i]["cq_upper"],"rd--",markersize=5,markerfacecolor="w") 99 | ax1[0][i].plot(xaxis,dataset[i]["cq_lower"],"rd--",markersize=5,markerfacecolor="w") 100 | ax1[0][i].plot(xaxis,dataset[i]["cq"],"ko-",markersize=4) 101 | ax1[0][i].xaxis.set_major_locator(ticker.MultipleLocator(2)) 102 | m = (max(abs(dataset[i]["cq"]).max(),abs(dataset[i]["cq_upper"]).max(),abs(dataset[i]["cq_lower"]).max())//0.05)*0.05+0.1 103 | ax1[0][i].set_ylim(-m,m) 104 | 105 | ax1[1][0].set_ylabel("Portmanteau",fontsize="x-large") 106 | for i in range(3): 107 | ax1[1][i].set_title("α={}".format(alist[i]),fontsize="xx-large") 108 | ax1[1][i].set_xlabel("year",fontsize="xx-large") 109 | ax1[1][i].tick_params(labelsize="large") 110 | ax1[1][i].plot(xaxis,dataset[i]["q"],color='black') 111 | ax1[1][i].plot(xaxis,dataset[i]["qc"],color='red',linestyle="dotted") 112 | ax1[1][i].xaxis.set_major_locator(ticker.MultipleLocator(2)) 113 | fig1.align_labels() 114 | 115 | if picname: 116 | fig1.savefig(picname+".png",dpi=200,quality=95,bbox_inches="tight") 117 | if show: 118 | print(picname+":") 119 | plt.show() -------------------------------------------------------------------------------- /src/CrossQuantilogram/api.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import numpy as np 3 | import time 4 | from .stationarybootstrap import Bootstrap 5 | from .crossquantilogram import CrossQuantilogram 6 | from .qtests import LjungBoxQ 7 | 8 | def CQBS(data1,a1,data2,a2,k,cqcl=0.95,testf=LjungBoxQ,testcl=0.95,n=1000,verbose=True): 9 | ''' 10 | Calculate Cross-Quantilogram statistics for a series of lags [1,k] at 1 specific quantile pair (a1,a2) from data2 to data1. 11 | Generating CQ data for bar plotting. And Return a DataFrame. Shape:[lag, item(a dict{"cq","cq_lower","cq_upper","q","qc"})] 12 | 13 | Input 14 | ----- 15 | data1: array-like, serie-1. 16 | a1: float between (0,1), quantile of serie-1. 17 | data2: array-like, serie-2 (k lagged). 18 | a2: float between (0,1), quantile of serie-2. 19 | k: non-negative integer, the serie-2's max lag. 20 | cqcl: optional float between (0,1), the level of confidence interval of CQ, 0.95 as default. 21 | testf: optional function, a function calculating the test statistics Q, qtests.LjungBoxQ as default. 22 | testcl: optional float between (0,1), the critical level of Q statistics, 0.95 as default. 23 | n: optional integer, the repeating time of bootstrap, 1000 as default. 24 | verbose: optional boolean, if it will print the procedure. 25 | 26 | Output 27 | ------ 28 | pandas.DataFrame containing k rows and 5 cols("cq","cq_upper","cq_lower","q","qc") 29 | 30 | ''' 31 | length = data1.shape[0] 32 | cqlist,qlist=[],[] 33 | for i in range(n): 34 | cqbs,proced=[0]*k,int(n*0.05) 35 | if verbose and proced>0 and i%proced==0: 36 | print("Bootstraping {}/{}".format(i,n),end='\r') 37 | for lag in range(1,k+1): 38 | bs1,bs2 = Bootstrap(data1,data2,lag,length,verbose=False) 39 | cqbs[lag-1] = CrossQuantilogram(bs1,a1,bs2,a2,lag) 40 | cqlist.append(cqbs) 41 | qlist.append(testf(cqbs,k,length)) 42 | 43 | cqsample = [CrossQuantilogram(data1,a1,data2,a2,i) for i in range(1,k+1)] 44 | 45 | cqdata = np.vstack(cqlist) 46 | qdata = np.vstack(qlist) 47 | 48 | cquc,cqlc = (1+cqcl)/2,(1-cqcl)/2 49 | cq_upper = np.quantile(cqdata,cquc,0,interpolation="higher") 50 | cq_lower = np.quantile(cqdata,cqlc,0,interpolation="lower") 51 | qc = np.quantile(qdata,testcl,0,interpolation="higher") 52 | if verbose: 53 | print("Bootstraping CQ done ") 54 | return pd.DataFrame({"cq":cqsample,"cq_upper":cq_upper,"cq_lower":cq_lower, 55 | "q":testf(cqsample,k,length),"qc":qc},index=list(range(1,k+1))) 56 | 57 | def CQBS_alphas(data1,a1list,data2,a2list,k=1,cqcl=0.95,testf=LjungBoxQ,testcl=0.95, 58 | all=False,n=1000,verbose=True): 59 | ''' 60 | Calculate Cross-Quantilogram result for a series of lags [1,k] and {a1list}×{a2list} quantiles from data2 to data1. 61 | Generating CQ data for many line plottings or heatmap plotting. 62 | Return a 2D list of DataFrame(if all=True) or a 2D list of dict(if all=False). Shape:[row(data2),col(data1)] 63 | It's slow beacuse of calling CQBS for len(a1list)×len(a2list) times. 64 | 65 | Input 66 | ----- 67 | data1: array-like, serie-1. 68 | a1list: array-like and between (0,1), quantiles of serie-1. 69 | data2: array-like, serie-2 (k lagged). 70 | a2list: array-like and between (0,1), quantiles of serie-2. 71 | k: optional non-negative integer, the serie-2's max lag, 1 as default. 72 | cqcl: optional float between (0,1), the level of confidence interval of CQ, 0.95 as default. 73 | testf: optional function, a function calculating the test statistics Q, qtests.LjungBoxQ as default. 74 | testcl: optional float between (0,1), the critical level of Q statistics, 0.95 as default. 75 | all: optional boolean, True if you want to save all [1,k] results so the 2D list will contain DataFrame; 76 | False if you want to save the last result (only for lag k) so the 2D list will contain dict, False as default. 77 | n: optional integer, the repeating time of bootstrap, 1000 as default. 78 | verbose: optional boolean, if it will print the procedure. 79 | 80 | Output 81 | ------ 82 | 2D list, rows(1D) are data2, cols(2D) are data1, items are dicts or DataFrame(return of CQ_lags) 83 | ''' 84 | mat,total,count=[],len(a1list)*len(a2list),1 85 | for a2 in a2list: 86 | mat.append([]) 87 | for a1 in a1list: 88 | if verbose: 89 | print("Processing {}/{} ".format(count,total),end='\r') 90 | count+=1 91 | res=CQBS(data1,a1,data2,a2,k,cqcl,testf,testcl,n,False) 92 | if all:mat[-1].append(res) 93 | else:mat[-1].append(dict(res.iloc[k-1])) 94 | mat=np.array(mat) 95 | if verbose: 96 | print("Bootstraping CQ done ") 97 | return mat 98 | 99 | def CQBS_years(data1,a1,data2,a2,k=1,window=1,cqcl=0.95,testf=LjungBoxQ,testcl=0.95, 100 | all=False,n=1000,verbose=True): 101 | ''' 102 | Calculate rolling Cross-Quantilogram result on lags [1,k] and (a1,a2) quantile from data2 to data1. 103 | Generating CQ data for rolling line plotting. 104 | Return 1 DataFrame(if all=False) or a list of DataFrame(if all=True) at lag∈[1,k]. 105 | It's slow beacuse of calling CQBS for #years times. 106 | 107 | Input 108 | ----- 109 | data1: array-like, serie-1. 110 | a1: float between (0,1), quantile of serie-1. 111 | data2: array-like, serie-2 (k lagged). 112 | a2: float between (0,1), quantile of serie-2. 113 | k: optional non-negative integer, the serie-2's max lag, 1 as default. 114 | window: optional positive integer, the rolling window (years), 1 as default. 115 | cqcl: optional float between (0,1), the level of confidence interval of CQ, 0.95 as default. 116 | testf: optional function, a function calculating the test statistics Q, qtests.LjungBoxQ as default. 117 | testcl: optional float between (0,1), the critical level of Q statistics, 0.95 as default. 118 | all: optional boolean, True if you want to save all [1,k] results so the list will contain k DataFrame, 119 | False if you want to save the last result (only for lag k) so it will return 1 DataFrame, False as default. 120 | n: optional integer, the repeating time of bootstrap, 1000 as default. 121 | verbose: optional boolean, if it will print the procedure. 122 | 123 | Output 124 | ------ 125 | pandas.DataFrame(return of CQ_lags) or a list of pandas.DataFrame(if all=True). 126 | ''' 127 | startyear,endyear = data1.index[0].year,data1.index[-1].year 128 | if window>1+endyear-startyear: 129 | raise ValueError("length of window must <= data range") 130 | 131 | cqres,yearlist=[],[(str(x),str(x+window-1)) for x in range(startyear,endyear-window+2)] 132 | for start,end in yearlist: 133 | if verbose: 134 | print("Processing {}/{} ".format(end,endyear),end='\r') 135 | cqres.append(CQBS(data1[start:end],a1,data2[start:end],a2,k,cqcl,testf,testcl,n,False)) 136 | 137 | res,yearindex=[],[str(x) for x in range(startyear+window-1,endyear+1)] 138 | if all: 139 | for i in [[df.iloc[x] for df in cqres] for x in range(k)]: 140 | merged = pd.concat(i,ignore_index=True) 141 | merged.index = yearindex 142 | res.append(merged) 143 | else: 144 | res=pd.concat(cqres,ignore_index=True) 145 | res.index = yearindex 146 | if verbose: 147 | print("Bootstraping CQ done ") 148 | return res 149 | 150 | --------------------------------------------------------------------------------