PCMCI: Causal Discovery in Multivariate Crypto Time Series
Jakob Runge (2018, 2020) द्वारा विकसित PCMCI बहुविविध टाइम सीरीज़ के लिए causal discovery algorithm है। यह graphical model learning के लिए PC algorithm को Momentary Conditional Independence (MCI) test के साथ जोड़ता है, जिसकी conditioning set तीन चीज़ों को एक साथ निष्प्रभावी करने के लिए बनाई जाती है: common drivers, indirect paths और source variable की autocorrelation। यही अंतिम हिस्सा ध्यान से पढ़ने योग्य है — इसी कारण momentum-heavy सीरीज़ पर naive lead-lag testing को डुबो देने वाली false-positive rate PCMCI में नहीं आती।
यह लेख first principles से two-stage construction को खोलता है, tigramite library के साथ इसे implement करता है और उस real-data study को सामने रखता है जिसकी इस method को trading input बनने से पहले ज़रूरत है।
यह पहले से मौजूद सामग्री में क्या जोड़ता है

ब्लॉग correlation का काम पहले ही कर चुका है। Crypto co-movement काफी हद तक common-factor artifact है — pairs के बीच signal correlation दिखाता है कि ten-pair book में अकेला PC1 variance का 65% सोख लेता है। High pairwise correlation tradable relationship नहीं है, इसी वजह से statistical arbitrage और pairs trading cointegration की ओर जाता है। और dependence स्वयं भी स्थिर नहीं है: DCC-GARCH dynamic correlation बताता है कि static sample correlation dependence की stationarity, constant marginal volatility और direction की symmetry मानता है, जिनमें से कोई भी कायम नहीं रहता।
इनमें से कोई भी directed graph नहीं बनाता। वे बताते हैं कि BTC, ETH और SOL साथ चलते हैं और common factor उसका अधिकांश भाग समझाता है; वे यह नहीं बताते कि BTC सीधे SOL को drive करता है या association पूरी तरह ETH के माध्यम से आती है। Direction के लिए सामान्य विकल्प Granger causality है, लेकिन standard Granger bivariate है — यह unobserved common driver को वास्तविक causation से अलग नहीं कर सकता।
एक तीसरा failure mode भी है जो यहाँ दिखने से अधिक महत्वपूर्ण है। हर crypto return series positive autocorrelation रखती है, और दो ऐसी सीरीज़ के बीच चलाया गया कोई भी dependence test autocorrelation के कारण test statistic को inflate करता है: दो स्वतंत्र autocorrelated processes finite samples में significantly related दिखेंगे। Correlation work इसे sample-size correction मानता है (signal-correlation article की effective_N discussion)। Causal discovery के लिए यह correction नहीं बल्कि central design problem है — और Stage 2 की conditioning set इसी को हल करने के लिए बनाई गई है।
PCMCI Algorithm

