Pairs Trading आणि Cointegration: स्पष्ट स्पष्टीकरण
Pairs Trading साठी correlation नव्हे, cointegration आवश्यक आहे. वास्तविक closing prices वापरून hedge ratio, spread आणि z score स्वतः कसे मोजायचे ते शिका.
Pairs trading आणि cointegration यांचा जवळचा संबंध आहे. हा व्यवहार दोन शेअरच्या किमतींमधील अशा फरकावर आधारित असतो, जो पुन्हा पुन्हा एका स्थिर पातळीवर परततो. Cointegration ही सांख्यिकीय गुणधर्म आहे; ती असा फरक अस्तित्वात असल्याचे दर्शवते.
Correlation वेगळी गोष्ट मोजते. दोन शेअरच्या किमतींमध्ये उच्च correlation असू शकते, तरीही त्या कायम एकमेकींपासून दूर जाऊ शकतात. उलट, ज्यांच्या दैनंदिन हालचालींमध्ये फारसा ताळमेळ नसतो, अशा दोन शेअरमधील फरक अनेक वर्षे स्थिर राहू शकतो.
या पानावर hedge ratio, spread आणि z score हे घटक प्रत्यक्ष पद्धतीने तयार केले आहेत. त्यानंतर वास्तविक closing pricesवर या तिन्हींचे मोजमाप केले आहे. ही रचना कुठे आणि कशी अपयशी ठरते, यालाही तितकीच जागा दिली आहे.
पेअर्स ट्रेडिंगमधील cointegration म्हणजे काय?
Correlation म्हणजे दोन शेअर्सच्या दैनंदिन टक्केवारीतील हालचाली किती जवळच्या आहेत हे दर्शवणारे मोजमाप. त्याची श्रेणी -1 ते +1 अशी असते. +1 म्हणजे नमुन्यातील प्रत्येक सत्रात दोन्ही शेअर्स एकाच दिशेने वाढले किंवा घसरले. मात्र, दोन किंमतींमधील अंतर यावरून समजत नाही.
Cointegration हे त्या अंतराविषयीचे विधान आहे. शेअर B मधून शेअर Aचा एक निश्चित गुणक वजा करा आणि उरलेली रक्कम पाहा. ही उरलेली मालिका, म्हणजे spread, दूरवर भटकण्याऐवजी मर्यादित पट्ट्यात राहिली, तर हे दोन्ही शेअर्स cointegrated आहेत. किंमती स्वतः कोणत्याही दिशेने जाऊ शकतात. मात्र, त्यांचे हे संयोजन मर्यादेबाहेर जात नाही.
पाठ्यपुस्तकांमध्ये यासाठी कुत्रा आणि त्याला बांधलेली दोरी हे उदाहरण दिले जाते. चालणारी व्यक्ती आणि कुत्रा रस्त्यावर अनिश्चित मार्गांनी पुढे जाऊ शकतात, पण दोरी त्यांच्यातील अंतर मर्यादित ठेवते. Correlation हे दोघे एकाच वेळी पाऊल टाकतात का, हे विचारते. Cointegration हे दोरी अंतर नियंत्रित ठेवते का, हे विचारते.
खालील तक्त्यात 2024 आणि 2025 या कालावधीत पाच परिचित पेअर्ससाठी पहिली संकल्पना मोजली आहे. return_corr स्तंभात दैनंदिन टक्केवारीतील हालचालींचे correlation दिले आहे. या शब्दाचा सामान्यतः हाच अर्थ घेतला जातो. price_corr स्तंभात कच्च्या closing pricesचे correlation दिले आहे. दोन्ही शेअर्स वाढले असतील, तर हा आकडा जवळपास कोणत्याही दोन शेअर्सना अनुकूल दिसू शकतो.
प्रत्येक आकड्यामागील अचूक SQL
WITH
px AS (
SELECT
ticker,
date,
toFloat64(any(close)) AS close_px
FROM global_markets.stocks_daily_aggs
WHERE ticker IN ('KO', 'PEP', 'HD', 'LOW', 'XOM', 'CVX', 'V', 'MA', 'AAPL', 'MSFT')
AND date BETWEEN '2024-01-01' AND '2025-12-31'
GROUP BY ticker, date
),
rets AS (
SELECT
ticker,
date,
close_px,
close_px / lagInFrame(close_px) OVER (
PARTITION BY ticker ORDER BY date
ROWS BETWEEN 1 PRECEDING AND CURRENT ROW
) - 1 AS ret
FROM px
)
SELECT
p.pair AS pair,
round(corr(a.ret, b.ret), 3) AS return_corr,
round(corr(a.close_px, b.close_px), 3) AS price_corr
FROM
(
SELECT
tupleElement(t, 1) AS leg_a,
tupleElement(t, 2) AS leg_b,
tupleElement(t, 3) AS pair
FROM
(
SELECT arrayJoin([
('KO', 'PEP', 'KO / PEP'),
('HD', 'LOW', 'HD / LOW'),
('XOM', 'CVX', 'XOM / CVX'),
('V', 'MA', 'V / MA'),
('AAPL', 'MSFT', 'AAPL / MSFT')
]) AS t
)
) AS p
INNER JOIN rets AS a ON a.ticker = p.leg_a
INNER JOIN rets AS b ON b.ticker = p.leg_b AND b.date = a.date
WHERE isFinite(a.ret) AND isFinite(b.ret)
GROUP BY pair
ORDER BY return_corr DESCदैनंदिन हालचालींच्या correlationनुसार क्रमवारी लावल्यास, HD / LOW हे 0.868 या गुणांकासह तक्त्याच्या शीर्षस्थानी आहे. त्याच कालावधीत दोन्ही price levelsचे correlation 0.849 इतके होते. तळाच्या ओळीत AAPL / MSFT चे return correlation 0.479 असून price correlation 0.508 आहे. यापैकी कोणताही स्तंभ pairs trader विचारत असलेल्या प्रश्नाचे उत्तर देत नाही. दोन्ही co-movement मोजतात; मात्र या व्यवहारासाठी अंतराचे मोजमाप आवश्यक असते.
हेज रेशो आणि spread कसा मोजता?
दोन closing pricesच्या यादींवर, क्रमाने पाच पायऱ्या करा. math हा एकमेव import वापरून Plain Pythonमध्ये हे सर्व करता येते: कोणतीही packages, data vendor किंवा network आवश्यक नाही.
- प्रत्येक seriesचे केंद्रिकीकरण करा.
mean_a = sum(a) / len(a), आणि यादीbसाठीही तेच करा. - हेज रेशो फिट करा. हा Bचा Aवरील सरळ रेषेचा slope आहे:
beta = sum((a[i] - mean_a) * (b[i] - mean_b) for i in range(len(a))) / sum((x - mean_a) ** 2 for x in a). एकक Bची भरपाई करण्यासाठी आवश्यक Aची एककसंख्या म्हणून त्याचा अर्थ घ्या. - spread तयार करा.
spread = [b[i] - beta * a[i] for i in range(len(a))]. cointegrated pairमध्ये ही series trend करण्याऐवजी एका पातळीभोवती फिरते. - trailing window घ्या.
w = spread[i - win + 1 : i + 1]वापरल्यास window meanmu = sum(w) / len(w)आणि sample standard deviationsd = math.sqrt(sum((x - mu) ** 2 for x in w) / (len(w) - 1))असतो. Trailingचा अर्थ, ज्या दिवसाचे मूल्यांकन केले जात आहे त्या दिवसावर window संपते आणि त्यानंतरचा कोणताही डेटा वापरला जात नाही. - स्कोअर काढा आणि print करा.
z = (spread[i] - mu) / sd, त्यानंतरabs(z) > 1.5असताना entry line आणिabs(z) < 0.5झाल्यावर exit line print करा. z चे मूल्य 2 असल्यास spread त्याच्या अलीकडील सरासरीपेक्षा दोन standard deviationsने वर आहे.
हा run पाहण्यासाठी hard-coded price listsच्या दोन याद्या पुरेशा आहेत. win चे मूल्य 5 ठेवल्यास toy example समजण्यास सोपे राहते. प्रत्यक्ष कामात सुमारे 63 sessions, म्हणजे जवळपास एक तिमाही, वापरली जातात. प्रवेशासाठी 1.5 आणि बाहेर पडण्यासाठी 0.5 हे thresholds एका tutorialमधून दुसऱ्यात पुढे आलेल्या प्रथा आहेत. ती निष्कर्षांवर आधारित नाहीत आणि खालील panelsमधील कोणत्याही डेटाने ती निवडलेली नाहीत.
प्रत्यक्ष किंमतींवर pairs trading z score कसा दिसतो?
हीच गणना दोन परिचित beverage कंपन्यांच्या शेअर्सवर, KO आणि PEP, लागू केली आहे. Hedge ratio केवळ 2023 मधील closing pricesवरून काढला आहे. त्यानंतर chartमध्ये समाविष्ट असलेल्या 2024 आणि 2025 या कालावधीत तो स्थिर ठेवला आहे. ज्या sampleवर पुढे score काढायचा आहे त्याच sampleवर slope fit करणे म्हणजे backtestingमधील look-ahead bias होय. Fit window आणि scoring window वेगळे ठेवणे हा त्यापासून बचाव करण्याचा सर्वात सोपा उपाय आहे.
प्रत्येक आकड्यामागील अचूक SQL
WITH
daily AS (
SELECT
date,
anyIf(toFloat64(close), ticker = 'PEP') AS pep,
anyIf(toFloat64(close), ticker = 'KO') AS ko
FROM global_markets.stocks_daily_aggs
WHERE ticker IN ('KO', 'PEP')
AND date BETWEEN '2023-01-01' AND '2025-12-31'
GROUP BY date
HAVING pep > 0 AND ko > 0
),
fitted AS (
SELECT covarSamp(pep, ko) / varSamp(ko) AS beta
FROM daily
WHERE date < '2024-01-01'
),
spread AS (
SELECT
daily.date AS date,
daily.pep - fitted.beta * daily.ko AS spread_usd
FROM daily
CROSS JOIN fitted
),
scored AS (
SELECT
date,
(spread_usd - avg(spread_usd) OVER (
ORDER BY date ROWS BETWEEN 62 PRECEDING AND CURRENT ROW))
/ stddevSampStable(spread_usd) OVER (
ORDER BY date ROWS BETWEEN 62 PRECEDING AND CURRENT ROW) AS z
FROM spread
)
SELECT
toString(toMonday(date)) AS week,
round(argMax(z, date), 2) AS z_score
FROM scored
WHERE date >= '2024-01-01'
AND isFinite(z)
GROUP BY week
ORDER BY weekया आलेखात 105 साप्ताहिक readings आहेत. Windowची सुरुवात z score -1.53 वर होते आणि शेवट -0.68 वर होतो. कोणत्याही एका बिंदूकडे न पाहता संपूर्ण आकृतीचा आकार पाहा. Strategy गृहीत धरते त्या पद्धतीने pair वागत असल्यास तो threshold ओलांडतो, काही आठवडे पुन्हा शून्याकडे सरकतो आणि नंतर threshold पुन्हा ओलांडतो. Pairने mean reversion थांबवले असल्यास तो thresholdच्या पलीकडेच राहतो. Chartवर हे मध्यमानापासून दूर असलेला लांब, सपाट पट्टा दिसतो.
कॉइन्टिग्रेटेड spread नेहमी पुन्हा सरासरीकडे येतो का?
नाही. हे समजून घेण्याचा योग्य मार्ग म्हणजे प्रत्येक सुरुवातीच्या बिंदूनंतर काय घडले, हे मोजणे. खालील पॅनेलमध्ये 2019 ते 2025च्या उत्तरार्धापर्यंतची प्रत्येक session त्या दिवसाच्या z scoreनुसार गटबद्ध केली आहे. त्यानंतर त्याच spreadचा वीस sessionsनंतरचा सरासरी z score दाखवला आहे. यासाठी fitted spreadऐवजी price ratioचा log वापरला आहे. त्यामुळे नंतर केलेल्या regressionचा chartवर कोणताही परिणाम होत नाही.
प्रत्येक आकड्यामागील अचूक SQL
WITH
daily AS (
SELECT
date,
anyIf(toFloat64(close), ticker = 'PEP') AS pep,
anyIf(toFloat64(close), ticker = 'KO') AS ko
FROM global_markets.stocks_daily_aggs
WHERE ticker IN ('KO', 'PEP')
AND date BETWEEN '2018-01-01' AND '2025-12-31'
GROUP BY date
HAVING pep > 0 AND ko > 0
),
spread AS (
SELECT
date,
log(pep / ko) AS log_ratio
FROM daily
),
scored AS (
SELECT
date,
(log_ratio - avg(log_ratio) OVER (
ORDER BY date ROWS BETWEEN 62 PRECEDING AND CURRENT ROW))
/ stddevSampStable(log_ratio) OVER (
ORDER BY date ROWS BETWEEN 62 PRECEDING AND CURRENT ROW) AS z
FROM spread
),
horizon AS (
SELECT
date,
z,
leadInFrame(z, 20) OVER (
ORDER BY date ROWS BETWEEN CURRENT ROW AND 20 FOLLOWING
) AS z_fwd
FROM scored
WHERE isFinite(z)
)
SELECT
multiIf(z < -2, 'z below -2',
z < -1, 'z -2 to -1',
z < 0, 'z -1 to 0',
z < 1, 'z 0 to 1',
z < 2, 'z 1 to 2',
'z above 2') AS z_bucket,
round(avg(z), 2) AS avg_z_start,
round(avg(z_fwd), 2) AS avg_z_20d_later,
count() AS episode_count
FROM horizon
WHERE date >= '2019-01-01'
AND date <= '2025-11-15'
AND isFinite(z_fwd)
GROUP BY z_bucket
ORDER BY avg_z_startz below -2 bucketमध्ये सुरू झालेल्या sessionsचा त्या दिवशीचा सरासरी z score -2.42 होता. त्याच spreadचा वीस sessionsनंतरचा सरासरी z score -0.84 होता, आणि यात 138 sessionsचा समावेश होता. दुसऱ्या टोकाला, z above 2 bucketची सुरुवात 2.64 वर झाली आणि वीस sessionsनंतरचा z score 0.92 मोजला गेला. दोन्ही टोकांहून शून्याकडे खेचणारा curve म्हणजे mean reversionचा नमुना आहे.
Count column लक्षात घेऊन हे वाचा. बाहेरील bucketsमध्ये sessionsची संख्या सर्वात कमी आहे. तसेच, एका bucketमधील sessionsमध्ये मोठ्या प्रमाणावर overlap असतो. त्यामुळे काही दीर्घ काळ चाललेल्या कालखंडांमुळे बहुतेक rows तयार होतात. सरासरी कोणत्याही एका entryबाबत हमी देत नाही.
पेअर्स ट्रेडिंग का फायदा होणे का थांबते?
हेज रेशो स्थिर नसतो. तो प्रत्येक कॅलेंडर वर्षासाठी पुन्हा मोजा; तो बदलतो.
प्रत्येक आकड्यामागील अचूक SQL
WITH daily AS (
SELECT
date,
anyIf(toFloat64(close), ticker = 'PEP') AS pep,
anyIf(toFloat64(close), ticker = 'KO') AS ko,
anyIf(toFloat64(close), ticker = 'LOW') AS lowes,
anyIf(toFloat64(close), ticker = 'HD') AS hd
FROM global_markets.stocks_daily_aggs
WHERE ticker IN ('KO', 'PEP', 'HD', 'LOW')
AND date BETWEEN '2019-01-01' AND '2025-12-31'
GROUP BY date
HAVING ko > 0 AND pep > 0 AND hd > 0 AND lowes > 0
)
SELECT
toYear(date) AS year,
round(covarSamp(pep, ko) / varSamp(ko), 3) AS pep_on_ko_beta,
round(covarSamp(lowes, hd) / varSamp(hd), 3) AS lowes_on_hd_beta
FROM daily
GROUP BY year
ORDER BY year2.338 ते 2019 आणि -0.543 ते 2025 या कालावधीत PEPचा KOवरील slope मोजला आहे. तुलनेसाठी दुसरी sector pairही बाजूला दाखवली आहे. संपूर्ण इतिहासावर एकच slope बसवून तो पहिल्या दिवसापासून वापरणाऱ्या backtestमध्ये सुरुवातीच्या प्रत्येक व्यवहाराला त्या वेळी उपलब्ध नसलेली संख्या दिली जाते. उपाय साधा आणि प्रभावी आहे: trailing windowवर पुन्हा मोजणी करा आणि प्रत्येक दिवसाचे मूल्यमापन त्या दिवशी उपलब्ध असलेल्या parametersने करा. पुनरुत्पादक backtestमुळे ही शिस्त दुसरी व्यक्ती तपासू शकते.
Corporate actionsमुळे एका legमध्ये रातोरात बदल होतो. 4-for-1 splitमध्ये एक शेअर चार शेअर्समध्ये बदलतो आणि quoted price एक-चतुर्थांश होते. त्या तारखेच्या आसपास unadjusted pricesवर मोजलेला spread अशा पातळीवर उडी मारतो, ज्यावर प्रत्यक्ष व्यवहार कधीच झाला नव्हता. Mergers, spin-offs आणि index changes याच प्रकारचे, पण कमी स्पष्ट, बदल घडवतात. सुरुवात split-adjusted price historyपासून करा. एखादा symbol पूर्णपणे वेगळ्या कंपनीला पुन्हा दिला जाऊ शकतो, आणि हेच ticker symbols datasetsमध्ये का बिघडतात याचे कारण आहे.
Break आणि opportunity घडत असताना एकसारखे दिसतात. एखाद्या pairने नोंदवलेला सर्वाधिक z score हा कधी मिळालेला सर्वोत्तम entry point असू शकतो; किंवा संपलेल्या संबंधाच्या पहिल्या आठवड्याचे चिन्ह असू शकतो. त्या दिवशी दोन्ही stretched spreadसारखेच दिसतात. Statistics जिथे अपुरी पडतात, तिथे position sizing महत्त्वाची ठरते. Kelly criterion position sizing आणि volatility targeting position sizing ही एका spreadला accountच्या किती भागावर परिणाम करू द्यायचा यासाठीची दोन frameworks आहेत. maximum drawdown चुकीच्या बाजूला दीर्घकाळ राहिल्याने equity curveवर होणारा परिणाम मोजतो.
Rolling z score ही cointegration test नाही. यासाठी औपचारिक tests उपलब्ध आहेत. Engle-Granger procedureमध्ये regression चालवून उरलेल्या spreadची stationarity तपासली जाते. Johansen test ही संकल्पना दोन namesच्या पुढे विस्तारतो. z scoreमध्ये या testsद्वारे तपासल्या जाणाऱ्या गुणधर्माचा अंदाज गृहीत धरला जातो. स्थिर mean नसलेल्या spreadवरही तो मोठी संख्या सहज दाखवू शकतो, कारण trending series स्वतःच्या trailing averageपासून नेहमीच बरीच दूर असते.
Data notes आणि या संख्यांमध्ये समाविष्ट नसलेल्या बाबी
Correlation panelमध्ये 2024-01-01 ते 2025-12-31 या कालावधीतील daily closes वापरले आहेत. सलग closesमधून returns मोजले असून प्रत्येक nameचे पहिले session वगळले आहे. z score traceमध्ये hedge ratio फक्त 2023 closesवर मोजला आहे. त्यानंतर 2024 आणि 2025चे scores trailing 63-session mean आणि sample standard deviation वापरून मोजले आहेत. प्रत्येक आठवड्याच्या शेवटच्या readingवर sampling केले आहे. Reversion panelमध्ये 2018पासूनचा history load केला आहे आणि 2019पासूनचे reports दाखवले आहेत. त्यामुळे प्रत्येक scored dayच्या आधी पूर्ण trailing window उपलब्ध आहे. हा panel 2025च्या मध्य-Novemberमध्ये थांबतो, जेणेकरून sampleमध्ये प्रत्येक दिवसानंतर पुढील वीस sessions उपलब्ध राहतात.
यापैकी कोणताही panel strategy नाही. त्यात trading costs, bid-ask spread, short legवरील borrow fee, dividends किंवा financing यांचा समावेश नाही. फक्त closing prices वापरल्या आहेत. त्यामुळे closeवर दाखवलेला entry हा प्रत्यक्षात कोणीही त्या किंमतीवर भरू शकलेला entry असेलच असे नाही.
वारंवार विचारले जाणारे प्रश्न
correlation आणि cointegration यांमध्ये काय फरक आहे?
Correlation दोन शेअर्स कोणत्या दिवशी आणि कोणत्या दिशेने एकाच वेळी हालचाल करतात का, हे -1 ते +1 या मापनावर दर्शवते. Cointegration त्यांच्या किंमतींमधील अंतर स्थिर मर्यादेत राहते का, हे मोजते. एखाद्या pairला पहिल्या मापनात उच्च गुण मिळू शकतो, पण दुसऱ्यात अपयश येऊ शकते. Correlation screenमधून मिळालेले pairs नंतर एकमेकांपासून दूर जाण्याचे हेच सामान्य कारण असते.
pairs tradingमध्ये hedge ratio कसा मोजतात?
एका शेअरची किंमत दुसऱ्या शेअरच्या किमतीवर सरळ रेषेने fit करा आणि तिचा slope घ्या. हा slope म्हणजे दुसऱ्या शेअरच्या एका unitचा परिणाम offset करण्यासाठी आवश्यक असलेल्या पहिल्या शेअरच्या unitsची संख्या. हे प्रमाण बदलत असते. त्यामुळे बहुतांश implementationsमध्ये संपूर्ण sampleवर एकदाच fit करण्याऐवजी trailing windowवर पुन्हा fit केले जाते.
प्रवेश आणि निर्गमनासाठी pairs traders कोणता z score वापरतात?
Tutorialमधील उदाहरणांमध्ये trailing meanपासून 1.5 ते 2 standard deviations अंतरावर entry आणि सुमारे 0.5वर exit घेतला जातो. ही संख्या मोजून ठरवलेली सर्वोत्तम पातळी नसून जुन्या लेखनांमधून आलेली प्रथा आहे. ही पातळी बदलल्यास tradesची संख्या आणि प्रत्येक trade किती काळ ठेवला जातो, हे दोन्ही बदलतात.
कमी correlation असलेले दोन शेअर्स cointegrated असू शकतात का?
होय. Correlation दैनंदिन बदलांवर मोजले जाते, तर cointegration gapच्या पातळीवर मोजले जाते. त्यामुळे दिवसागणिक हालचाली क्वचितच एकसारख्या असलेल्या दोन शेअर्समधील दीर्घकालीन अंतर तरीही स्थिर राहू शकते. याउलटही घडते: दैनंदिन correlation उच्च असते, पण gap वर्षागणिक वाढत जाते.
cointegrated pairची रणनीती काम करणे का थांबवते?
Gap स्थिर ठेवणारा संबंध बदलू शकतो. याची कारणे merger, spin-off, एखाद्या कंपनीच्या व्यवसायमिश्रणातील बदल किंवा शेअर कोणाच्या मालकीचा आहे हे बदलणारा index बदल असू शकतात. Breakच्या आधीच्या दिवसांमधील statistics आणि सामान्य reversionच्या आधीच्या दिवसांमधील statistics सारखीच दिसतात. त्यामुळे या जोखमीचा अंदाज बांधण्यापेक्षा position sizingमध्ये तिचा समावेश करणे आवश्यक असते.
येथील प्रत्येक panelच्या मागे अचूक SQL दिलेले आहे. त्यामुळे windows, fit period आणि bucket edges हे सर्व स्पष्टपणे दिसतात. तुम्ही पाहत असलेल्या pairवर तोच spread आणि z score चालवण्यासाठी Strasmore terminalवर plain Englishमध्ये प्रश्न विचारा.