Workspace IndexMath › Day 43

Correlation and Cointegration (A Light Treatment) TODO

Math · Day 43 / 52 · November — Probability, Statistics & Financial Math (Day 35-43)

Concept

The correlation coefficient standardizes the degree of linear co-movement between two variables to a value between -1 and 1; it does not imply causation and fails to capture nonlinear relationships properly. In time series, when two series each have their own trend, correlation and regression coefficients can come out large even with no real relationship — the spurious regression problem — so correlation between levels shouldn't be trusted at face value. Cointegration describes a relationship where each series is individually non-stationary (has a unit root), but some linear combination of the two is stationary and mean-reverting — a statement that a long-run equilibrium exists between the two series. The standard procedure is to test each series for a unit root, estimate the cointegrating relationship, then test whether its residuals are stationary; if cointegration holds, an error-correction model can describe how fast short-term deviations revert to equilibrium. In short: correlation is a statement about simultaneous movement, and cointegration is a statement about a long-run relationship.

Pairs trading, hedge-ratio sizing, and analyzing peg deviations for stablecoins or LSTs are all really asking whether the levels of two series stay tied together over the long run — which is cointegration's territory, not correlation's.

Code & Formula

# 상관관계와 공적분(가볍게) — 수준(level) 상관의 함정 vs 스프레드의 평균회귀
# 두 계열이 각자 추세를 가지면 아무 관계가 없어도 수준끼리는 강하게 "상관"돼 보인다(허위회귀).

import random
import statistics

random.seed(3)
N = 500

def corr(xs, ys):
    mx, my = statistics.mean(xs), statistics.mean(ys)
    cov = sum((x - mx) * (y - my) for x, y in zip(xs, ys))
    sx = (sum((x - mx) ** 2 for x in xs)) ** 0.5
    sy = (sum((y - my) ** 2 for y in ys)) ** 0.5
    return cov / (sx * sy)

# 1) 서로 무관한 두 랜덤워크(각자 추세만 가짐) — 진짜 관계는 없다
a_level = [100.0]
b_level = [50.0]
for _ in range(N):
    a_level.append(a_level[-1] + random.gauss(0.15, 1.0))  # 독립적인 상승 추세(추세가 노이즈를 압도)
    b_level.append(b_level[-1] + random.gauss(0.10, 1.0))
a_ret = [a_level[i] - a_level[i - 1] for i in range(1, len(a_level))]
b_ret = [b_level[i] - b_level[i - 1] for i in range(1, len(b_level))]

print(f"[무관한 두 랜덤워크] 수준(level) 상관 = {corr(a_level, b_level):.3f}  (허위로 높게 나옴)")
print(f"[무관한 두 랜덤워크] 수익률(return) 상관 = {corr(a_ret, b_ret):.3f}  (실제로는 0에 가까움)")

# 2) 공적분 관계: 두 계열은 각자 비정상(추세)이지만 스프레드는 평균회귀하도록 구성
x_level = [100.0]
for _ in range(N):
    x_level.append(x_level[-1] + random.gauss(0.0, 1.0))

spread = [0.0]
for _ in range(N):
    # 스프레드가 커질수록 되돌아오는 힘(AR(1), 계수<1 => 평균회귀) + 노이즈
    spread.append(spread[-1] * 0.8 + random.gauss(0.0, 0.5))

y_level = [x_level[i] - spread[i] for i in range(N + 1)]

print(f"\n[공적분 쌍] 수준 상관 = {corr(x_level, y_level):.3f}")
print(f"[공적분 쌍] 스프레드 평균/표준편차 = {statistics.mean(spread):.3f} / {statistics.stdev(spread):.3f}")
print(f"[공적분 쌍] 스프레드가 [-3, 3] 범위 안에 머문 비율 = {sum(-3 <= s <= 3 for s in spread) / len(spread):.2%}")
print("-> 스프레드가 특정 범위를 벗어나지 않고 되돌아온다면 두 계열이 장기적으로 묶여있다는 신호(공적분).")

Exercise

Take price time series for two assets, compute the correlation of log-price levels and the correlation of log returns separately, compare how different the values are, and plot whether the spread between the two series mean-reverts.

Practical Connection

Prices of different markets covering the same event, or of complementary outcome tokens, should stay tied together in the long run — so if the spread does not mean-revert, that's grounds to suspect a liquidity shortfall or a difference in settlement terms.

If you study this on a given day, add a note link and a ✅ to this line in the source curriculum (docs/knowledge/math-50-curriculum.md) and this spot will lead straight to the note body. You can also write directly on this page — but regenerating overwrites it, so it's safer to keep anything you want to save as markdown under docs/algorithms/.


한국어

상관관계와 공적분(가볍게) TODO

Math · Day 43 / 52 · 11월 — 확률·통계·금융수학 (Day 35–43)

개념