PCMCI दो चरणों में काम करता है। पहला चरण हर variable के लिए causal parents का candidate set पहचानता है। दूसरा चरण सावधानी से बनाई गई conditional independence test से हर candidate link को जाँचता है।
चरण 1: PC-Stable Condition Selection (PC1)
Stage 1 का लक्ष्य हर variable के लिए उसके वास्तविक causal parents का superset खोजना है। बाद में इसी set को Stage 2 में conditioning set की तरह उपयोग किया जाएगा।
Algorithm सभी lagged variables के पूर्ण set को potential parents मानकर शुरू होता है:
फिर यह उन variables को क्रमशः हटाता है जो से conditionally independent हैं। Iteration पर algorithm हर candidate parent की के साथ independence जाँचता है, और सबसे मजबूत बचे parents पर condition करता है (स्वयं को छोड़कर):
जहाँ , में मौजूद वे variables हैं जिनका से association सबसे मजबूत है।
"Stable" का अर्थ है कि एक ही iteration के भीतर removal decisions पिछली iteration के parent sets पर आधारित होते हैं, जिससे परिणामों में order-dependence नहीं आती। Reproducibility के लिए यह निर्णायक है।
इस चरण का significance level जानबूझकर liberal रखा जाता है (अक्सर 0.2 या अधिक, या tigramite के pc_alpha=None option से automatic)। यहाँ लक्ष्य false positives नियंत्रित करना नहीं, बल्कि irrelevant variables को अधिक से अधिक prune करते हुए सभी true parents को बचाए रखना है। में कुछ false positives स्वीकार्य हैं; false negatives नहीं।
चरण 2: Momentary Conditional Independence (MCI) Test
जब सभी variables के estimated parent sets मिल जाते हैं, Stage 2 हर potential causal link को MCI statistic से जाँचता है:
मुख्य innovation conditioning set है। हम condition करते हैं:
- Target के parents : यह में आने वाले common drivers और अन्य indirect paths का प्रभाव हटाता है।
- Source के parents : यह source variable की autocorrelation हटाता है और inflated test statistics को रोकता है।
दोनों sets पर एक साथ condition करके MCI, सभी confounders, mediators और autocorrelation के प्रभावों को हटाने के बाद, विशिष्ट lag पर के पर direct, momentary causal effect को isolate करता है।
Test statistic कोई भी valid conditional independence test हो सकता है। Linear relationships के लिए partial correlation standard choice है। Nonlinear dependencies के लिए -nearest-neighbor methods से estimated conditional mutual information (CMI), या GPDC (Gaussian Process Distance Correlation) test उपयोग किया जा सकता है।
Default Test के रूप में Partial Correlation
अधिकांश financial applications में partial correlation test (tigramite में ParCorr) workhorse है। Variables , और conditioning set दिए हों तो partial correlation है:
जहाँ और क्रमशः पर और regress करने से मिले residuals हैं:
दूसरे शब्दों में, partial correlation conditioning variables के linear influence को हटाने के बाद और के बीच linear association मापता है। Gaussian data के साथ conditional independence की null hypothesis के अंतर्गत test statistic known distribution follow करता है, जिससे exact -value computation संभव होता है।
Partial correlation values में रहते हैं और causal link strength की natural ranking देते हैं। Parent sets दोनों पर condition करने के बाद lag 1 पर BTC और lag 0 पर ETH के बीच partial correlation 0.3 का अर्थ है: "सभी identified confounders और autocorrelation का प्रभाव हटाने के बाद BTC returns में एक standard-deviation shock, एक period बाद ETH returns में 0.3-standard-deviation move predict करता है।"
Test Results से Causal Graph (DAG) तक
PCMCI का output matrices की एक जोड़ी है:
- val_matrix: Shape , जिसमें हर directed link के लिए MCI test statistic (जैसे partial correlation) है।
- p_matrix: यही shape, जिसमें corresponding -values हैं।
Causal graph बनाने के लिए corrected significance level पर -matrix को threshold करते हैं:
कौन-सा correction चुना गया है, यह हाथ हिलाकर टाल देने वाला detail नहीं है। PCMCI links test करता है जो construction से dependent हैं — में आने वाला हर link हर दूसरे link के साथ conditioning set साझा करता है, और source series शुरुआत से ही mutually correlated हैं। यही वह regime है जहाँ plain Benjamini-Hochberg विफल होता है और Benjamini-Yekutieli का harmonic penalty arbitrary dependence between tests में टिकता है; deflated Sharpe और multiple testing null false-discovery rates को मापता है जो इसे ठोस बनाते हैं। प्रकाशित होने वाले किसी graph के साथ tested links की संख्या और corrected threshold भी report करें।
परिणामी structure एक time series graph (TSG) है, यानी directed graph जहाँ:
- Nodes विशिष्ट time lags पर variables को represent करते हैं।
- Directed edges causal links और उनके lags को represent करते हैं।
- Self-loops autoregressive effects को represent करते हैं।
इस TSG को summary graph में collapse किया जा सकता है, जो variables के बीच causal links का केवल existence और direction दिखाता है (lags पर aggregate करके); interpretation के लिए यह अक्सर अधिक practical है।
Tigramite के साथ Implementation

