가족 바깥에서, 경쟁사와 옵션의 교집합 (월드퀀트 브레인 5편)
퀀트 트레이딩 플랫폼 WorldQuant BRAIN에서 제출 가능한 알파를 쌓아 점수를 올리는 공개 기록. 4편에서 현금흐름 회귀(pcf)에 애널리스트 전망을 곱해 두 번째 알파를 만들고 브론즈에 올랐다. 그런데 그 곱셈 가족은 첫 번째와 두 번째로 이미 포화였다. 세 번째는 가족 바깥, 가격도 애널리스트도 아닌 완전히 다른 신호에서 찾아야 했다. 이번 편은 그 사냥의 기록이다.
0. 한 줄 요약
스물여섯 번의 시뮬레이션 끝에, “경쟁사들이 강세면 본사도 따라 오른다”와 “옵션시장이 그 종목의 상승에 베팅 중이다”의 교집합이 세 번째 알파가 됐다. 첫 두 알파와의 자기상관이 0.14, 거의 완전한 독립이다. 그 과정에서 멀쩡해 보이던 가설 다섯 개가 죽었고, 부호 하나가 뒤집히며 살아났고, 도구의 문법 함정 하나에 네 개가 한꺼번에 헛돌았다.
1. 출발점: 가족이 포화됐다
첫 번째 알파(pcf)는 “현금흐름 대비 주가가 자기 역사보다 비싸졌나”였다. 두 번째(cau)는 거기에 “애널리스트가 전망을 올리는 중인가”를 곱한 것이었다. 둘 다 분자에 시가총액(cap), 즉 주가가 들어간다. 4편 끝에서 깨달은 게 이거였다. pcf, pb(주가/순자산), EV 배수 회귀, 그리고 그것들에 애널리스트 신호를 곱한 변형까지, 전부 주가를 공유하는 한 가족이라 서로 닮았다. 첫 번째와 두 번째로 그 가족은 포화됐고, 같은 가족에서 세 번째를 만들면 자기상관에 막힌다.
자기상관 규칙은 이렇다. 새 알파는 내가 이미 제출한 알파들과 너무 닮으면 안 된다(0.7 미만). 그러니 세 번째는 주가도, 애널리스트 전망 변화도 쓰지 않는, 메커니즘 자체가 다른 신호여야 했다. 문제는 그런 “다른” 신호일수록 혼자서는 약하다는 것. 이번 사냥의 벽이 처음부터 분명했다.
2. 서른여섯 개의 가설을 여섯 갈래로 흩뿌리다
혼자 머리로 후보를 짜는 대신, 가설 생성을 병렬로 돌렸다. 여섯 개의 탐색 축을 정하고, 축마다 에이전트 하나씩을 붙여 동시에 후보를 설계하게 했다. 펀더멘털 시계열 회귀(주가 없이), 회계 발생액, 옵션 변동성, 애널리스트와 뉴스, 기업 관계 그래프, 그리고 곱셈 조합. 각 에이전트에는 지금까지의 전장 기록을 다 줬다. 무엇이 죽었고(횡단면 밸류 랭크, 등가중 합성), 무엇이 살아 있고(시계열 회귀, 곱셈), 어떤 게 함정인지(작은 유니버스 붕괴).
여섯 에이전트가 각자 여섯 개씩, 서른여섯 개의 후보를 만들었다. 마지막에 종합 에이전트가 중복을 걷어내고 우선순위 열다섯 개로 추렸다. 여기서도 원칙은 같았다. 가설은 병렬로 흩뿌리되, 실제 시뮬레이션은 직렬로. 이 계정은 한 번에 시뮬을 딱 하나만 돌릴 수 있어서, 후보별로 세팅(평활, 중립화, 비중 상한, 유니버스)을 달리 먹이는 직렬 러너를 만들어 한 줄로 측정했다.
3. 주가를 빼니 신호도 죽었다
가장 기대했던 축은 “주가 없는 순수 펀더멘털 회귀”였다. 논리는 단순했다. pcf가 강했던 건 시계열 회귀라는 형태 덕분이니, 주가를 빼고 회사의 품질 비율만 같은 형태로 회귀시키면 직교하면서도 강할 것이다. 자기자본이익률(ROE)이 자기 역사보다 높아진 종목을 숏으로 잡아봤다.
결과는 Sharpe 마이너스 0.45. 부호가 정반대였다. 자산회전율(매출÷자산)로 바꿔도 마이너스 0.12. 둘 다 죽었다. 교훈이 아팠다. 주가를 빼는 순간 신호도 같이 죽었다. pcf가 강했던 진짜 이유는 시계열 회귀라는 형태가 아니라, 분자의 주가가 매일 출렁이며 신호를 만들어 줬기 때문이다. 품질 지표는 분기에 한 번 갱신되는 끈끈한 숫자라, 자기 역사와 비교해도 거의 평평했다. 게다가 품질은 평균회귀가 아니라 모멘텀에 가깝다. 좋은 회사는 계속 좋다. 그래서 “비싸진 품질을 숏”이 손해였다. 회계 발생액도 Sharpe 0.3으로 약했다.
4. 가장 유망했던 둘이 죽었다
종합 에이전트가 1순위와 2순위로 꼽은 건 옵션과 실적 서프라이즈였다.
- 옵션의 분산위험프리미엄(
option8): 내재변동성(시장이 예상하는 미래 변동성)에서 실현변동성(실제 과거 변동성)을 뺀 값이 자기 역사보다 벌어졌나를 회귀로 봤다. 입력에 주가가 한 글자도 없어 직교는 보장된 카드였다. - 실적 서프라이즈(
analyst4): 실제 발표된 분기 주당순이익에서 애널리스트 기대치를 뺀 깜짝 실적을, 추정치들의 흩어짐으로 나눠 표준화했다. 대형주에서도 살아남는 드문 아노말리(실적 발표 후 표류, PEAD)라 기대가 컸다.
둘 다 죽었다. 옵션 회귀 마이너스 0.17, 실적 서프라이즈 마이너스 0.1. 직교성은 완벽했지만 신호의 강도가 없었다. 이전 세션에서 얻은 결론, “닮지 않은 신호는 약하다”가 또 한 번 그대로였다. 닮지 않은 신호는 많은데, 닮지 않은 신호는 약했다.
5. 부호 하나가 뒤집힌 순간
거의 모든 직교 후보가 무너지던 와중에, 하나가 눈에 띄게 강한 음수를 냈다. 기업 관계 그래프의 경쟁사 신호였다. Sharpe 마이너스 0.99.
pv13이라는 데이터셋에는 한 회사를 둘러싼 경쟁사, 고객사, 협력사의 평균 수익률이 들어 있다. 나는 처음에 “경쟁사가 강하면 본사는 점유율을 빼앗겨 약해진다”는 점유율 잠식 가설로 부호를 음수로 잡았다. 그게 마이너스 0.99로 나왔다는 건, 가설이 정확히 거꾸로였다는 뜻이다. 부호를 뒤집자 플러스 0.99.
해석이 바뀌었다. 경쟁사가 강하면 본사도 따라 오른다. 점유율 싸움이 아니라 정보의 확산이었다. 같은 산업의 동료들이 먼저 움직이면 그 호재나 악재가 본사로 번지는 데 며칠이 걸리고, 그 시차를 타는 것이다. 이게 이번 사냥에서 처음으로 만난, 강하면서 진짜로 직교하는 신호였다. 주가도 펀더멘털도 아닌, 순전히 “남들의 수익률”로만 만든 신호. 다만 0.99는 제출 기준(1.25)에는 못 미쳤다.
6. 더하지 말고 곱하라, 다시
4편의 교훈을 다시 꺼냈다. 약한 신호는 더하면 물타기, 곱하면 교집합이다. 경쟁사 신호(0.99)를 축으로 삼고, 직교하는 다른 신호를 곱해 교집합을 강화하기로 했다. 곱할 짝으로 옵션을 골랐다.
옵션에는 콜과 풋의 내재변동성이 따로 있다(option8). 콜의 내재변동성이 풋보다 비싸다면, 정보를 가진 거래자들이 상승에 베팅하느라 콜을 사들이고 있다는 신호로 읽는다. 풋콜 패리티라는 균형에서 벗어난 만큼이 방향성 정보다. 경쟁사 신호와 이 옵션 신호를 각각 종목 순위로 바꾼 뒤 곱했다.
rank(경쟁사 수익률 모멘텀) × rank(콜 내재변동성 - 풋 내재변동성)
곱셈이 노리는 교집합은 이렇다. 같은 산업 동료들이 먼저 오르고 있고(확산), 동시에 옵션시장의 똑똑한 돈이 그 종목의 상승에 베팅 중인(정보) 종목만 롱. 두 줄기의 정보가 한 종목에서 만나는 자리.
결과는 Sharpe 1.70. 0.99짜리 신호가 곱셈으로 1.70까지 뛰었다. 4편에서 1.57을 1.91로 올렸던 그 패턴이 또 작동했다. 그리고 양쪽 다리 어디에도 주가나 애널리스트 전망 변화가 없으니, 첫 두 알파와는 메커니즘이 완전히 달랐다.
문제는 하나. Fitness가 0.83으로 제출 기준(1.0) 아래였다. 옵션 다리가 매일 출렁여서 턴오버가 28.6%로 높았고, 턴오버가 높으면 Fitness가 깎인다.
7. 옵션 다리를 다듬어 문을 넘다
Fitness 공식은 Sharpe × sqrt(수익 / max(턴오버, 0.125))다. 턴오버가 12.5% 위에 있으면, 턴오버를 깎을수록 Fitness가 오른다. 지금은 28.6%라 깎을 여지가 충분했다. 신호의 방향은 그대로 두고 속도만 늦추면 된다.
빠른 쪽은 옵션 다리였다. 콜풋 내재변동성 차이를 15거래일 이동평균으로 부드럽게 폈다. 경쟁사 다리는 이미 21일 평균이라 느렸으니, 빠른 다리만 다듬은 것이다. 턴오버가 28.6%에서 14.2%로 떨어졌고, Sharpe는 1.58로 거의 유지됐다. Fitness는 1.01. 기준을 넘었다.
서버의 여덟 개 문을 전부 받았다. 전부 통과였다. 특히 두 개가 좋았다.
- 작은 유니버스 Sharpe 1.24. 3편에서 옵션 알파(cremers)가 1.83이라는 화려한 숫자를 내고도 이 문에서 0.59로 떨어져 폐기됐었다. 거래가 덜한 소수 종목에 edge가 몰려 있었기 때문이다. 그런데 이번엔 작은 유니버스에서도 1.24로 견고했다. 경쟁사 다리가 신호를 대형주로 넓게 펴 준 덕분에, 옵션만 썼다면 빠졌을 함정을 피했다.
- 자기상관 0.138. 첫 두 알파와 거의 완전히 독립이다. 0.7 기준은 말할 것도 없고, 상위 풀 기준인 0.5도 한참 밑이다. 점수는 자기상관이 낮을수록 품질 보너스가 붙으니, 숫자 자체로도 값지다.
rank(winsorize(ts_mean(ts_backfill(rel_ret_comp, 5), 21), std=4))
× rank(ts_mean(ts_backfill(implied_volatility_call_30 - implied_volatility_put_30, 5), 15))
USA / TOP3000 / 지연 1일 / decay 6 / 업종 중립화 / 비중상한 2%
→ Sharpe 1.58, Fitness 1.01, 턴오버 14.2%, 작은 유니버스 1.24, 자기상관 0.138, 8/8 통과
세 번째 알파다. 가족 바깥에서, 남들의 수익률과 옵션시장의 베팅이 만나는 자리에서 찾았다.
8. 작은 함정 하나: 콤마 대신 이름
이번 사냥에서 가장 어이없던 순간은 따로 있었다. 첫 배치에서 가장 유망한 후보 네 개가 한꺼번에 “입력이 2개인데 1개여야 한다”는 에러로 헛돌았다. 한참을 들여다보니, 죽은 넷의 공통점은 전부 winsorize(극단치를 잘라내는 함수)를 쓴다는 것이었다. 살아남은 셋엔 그게 없었다.
원인은 문법이었다. 이 플랫폼에서 winsorize의 두 번째 인자는 위치로 넘기는 값이 아니라 이름을 붙여 넘기는 파라미터였다. winsorize(x, 4)가 아니라 winsorize(x, std=4). 콤마로 그냥 넘기면 입력이 둘로 잡혀 에러가 났다. 이름 하나 붙이자 넷이 다 살아났다. 그 넷 중에 경쟁사 신호가 있었으니, 이 사소한 문법 하나에 세 번째 알파가 묻힐 뻔했다. 도구의 방언을 모르면 멀쩡한 가설도 죽은 것처럼 보인다.
9. 이번 편에서 배운 것
- 신호의 엔진이 어디 있는지 보라. pcf가 강했던 건 형태가 아니라 분자의 주가였다. 주가를 빼자 같은 형태로도 신호가 죽었다. 무엇을 회귀시키느냐가 형태보다 먼저다.
- 직교는 공짜가 아니다. 닮지 않은 신호는 거의 다 약했다(옵션 회귀, 실적 서프라이즈, 순수 펀더멘털). 강하면서 직교한 건 서른여섯 중 하나, 경쟁사 확산뿐이었다.
- 부호를 의심하라. 강한 음수는 죽은 게 아니라 거꾸로 본 것일 수 있다. 마이너스 0.99가 부호를 뒤집자 세 번째 알파의 씨앗이 됐다.
- 곱셈은 두 번 통했다. 약한 직교 신호를 검증된 신호에 곱해 교집합을 만들면 Sharpe가 오른다. 0.99가 1.70이 됐다.
- 빠른 다리만 다듬어라. Fitness가 턴오버에 막힐 때, 신호 전체를 뭉개지 말고 가장 빠른 다리만 평활하면 방향을 지키며 턴오버를 깎을 수 있다.
- 도구의 방언을 익혀라.
winsorize(x, std=4). 이름 하나로 네 개의 가설이 살았다.
세 번째 알파는 첫 두 개와 거의 무상관이라, 점수판에서 특히 값지다. 다음 편은 이 직교 알파들을 모아 실버를 향해 점수를 쌓는 그라인드, 그리고 자기상관 0.5 아래 알파들로만 들어갈 수 있는 상위 풀 이야기가 될 것이다.
기술 메모 (부록)
- 제출 후보 3호 (comp_x_ivspread, id GrwKjdP5):
rank(winsorize(ts_mean(ts_backfill(rel_ret_comp, 5), 21), std=4)) * rank(ts_mean(ts_backfill(implied_volatility_call_30 - implied_volatility_put_30, 5), 15)), USA/TOP3000/delay1/decay6/INDUSTRY/truncation0.02. Sharpe 1.58, Fitness 1.01, turnover 0.1425, returns 0.0584, sub-universe 1.24, self-corr 0.138. 서버 8/8 PASS. (버퍼 변형 ivsm15+decay9 id 2r7XgJRZ도 8/8, F1.02/S1.52/sub1.19. Sharpe와 sub가 더 높은 GrwKjdP5 채택.) 제출은 IP 귀속 게이트라 UI에서 직접. - 데이터 출처:
rel_ret_comp(pv13, 경쟁사 평균 수익률, 관계 그래프),implied_volatility_call_30/implied_volatility_put_30(option8, 30일 콜/풋 내재변동성). 둘 다 주가 비율도 애널리스트 추정치도 아니라 1, 2호와 직교. - 곱셈의 의미:
rank(a) * rank(b)는 두 신호가 동시에 높은 종목만 살리는 교집합(AND). 더하기(a + b)는 한쪽만 높아도 베팅하는 합집합(OR)이라 약신호가 잡음을 보탠다. 경쟁사 0.99 × 옵션 → Sharpe 1.70. - Fitness 리프트: Fitness = Sharpe × sqrt(|Returns| / max(Turnover, 0.125)). 빠른 옵션 다리만 15일 이동평균으로 평활 → turnover 0.286 → 0.142, Sharpe 1.70 → 1.58 유지, Fitness 0.83 → 1.01. 턴오버가 free floor(0.125)에 닿으면 더는 안 오르므로 Fitness는 거기서 Sharpe에 묶인다.
- 죽은 후보들(우리 USA TOP3000 delay1 환경): ROE 회귀 S-0.45, 자산회전 회귀 S-0.12, 발생액 S0.3, 옵션 IV-HV 회귀 S-0.17, 실적 서프라이즈(분산표준화) S-0.1, 경쟁사 점유율잠식(부호 음) S-0.99. 부호 플립한 경쟁사 확산 S+0.99가 곱셈의 베이스가 됨.
- winsorize 문법: 두 번째 인자는 명명 파라미터.
winsorize(x, std=4)가 맞고winsorize(x, 4)는 “Invalid number of inputs: 2, should be exactly 1”로 실패. 같은 패턴의 후보 4개가 이 한 글자에 헛돌았다. - 워크플로: 6축(펀더멘털회귀, 발생액, 옵션, 애널리스트/뉴스, 관계lead-lag, 곱셈콤보) 병렬 팬아웃 → 36후보 생성 → 종합 dedup/랭킹 → shortlist 15(must 8). 시뮬은 동시 한도 1이라 직렬. 이번 세션 시뮬 약 26회.
- 점수 맥락: 자기상관은 0.7 게이트뿐 아니라 점수 Quality Factor에도 연속 영향(낮을수록 이득), 상위 풀(Power Pool)은 0.5 이하 요구. 3호의 0.138은 양쪽 다 여유로 충족.