상관계수는 두 변수의 선형 동조 정도를 -1과 1 사이로 표준화한 값이며, 인과를 뜻하지 않고 비선형 관계도 제대로 잡지 못한다. 시계열에서는 두 계열이 각각 추세를 가질 경우 실제 관계가 없어도 상관과 회귀 계수가 크게 나오는 허위 회귀 문제가 생기므로, 수준(level)끼리의 상관은 그대로 믿기 어렵다. 공적분은 각 계열은 비정상(단위근을 가짐)이지만 둘의 어떤 선형결합은 정상이 되어 평균으로 회귀하는 관계를 말하며, 이는 두 계열 사이에 장기 균형이 존재한다는 진술이다. 실무 절차는 각 계열의 단위근을 검정하고, 공적분 관계를 추정한 뒤 그 잔차가 정상인지 검정하는 순서이며, 공적분이 성립하면 오차수정모형으로 단기 이탈이 균형으로 되돌아가는 속도를 모델링한다. 요약하면 상관은 동시적 움직임에 대한 진술이고 공적분은 장기 관계에 대한 진술이다.

페어 트레이딩, 헤지 비율 산정, 스테이블코인이나 LST의 페그 이탈 분석은 모두 두 계열의 수준이 장기적으로 붙어 있는지를 묻는 질문이라 상관이 아니라 공적분의 영역이다.

코드 · 수식

# 상관관계와 공적분(가볍게) — 수준(level) 상관의 함정 vs 스프레드의 평균회귀
# 두 계열이 각자 추세를 가지면 아무 관계가 없어도 수준끼리는 강하게 "상관"돼 보인다(허위회귀).

import random
import statistics

random.seed(3)
N = 500

def corr(xs, ys):
    mx, my = statistics.mean(xs), statistics.mean(ys)
    cov = sum((x - mx) * (y - my) for x, y in zip(xs, ys))
    sx = (sum((x - mx) ** 2 for x in xs)) ** 0.5
    sy = (sum((y - my) ** 2 for y in ys)) ** 0.5
    return cov / (sx * sy)

# 1) 서로 무관한 두 랜덤워크(각자 추세만 가짐) — 진짜 관계는 없다
a_level = [100.0]
b_level = [50.0]
for _ in range(N):
    a_level.append(a_level[-1] + random.gauss(0.15, 1.0))  # 독립적인 상승 추세(추세가 노이즈를 압도)
    b_level.append(b_level[-1] + random.gauss(0.10, 1.0))
a_ret = [a_level[i] - a_level[i - 1] for i in range(1, len(a_level))]
b_ret = [b_level[i] - b_level[i - 1] for i in range(1, len(b_level))]

print(f"[무관한 두 랜덤워크] 수준(level) 상관 = {corr(a_level, b_level):.3f}  (허위로 높게 나옴)")
print(f"[무관한 두 랜덤워크] 수익률(return) 상관 = {corr(a_ret, b_ret):.3f}  (실제로는 0에 가까움)")

# 2) 공적분 관계: 두 계열은 각자 비정상(추세)이지만 스프레드는 평균회귀하도록 구성
x_level = [100.0]
for _ in range(N):
    x_level.append(x_level[-1] + random.gauss(0.0, 1.0))

spread = [0.0]
for _ in range(N):
    # 스프레드가 커질수록 되돌아오는 힘(AR(1), 계수<1 => 평균회귀) + 노이즈
    spread.append(spread[-1] * 0.8 + random.gauss(0.0, 0.5))

y_level = [x_level[i] - spread[i] for i in range(N + 1)]

print(f"\n[공적분 쌍] 수준 상관 = {corr(x_level, y_level):.3f}")
print(f"[공적분 쌍] 스프레드 평균/표준편차 = {statistics.mean(spread):.3f} / {statistics.stdev(spread):.3f}")
print(f"[공적분 쌍] 스프레드가 [-3, 3] 범위 안에 머문 비율 = {sum(-3 <= s <= 3 for s in spread) / len(spread):.2%}")
print("-> 스프레드가 특정 범위를 벗어나지 않고 되돌아온다면 두 계열이 장기적으로 묶여있다는 신호(공적분).")

연습

두 자산의 가격 시계열을 받아 로그 가격 수준의 상관과 로그 수익률의 상관을 각각 계산해 값이 얼마나 다른지 비교하고, 두 계열의 스프레드가 평균으로 회귀하는지 그려 확인하라.

실무 · Verex 연결

같은 사건을 다루는 서로 다른 마켓이나 상보적 아웃컴 토큰의 가격은 장기적으로 묶여 있어야 하므로, 스프레드가 평균 회귀하지 않는다면 유동성 부족이나 정산 조건 차이를 의심할 근거가 된다.

공부한 날 원본 커리큘럼(docs/knowledge/math-50-curriculum.md)의 이 줄에 노트 링크와 ✅ 를 붙이면, 이 자리는 노트 본문으로 바로 이어집니다. 노트 없이 이 페이지에 바로 적어도 됩니다 — 다만 다시 생성하면 덮어쓰이므로, 남길 글은 docs/algorithms/ 의 마크다운으로 쓰는 편이 안전합니다.

← 42. 마르코프 체인(개념)44. 정수론·모듈러 산술 →