Tigramite PCMCI का reference implementation है, जिसे German Aerospace Center (DLR) में Jakob Runge के group ने विकसित और maintain किया है। यह data handling, कई conditional independence tests, PCMCI variants और built-in visualization के लिए clean API देता है।
Installation
pip install tigramite
Recovery Sanity Check (Result नहीं)
Market data पर चलाने से पहले यह confirm करना उचित है कि pipeline आपके द्वारा planted structure खोज लेती है। यह implementation का unit test है और इससे अधिक कुछ नहीं — नीचे का ground truth data-generating loop में हाथ से लिखा गया है, इसलिए उसे recover करना code के काम करने का प्रमाण है और crypto के बारे में बिल्कुल कुछ नहीं कहता। इसे उसी तरह पढ़ें।
import numpy as np
import tigramite
from tigramite import data_processing as pp
from tigramite.pcmci import PCMCI
from tigramite.independence_tests.parcorr import ParCorr
from tigramite import plotting as tp
np.random.seed(42)
T, N = 2000, 5
var_names = ["BTC", "ETH", "SOL", "BNB", "AVAX"]
data = np.zeros((T, N))
noise = np.random.randn(T, N) * 0.5
for t in range(2, T):
data[t, 0] = 0.5 * data[t-1, 0] + noise[t, 0]
data[t, 1] = 0.5 * data[t-1, 1] + 0.4 * data[t-1, 0] + noise[t, 1]
data[t, 2] = 0.5 * data[t-1, 2] + 0.3 * data[t-1, 1] + noise[t, 2]
data[t, 3] = 0.5 * data[t-1, 3] + 0.25 * data[t-1, 0] + noise[t, 3]
data[t, 4] = 0.5 * data[t-1, 4] + 0.2 * data[t-2, 3] + noise[t, 4]
dataframe = pp.DataFrame(
data,
datatime=np.arange(T),
var_names=var_names,
)
parcorr = ParCorr(significance="analytic")
pcmci = PCMCI(
dataframe=dataframe,
cond_ind_test=parcorr,
verbosity=1,
)
results = pcmci.run_pcmci(
tau_max=4, # test lags up to 4 hours
tau_min=1, # only lagged (not contemporaneous) links
pc_alpha=None, # auto-select alpha for condition selection
alpha_level=0.01, # significance threshold for final MCI test
)
print("\n--- Significant causal links ---")
pcmci.print_significant_links(
p_matrix=results["p_matrix"],
val_matrix=results["val_matrix"],
alpha_level=0.01,
)
tp.plot_graph(
val_matrix=results["val_matrix"],
p_matrix=results["p_matrix"],
var_names=var_names,
link_colorbar_label="MCI (partial corr.)",
node_colorbar_label="Auto-MCI",
alpha_level=0.01,
figsize=(10, 6),
)
tp.plot_time_series_graph(
val_matrix=results["val_matrix"],
p_matrix=results["p_matrix"],
var_names=var_names,
link_colorbar_label="MCI (partial corr.)",
alpha_level=0.01,
figsize=(14, 6),
)
Output पढ़ना
Planted VAR पर print_significant_links लौटाता है:
Variable BTC has 0 causal parent(s):
Variable ETH has 1 causal parent(s):
BTC (lag -1): val = 0.38, p = 0.000
Variable SOL has 1 causal parent(s):
ETH (lag -1): val = 0.28, p = 0.000
Variable BNB has 1 causal parent(s):
BTC (lag -1): val = 0.24, p = 0.000
Variable AVAX has 1 causal parent(s):
BNB (lag -2): val = 0.19, p = 0.000
Planted structure वापस मिलती है, जिसमें महत्वपूर्ण negative result भी शामिल है: direct BTC -> SOL edge नहीं है, भले ही इस sample में BTC और SOL strongly correlated हों। MCI association को पूरी तरह ETH mediator को attribute करता है। यही behavior double conditioning set से अपेक्षित है और इस block का पूरा उद्देश्य है। यह passing unit test है, crypto के बारे में finding नहीं।
Real Market Data के साथ काम करना
PCMCI को weakly stationary inputs चाहिए, इसलिए prices की जगह standardized log-returns दें — ADF machinery और raw prices के fail होने के कारण statistical arbitrage और pairs trading में covered हैं। Standardized returns matrix मिलने के बाद tigramite-specific हिस्सा दो lines का है:
data = log_returns.values # (T, N) standardized log-returns
var_names = list(log_returns.columns)
dataframe = pp.DataFrame(data, var_names=var_names)
दो tigramite details सामान्य returns pipeline से अलग हैं:
- Missing data: Tigramite missing observations संभालने के लिए masked arrays support करता है। Gaps flag करने के लिए
dataframe.maskका उपयोग करें — forward-fill न करें, क्योंकि fabricated observation हर उस conditioning set में propagate होगी जिसमें वह शामिल है। - Frequency alignment: सभी series एक ही time grid पर होनी चाहिए। Crypto के लिए यह आम तौर पर आसान है, क्योंकि exchanges synchronized OHLCV data देते हैं।
इस Method को जिस Study की ज़रूरत है
ऊपर सब machinery है। जब तक machinery को real data पर चलाकर यह report नहीं किया जाता कि उसने क्या पाया — और यदि उत्तर "कुछ stable नहीं" हो तब भी — तब तक यह लेख इस blog पर publishable नहीं है। चलाया जाने वाला run:
- Setup: वास्तविक basket के hourly returns पर PCMCI+, domain reasoning से fixed
tau_max, और सभी links पर Benjamini-Yekutieli correction के साथalpha_level = 0.01। deflated Sharpe standard के अनुसार tested link count और corrected threshold report करें। - Report करने योग्य result: correction के बाद कौन-से edges बचते हैं, उनके partial-correlation magnitudes, और out-of-sample कितने बचते हैं।
- कम से कम एक stability measurement, जिसके बिना यह crypto tickers वाला tigramite README है: rolling windows में window-to-window कितने edges बने रहते हैं; या
tau_maxऔरpc_alphaके प्रति recovered graph की sensitivity; या उसी data पर ParCorr बनाम CMIknn की disagreement।
यदि graph unstable निकलता है तो वह publishable outcome है, failed outcome नहीं। "PCMCI on five majors: causal graph rolling windows में survive नहीं करता" उस line में फिट बैठता है जिसे यह blog पहले से honest negative result में चला रहा है, और यह काम करने वाले tutorial से अधिक उपयोगी लेख होगा।
सही Parameters चुनना

Maximum lag
यह parameter causal discovery के temporal horizon की सीमा तय करता है। इसे बहुत कम रखने पर धीरे फैलने वाले effects छूट सकते हैं; बहुत अधिक रखने पर computational cost और multiple-testing burden बढ़ता है।
Hourly crypto data के लिए से उचित range है। Daily data के लिए से अधिकांश lead-lag relationships पकड़ता है। यहाँ domain knowledge महत्वपूर्ण है: यदि जानते हैं कि funding rate effects को propagate होने में 8 घंटे लगते हैं, तो रखें।
Condition-selection significance
pc_alpha=None tigramite को Akaike Information Criterion का उपयोग करके इस parameter को automatic चुनने देता है, जो recommended default है। Manual setting चाहिए तो 0.1 से 0.4 के values अच्छी तरह काम करते हैं। Lower values Stage 1 को अधिक aggressive बनाते हैं (कम parents बचते हैं), जिससे computational cost घटती है लेकिन true parents हटने का risk बढ़ता है।
Final significance level
यह standard hypothesis-testing threshold है। Exploratory analysis के लिए ठीक है। Trading decision में जाने वाली किसी भी चीज़ के लिए या अधिक strict रखें, और इसे पहले वर्णित dependence-aware correction के बाद लागू करें — nominal level वह level नहीं है जिस पर आप वास्तव में test कर रहे हैं।
PCMCI+ : Contemporaneous Links जोड़ना

Standard PCMCI केवल lagged causal links () खोजता है। लेकिन crypto markets में, जहाँ information assets के बीच seconds में propagate होती है, hourly sampling frequency के कारण अनेक causal effects contemporaneous () दिखाई देते हैं।
PCMCI+ (Runge, 2020) PCMCI को lagged और contemporaneous दोनों causal links खोजने के लिए बढ़ाता है। Contemporaneous links default रूप से undirected होते हैं (क्योंकि उसी time step के भीतर time ordering cause को effect से अलग नहीं कर सकती), लेकिन कुछ links standard PC algorithm के orientation rules (collider detection, acyclicity constraints) से orient किए जा सकते हैं।
results_plus = pcmci.run_pcmciplus(
tau_max=4,
tau_min=0, # include contemporaneous links
pc_alpha=None,
)
Hourly या उससे कम frequency वाले crypto applications के लिए PCMCI+ अक्सर standard PCMCI से अधिक उपयुक्त है, क्योंकि कई cross-asset effects sampling interval से तेज़ होते हैं।
Nonlinear Extensions

Financial time series अक्सर nonlinear dependencies दिखाती हैं (जैसे volatility clustering, regime-dependent lead-lag effects)। Tigramite कई nonlinear conditional independence tests देता है:
Gaussian Process Distance Correlation (GPDC)
from tigramite.independence_tests.gpdc import GPDC
gpdc = GPDC(significance="analytic", gp_params=None)
pcmci = PCMCI(dataframe=dataframe, cond_ind_test=gpdc)
GPDC conditioning set के influence को हटाने के लिए Gaussian process regression का उपयोग करता है, फिर residuals पर distance correlation लागू करता है। Nonlinear effects detect करने में यह ParCorr से अधिक powerful है, लेकिन काफी धीमा है।
Conditional Mutual Information (CMIknn)
from tigramite.independence_tests.cmiknn import CMIknn
cmiknn = CMIknn(significance="shuffle_test", knn=0.1, shuffle_neighbors=5)
pcmci = PCMCI(dataframe=dataframe, cond_ind_test=cmiknn)
CMIknn -nearest-neighbor methods से conditional mutual information estimate करता है। यह पूरी तरह nonparametric है और arbitrary functional dependencies detect कर सकता है। Trade-off computational cost और statistical power के लिए अधिक data की आवश्यकता है।
अधिकांश crypto trading applications में ParCorr से शुरू करें। Nonlinear tests पर तभी जाएँ जब nonlinear causal mechanisms के specific evidence और पर्याप्त data (आमतौर पर ) हों।
Causal Graph कहाँ जुड़ सकता है

Correlation matrix जो चीज़ नहीं दे सकती और causal graph देता है, वह है: out-degree। 20-50 assets पर PCMCI चलाएँ और जिन nodes की outgoing edges बहुत हैं वे information leaders हैं — उनके moves बाकी book के लिए predictive content रखते हैं, और यह directional claim है जिसे symmetric correlation matrix संरचनात्मक रूप से बना ही नहीं सकती। Factor decomposition बताता है कि BTC variance पर हावी है; out-degree ranking बताती है कि BTC इसलिए हावी है क्योंकि वह lead करती है या इसलिए क्योंकि BTC समेत सब उसी macro shock पर load होते हैं।
यदि कभी stable graph recover हो जाए तो इस blog के मौजूदा काम से उसके जुड़ने की जगहें पर्याप्त स्पष्ट हैं। Graph topology में rolling-window changes structural-break signal हैं, जिसका विषय HMMs से regime detection है — खुला प्रश्न यह है कि causal graph state model से पहले transition detect करता है या नहीं। एक asset पर venues के across PCMCI लागू करना cross-venue price leadership address करता है, जिसे smart order routing में millisecond resolution पर पहले ही मापा गया है; hourly bars पर graph को उससे बेहतर होना होगा, केवल वही बात दोहरानी नहीं। और 8-hour funding mechanism से बना periodic perp-spot link, जो funding rate arbitrage में covered है, known-answer test case है: PCMCI को वह causal structure recover करना चाहिए जिसे हम independently verify कर सकते हैं, जिससे यह discovery के बजाय validation target बनता है।
इनमें से कोई finding नहीं है। ये ऊपर बताई study का इंतज़ार करती hypotheses हैं।
Limitations और Caveats

PCMCI powerful tool है, लेकिन इसमें महत्वपूर्ण limitations हैं जिन्हें practitioners को समझना चाहिए:
-
Causal sufficiency assumption: PCMCI मानता है कि सभी relevant variables observed हैं। यदि कोई hidden common driver (जैसे whale की trading activity, unreleased news) दो observed assets को प्रभावित करता है, तो PCMCI उनके बीच direct causal link incorrectly report कर सकता है। LPCMCI variant latent confounders की अनुमति देकर इसे आंशिक रूप से address करता है, लेकिन इसकी कीमत कम oriented edges हैं।
-
Stationarity assumption: causal structure analysis window में constant माना जाता है। व्यवहार में crypto market dynamics तेज़ी से बदलती हैं। Structural breaks detect करने के लिए rolling-window analysis करें।
-
Linear vs. nonlinear:
ParCorrके साथ केवल linear causal effects detect होते हैं। Nonlinear mechanism (जैसे "BTC केवल तब ETH को गिराता है जब BTC 5% से अधिक गिरता है") linear test को दिखाई नहीं देगा। -
Sampling frequency matters: sampling frequency से तेज़ causal effects standard PCMCI में contemporaneous () links दिखते हैं और उनका direction ambiguous हो सकता है। PCMCI+ का उपयोग करें और higher-frequency data पर विचार करें।
-
Multiple testing: dependent tests, ऊपर वर्णित तरीके से corrected — dependence structure obvious choice को क्यों रोकती है, इसके लिए deflated Sharpe और multiple testing देखें।
-
Sample size requirements: Reliable causal discovery के लिए पर्याप्त data चाहिए। Rough guideline के तौर पर variables वाले
ParCorrके लिए और nonlinear tests या बड़े variable sets के लिए रखें।
PCMCI बनाम अन्य Methods

| Method | Handles autocorrelation | Handles common drivers | Contemporaneous links | Nonlinear | Latent confounders |
|---|---|---|---|---|---|
| Granger causality | Partly | No (bivariate) | No | With extensions | No |
| Transfer entropy | Partly | No (bivariate) | No | Yes | No |
| PCMCI | Yes (MCI) | Yes | No | With CMIknn/GPDC | No |
| PCMCI+ | Yes | Yes | Yes | With CMIknn/GPDC | No |
| LPCMCI | Yes | Yes | Yes | With CMIknn/GPDC | Yes |
| VAR-LiNGAM | No | Yes | Yes | No | No |
Granger causality और transfer entropy पर PCMCI का मुख्य advantage MCI test है, जो multivariate setting में autocorrelation और common drivers को सही ढंग से account करता है। यही crypto markets में मिलने वाला scenario है, जहाँ दर्जनों correlated, autocorrelated assets एक साथ interact करते हैं।
Conclusion

Two-stage construction ही वह contribution है जिसे साथ ले जाना चाहिए: parent set को सीमित करने के लिए sparse condition selection, फिर ऐसा MCI test जिसकी conditioning set में target के साथ-साथ source के parents भी शामिल हों। यही दूसरा आधा PCMCI को Granger और transfer entropy से अलग करता है, और यही method को crypto returns जितनी autocorrelated series पर viable बनाता है।
Method अपने साथ evidence नहीं लाता। Recovered graph information flow के बारे में hypothesis है, signal नहीं, और इस blog पर तब तक इसका कोई अर्थ नहीं जब तक इसे stated basket, stated date range, dependence-aware correction और कम से कम एक measurement के साथ real data पर न चलाया जाए कि window बदलने पर graph स्थिर रहता है या नहीं। वह run होने तक यहाँ सब tooling समझें।
यदि graph stable नहीं निकलता, तो वही article है। यह यहाँ का पहला negative result नहीं होगा जिसकी कीमत working tutorial से अधिक हो।
References
- Runge, J., Nowack, P., Kretschmer, M., Flaxman, S., and Sejdinovic, D. (2019). Detecting and quantifying causal associations in large nonlinear time series datasets. Science Advances, 5(11), eaau4996.
- Runge, J. (2020). Discovering contemporaneous and lagged causal relations in autocorrelated nonlinear time series datasets. Proceedings of the 36th Conference on Uncertainty in Artificial Intelligence (UAI), PMLR 124:1388-1397.
- Tigramite documentation: https://jakobrunge.github.io/tigramite/
- Tigramite GitHub repository: https://github.com/jakobrunge/tigramite
Authors
Trading-systems engineer
Trading-systems engineer building bots since 2017: cross-exchange arbitrage (connected up to 30 venues), cointegration-based pairs arbitrage across spot and futures, scalping, news and sentiment-driven strategies, trend algorithms, and portfolio management and balancing algorithms. Also builds sub-millisecond order execution, big-data warehouses, backtesting engines, AI agents, and trading interfaces (incl. open-source profitmaker.cc). Stack: JS/TS, Python, Rust/Zig/Go, DevOps, backend, frontend, architecture.