Synthetic Control Methods for Evaluating Trading Strategies
이 시리즈는 오랫동안 거짓된 엣지로 이어지는 한 가지 경로, 즉 선택을 다뤘습니다. 수정 샤프 비율은 검색에서 승자에게 가격을 매기고, 백테스트 과적합 확률은 검색 자체에 가격을 매깁니다. 정직한 부정적 결과는 실제로 거래하고 싶었던 전략에 둘 다 적용했을 때 일어나는 일입니다. 이 도구들은 한 가지 질문에 잘 답합니다. 너무 많이 들여다봐서 이걸 발견한 것은 아닐까?
하지만 이 도구들은 완전히 별개의 두 번째 경로인 혼란을 다루지 못합니다. 새로운 실행 알고리즘을 배포하거나, 매개변수를 바꾸거나, 특정 국면에 반응하는 등 한 가지만 바꿨는데 성과가 좋아졌다고 합시다. 검색도 없었고 다중 검정 문제도 없었습니다. 그런데도 개선이 당신과 아무 관련이 없을 수 있습니다. 배포한 주에 변동성이 두 배가 되었기 때문입니다. 디플레이션은 시행 횟수를 보정하고 당신은 한 번만 실행했으므로, 어떤 디플레이션도 이를 포착하지 못합니다.
Abadie와 Gardeazabal(2003)이 개발하고 Abadie, Diamond, Hainmueller(2010, 2015)가 발전시킨 합성 통제 방법(SCM)은 혼란을 직접 공격합니다. 이 방법은 반사실을 만듭니다. 즉, 당신이 건드리지 않은 도구들의 가중 조합을 구성하되, 개입 이전에 처리된 도구를 밀접하게 추적하도록 선택합니다. 개입 이후 두 경로의 차이가 효과 추정치입니다. 중요한 점은 SCM에 신뢰할 만한 반사실을 만들 수 없는 경우를 알려주는 위조 검증 기준과, 어떤 점근 이론도 믿을 필요가 없는 순열 기반 p값이 함께 제공된다는 것입니다.
명백한 대안이 실패하는 이유

개입 전후 비교는 개입과 그 날짜에 바뀐 모든 것을 뒤섞습니다. 단일 벤치마크 도구와 비교하는 것도 실패합니다. 어떤 단일 자산도 당신의 자산을 추적하지 못하기 때문입니다. 이것은 다른 방향에서 본 단일 도구 함정입니다. 한 도구의 고유 구조가 엣지를 속일 수 있다면, 한 도구의 고유 구조가 통제군을 속일 수도 있습니다. SCM의 기부자 풀은 바로 이 반론에 대한 형식적 답입니다. 비교 가능한 자산 하나를 고르는 대신, 개입 전 데이터가 가중 바스켓을 선택하게 합니다.
차분의 차분(DiD)은 거의 맞지만 놓치는 방법이므로 한 문장 이상 설명할 가치가 있습니다. DiD는 처리 단위의 변화와 통제군의 변화를 비교하며, 평행 추세 가정에서만 효과를 식별합니다. 개입이 없었다면 처리군과 통제군이 함께 움직였어야 한다는 뜻입니다. 금융시장에서는 이 가정을 방어하기 어렵습니다. 자산마다 베타, 변동성 국면, 유동성 계층이 다르고 상대 경로도 달라집니다. SCM은 가정을 적합된 객체로 대체합니다. 평행하게 움직이는 통제를 가정하는 대신 하나를 구성한 후 실제 추적 성능을 보여줍니다. 추적이 나쁘면 중단하라고 알려줍니다.
방법

설정과 표기
개 기간 동안 관측된 개 단위(자산, 전략 또는 포트폴리오)를 생각합니다. 단위 1은 개입한 자산 또는 전략인 처리 단위입니다. 단위 은 비교 가능한 미처리 단위의 기부자 풀을 이룹니다.
를 시점 에 개입이 있었을 때 처리 단위의 결과, 을 개입이 없었을 때의 결과라고 하겠습니다. 에서의 처리 효과는 다음과 같습니다.
는 직접 관측합니다. 전체 문제는 을 추정하는 것입니다.
합성 통제 구성
SCM은 을 기부자 결과의 가중평균으로 추정합니다.
가중치 벡터 는 모든 에 대해 이고 을 만족합니다.
이 두 제약은 실질적인 역할을 합니다. 합성 통제가 기부자들의 볼록 조합이 되도록 해 데이터가 지지하는 범위 밖으로 외삽하지 못하게 합니다. 제약 없는 회귀는 한 코인에 3.4배 롱, 다른 코인에 2.4배 숏을 구성한 반사실을 줄 수 있습니다. 개입 전 적합도는 수치상 훌륭하지만 누구도 보유할 수 없는 포트폴리오입니다. 볼록성 제약은 반사실을 해석 가능하게 하고 가중치를 희소하게 만듭니다. 대개 소수의 기부자만 0이 아닌 가중치를 가지므로 그 기부자를 읽어낼 수 있습니다.
최적 가중치 선택
가중치는 개입 전 기간에 처리 단위와 합성 통제 사이의 거리를 최소화하도록 선택합니다.
은 처리 단위의 개입 전 특성을 담은 벡터이고, 는 기부자 풀의 같은 특성을 담은 행렬입니다. 는 각 예측 변수의 중요도를 지정하는 양의 준정부호 대각 행렬입니다.
는 교차 검증으로 선택합니다. 결과 가중치 가 개입 전 기간의 결과 변수 평균제곱예측오차(MSPE)를 최소화하도록 를 고릅니다. 이는 중첩 최적화입니다. 바깥 루프는 를 탐색하고, 안쪽에서는 를 제약 조건하에 풉니다.
거래 응용을 위한 예측 변수
Abadie 등은 1인당 GDP와 산업 구성을 사용했지만, 거래 응용에서는 다음을 원합니다.
- 수익률 특성: 개입 전 기간의 평균 수익률, 변동성, 왜도, 첨도
- 유동성 측정치: 평균 스프레드, 일일 거래량, Amihud 비유동성 비율
- 미시구조 특성: 주문 흐름 불균형, 거래 도착률, 호가-거래 비율
- 요인 노출: 시장, 모멘텀, 가치, 변동성 요인에 대한 beta
- 시차 결과: 개입 전 여러 시점의 누적 PnL 값
시차 결과가 가장 중요합니다. Abadie 등(2010)은 합성 통제가 긴 개입 전 구간에서 처리 단위의 시차 결과를 맞추면 선형 요인 모형 아래에서 관측되지 않은 혼란 요인을 암묵적으로 통제한다는 것을 보였습니다. 이것이 이 방법을 번거롭게 적용할 가치가 있는 이유입니다. PnL을 움직인 혼란 요인의 이름을 알아낼 필요는 없습니다. 그 요인에 함께 노출된 기부자를 찾아야 합니다.
적용되는 곳

시나리오 1: 새로운 실행 알고리즘 평가
한 도구에 새로운 TWAP 스케줄을 배포하고 구현 부족에 미치는 효과를 알고 싶다고 합시다. 지표와 알고리즘 계열은 다른 글에서 자세히 다뤘습니다. 비용 분해는 구현 부족과 TCA, 스케줄러 자체는 TWAP/VWAP/POV 알고리즘을 참고하세요. SCM의 기여는 그 위에 놓이는 평가 계층입니다. 기부자 풀은 같은 기간에 구형 스케줄러를 계속 사용하는 동일한 도구들로 구성하므로, 거래소 전체의 스프레드나 거래량 변화가 처리 단위와 기부자 모두에 똑같이 작용합니다.
개입 전 기간: 전환 전 60거래일. 개입 후 기간: 전환 후 30거래일. 효과 추정치는 다음 차이입니다.
새 스케줄러가 실제로 비용을 줄였다면 이 이후 지속되어야 합니다. 더 중요한 것은 처리 단위의 차이가 기부자 차이의 구름 밖에 있어야 한다는 점입니다.
시나리오 2: 시장 이벤트의 영향
한 거래소에서 수수료 일정 변경, 최소 호가 단위 시범사업, 거래세 또는 서킷브레이커 규칙 변경이 발생했다고 합시다. 처리 단위는 영향을 받은 거래소에서의 전략이고, 기부자 풀은 변경이 닿지 않은 거래소의 같은 거래쌍입니다. 실행에 대한 수수료 구조 효과는 메이커-테이커 수수료와 리베이트에서 별도로 다룹니다. SCM은 “변경 후 체결이 나빠졌다”를 귀무가설이 붙은 추정치로 바꿉니다.
시나리오 3: 국면 조건부 전략 전환
국면 탐지 신호가 발생하면 한 자산에서 평균회귀 전략을 모멘텀 전략으로 바꿉니다. 전환하지 않고 유지하는 것보다 나았을까요? 기부자 풀은 신호가 발생했지만 전략을 바꾸지 않은 유사 자산입니다. 여기서 함정을 주의해야 합니다. 국면 신호가 시장 전체에서 발생하면 기부자도 처리되므로 설계가 무너집니다. 아래의 파급 규칙을 참고하세요.
위약 검정과 추론

SCM의 추론은 순열 기반입니다. 폐쇄형 분포를 믿는 대신 재표본추출로 경험적 귀무분포를 만드는 일반적 논리는 백테스트의 몬테카를로 및 부트스트랩 방법에 설명되어 있습니다. 다음은 SCM에 특화된 구성입니다.
시간 내 위약
개입 날짜를 개입 전 기간의 중간쯤으로 앞당겨 다시 실행합니다. 아무 일도 일어나지 않은 날짜에 SCM이 “처리 효과”를 찾는다면, 이는 개입이 아니라 기존의 분리를 포착한 것입니다. 신뢰할 만한 합성 통제는 가짜 개입 날짜에서 효과를 보여서는 안 됩니다.
공간 내 위약(순열 검정)
각 기부자를 차례로 처리 단위라고 가정하면서 전체 SCM 절차를 적용합니다.
- 각 기부자 에 대해 나머지 기부자와 원래 처리 단위를 사용해 합성 통제를 구성합니다.
- 개입 후 차이 를 계산합니다.
- 처리 단위의 차이를 위약 차이의 분포와 비교합니다.
p값은 순위입니다.
여기서
개입 후 원시 차이보다 이 비율이 올바른 통계량입니다. 한 번도 잘 맞지 않았던 기부자는 개입과 무관한 이유로 큰 개입 후 차이를 보일 수 있으며, 개입 전 MSPE로 나누면 바로 그런 경우를 벌점으로 처리합니다.
이는 수정 샤프 비율의 추론 방식과 같습니다. 후보를 0과 비교하지 않고 경험적으로 만든 기준 분포 안에서 순위를 매깁니다. DSR은 여러 시행의 최선-N 귀무분포에 대해 승자의 순위를 매기고, SCM은 단위에 대한 귀무분포에 처리 단위의 순위를 매깁니다. 추정량은 달라도 원칙은 같고 경고도 같습니다. 개 기부자라면 달성 가능한 최소 p값은 입니다. 순열 검정은 자체 해상도를 넘어설 수 없으므로, 기부자 풀이 작으면 효과가 아무리 커 보여도 주장할 수 있는 유의성이 제한됩니다.
실무 필터링
Abadie 등은 개입 전 적합도가 나쁜 위약 단위를 제외하라고 권합니다. 즉 처리 단위의 MSPE보다 배 이상 큰 경우이며, 보통 는 2에서 20 사이입니다. 적합도가 나쁜 위약은 분모가 작아 거대한 MSPE 비율을 만들고 기준 분포를 오염시켜 처리 단위를 평범해 보이게 합니다. 여러 임계값에서 비율을 보고하세요. 특정 하나에서만 p값이 유의하다면 효과가 아니라 조정 파라미터를 찾은 것입니다.
구현

아래 코드는 메커니즘이 잘 보이도록 numpy와 scipy만 사용합니다. 프로덕션 대안인 SparseSC(Microsoft Research), pysyncon, SyntheticControlMethods는 예외 상황을 처리하고 더 잘 확장됩니다.
예측 변수 행렬 구성
import numpy as np
import pandas as pd
from scipy.optimize import minimize
def build_predictors(series: pd.Series, T0: int) -> np.ndarray:
"""
Extract predictor vector from pre-intervention data.
Features: mean return, volatility, skewness, kurtosis,
plus lagged outcome values at regular intervals.
"""
pre = series.iloc[:T0]
daily_ret = pre.diff().dropna()
stats = [
daily_ret.mean(),
daily_ret.std(),
daily_ret.skew(),
daily_ret.kurtosis(),
]
lag_indices = np.linspace(0, T0 - 1, 5, dtype=int)
lags = pre.iloc[lag_indices].values.tolist()
return np.array(stats + lags)
여기서 df는 누적 수익률 패널입니다. 0열은 처리 단위, 나머지는 기부자이고 각 행은 하나의 bar입니다. 가격 수준이 아니라 누적 수익률을 사용하세요.
X1 = build_predictors(df["Treated"], T0).reshape(-1, 1) # (k x 1)
X0 = np.column_stack([ # (k x J)
build_predictors(df[col], T0) for col in df.columns[1:]
])
가중치 최적화
def solve_weights(
X1: np.ndarray,
X0: np.ndarray,
Y1_pre: np.ndarray,
Y0_pre: np.ndarray,
) -> np.ndarray:
"""
Solve for optimal synthetic control weights.
Nested optimization:
Outer: optimize V (predictor importance)
Inner: optimize w (donor weights) given V
"""
k = X1.shape[0]
J = X0.shape[1]
def solve_w_given_V(V_diag: np.ndarray) -> np.ndarray:
"""Inner: find w minimizing weighted predictor distance."""
V = np.diag(V_diag)
def objective(w):
gap = X1.flatten() - X0 @ w
return gap @ V @ gap
constraints = {"type": "eq", "fun": lambda w: np.sum(w) - 1.0}
bounds = [(0, 1)] * J
w0 = np.ones(J) / J
result = minimize(objective, w0, method="SLSQP",
bounds=bounds, constraints=constraints,
options={"maxiter": 1000, "ftol": 1e-12})
return result.x
def outer_objective(V_diag: np.ndarray) -> float:
"""Outer: minimize pre-period MSPE given V."""
w = solve_w_given_V(np.abs(V_diag))
return np.mean((Y1_pre - Y0_pre @ w) ** 2)
V0 = np.ones(k)
result = minimize(outer_objective, V0, method="Nelder-Mead",
options={"maxiter": 5000, "xatol": 1e-8})
return solve_w_given_V(np.abs(result.x))
Y1_pre = df["Treated"].iloc[:T0].values
Y0_pre = df.iloc[:T0, 1:].values
weights = solve_weights(X1, X0, Y1_pre, Y0_pre)
for col, w in zip(df.columns[1:], weights):
if w > 0.01:
print(f" {col}: {w:.4f}")
이후 차이 시계열은 df["Treated"].values - df.iloc[:, 1:].values @ weights입니다. 이를 개입 날짜와 함께 그리고, 아래 위약 실행에서 얻은 기부자 차이와 비교합니다.
위약 검정
def run_placebo_tests(df: pd.DataFrame, T0: int) -> pd.DataFrame:
"""In-space placebo: treat each unit in turn as if intervened upon."""
results = []
all_columns = df.columns.tolist()
for placebo_col in all_columns:
donor_cols = [c for c in all_columns if c != placebo_col]
donor_df = df[donor_cols]
X1_p = build_predictors(df[placebo_col], T0).reshape(-1, 1)
X0_p = np.column_stack([
build_predictors(donor_df[c], T0) for c in donor_cols
])
w_p = solve_weights(
X1_p, X0_p,
df[placebo_col].iloc[:T0].values,
donor_df.iloc[:T0].values,
)
gap_p = df[placebo_col].values - donor_df.values @ w_p
mspe_pre = np.mean(gap_p[:T0] ** 2)
mspe_post = np.mean(gap_p[T0:] ** 2)
results.append({
"unit": placebo_col,
"mspe_pre": mspe_pre,
"mspe_post": mspe_post,
"mspe_ratio": mspe_post / mspe_pre if mspe_pre > 1e-10 else np.inf,
"gap_series": gap_p,
"is_treated": placebo_col == "Treated",
})
return pd.DataFrame(results)
placebo_results = run_placebo_tests(df, T0)
treated_pre = placebo_results.loc[placebo_results["is_treated"], "mspe_pre"].values[0]
filtered = placebo_results[placebo_results["mspe_pre"] <= 5 * treated_pre]
treated_ratio = filtered.loc[filtered["is_treated"], "mspe_ratio"].values[0]
p_value = (filtered["mspe_ratio"] >= treated_ratio).sum() / len(filtered)
print(f"MSPE ratio (treated): {treated_ratio:.2f} p = {p_value:.3f}")
추정량 보정
실행할 가치가 있는 시연과 거부해야 할 시연이 있습니다. 합성 패널 데이터를 만들고 하루 +0.3% 효과를 주입한 뒤 대략 +0.3%/일을 회복해 증거로 제시하는 것은 SLSQP가 수렴한다는 것만 증명합니다. 순환 논리이므로 이 블로그는 이를 게시하지 않습니다.
합성 데이터가 유용한 것은 보정입니다. DSR 글과 PBO에서 적용한 기준과 같습니다. 알려진 귀무가설이 있어야 통계량을 신뢰할 수 있습니다. 실제로 가진 기부자 수와 개입 전 길이를 사용해 선형 요인 모형에서 생성한 데이터로 다음 두 가지를 모두 실행하세요.
- 알려진 효과 크기에서의 검정력. 알려진 크기의 효과를 주입합니다. 추정된 개입 후 차이가 이를 회복하는가, 오차는 얼마인가? 회복에 실패할 때까지 효과 크기를 줄입니다. 그 하한이 패널 구조가 탐지할 수 있는 최소 효과이며, 그보다 작은 실제 결과는 p값과 관계없이 노이즈입니다.
- 참인 귀무가설에서의 검정 크기. 아무것도 주입하지 않습니다. 전체 위약 파이프라인을 실행합니다. p값은 에서 대략 균일해야 하므로 반복 추출에서 약 10%가 0.10 아래에 있어야 합니다. 훨씬 자주 발생한다면 예측 변수나 필터 임계값이 유의성을 만들어내는 것이며, 실제 데이터에 닿기도 전에 파이프라인이 고장난 것입니다.
두 번째 검사는 사람들이 건너뛰지만 가장 중요합니다. 먼저 실행하세요.
실무 고려사항

기부자 풀 선택
기부자는 처리 단위와 그럴듯하게 비교 가능해야 하며 개입의 영향을 받지 않아야 합니다. 실행 작업이라면 유사한 도구에서 같은 알고리즘을 사용해야 합니다. 같은 섹터, 시가총액 분위, 유동성 계층이어야 합니다. 다중 심볼 검증의 계층 분류를 그대로 재사용할 수 있습니다. 도구를 공정한 견고성 테스트로 만드는 속성이 공정한 기부자 풀로도 만듭니다. 거래소 이벤트라면 변경이 닿지 않은 거래소의 같은 도구를 사용합니다.
파급이 있는 단위를 제외하세요. 한 도구의 알고리즘을 바꿔 상관된 도구에 대한 호가도 바뀌었다면 그 도구들은 부분적으로 처리된 것입니다. 이를 기부자 풀에 남기면 합성 통제가 처리 단위의 개입 후 경로 쪽으로 치우치고 추정치를 0으로 축소합니다. 이는 나쁜 통제가 양의 결과를 만드는 것만큼 쉽게 귀무 결과를 만들어내는 설계 결함입니다.
개입 전 적합도가 전체 논거다
SCM의 신뢰성은 전적으로 개입 전 적합도에 달려 있습니다. 개입 전 MSPE를 보고하고 적합도를 보여주세요. 개입 전 적합도가 나쁘면 이후의 모든 것이 무효입니다.
Abadie 등(2015)은 규칙을 분명히 했습니다. 어떤 기부자의 볼록 조합도 처리 단위의 개입 전 경로를 재현하지 못한다면 이 방법을 적용하지 마세요. 이것은 정직한 위조 검증 기준이지만 실제로는 그만큼 자주 사용되지 않습니다. 대부분의 추정량은 무엇을 넣든 숫자를 반환하기 때문입니다. SCM은 신뢰할 만한 반사실을 만들 수 없을 때 알려줍니다. 그 메시지에 대한 올바른 대응은 실패를 게시하는 것이지, 적합도가 좋아질 때까지 기부자 풀을 넓히는 것이 아닙니다. 개입 전 적합도를 좇아 풀을 넓히는 것은 이 시리즈가 다루는 같은 과적합을 통제 구성 단계로 옮긴 것에 불과합니다.
짧은 패널의 과적합
SCM에서 주시할 값은 기부자 수 와 개입 전 관측치 수 의 비율입니다. 개 점에 개의 자유 가중치를 맞출 때 가 에 가까워지면 구성만으로 완벽한 개입 전 적합이 가능해지며, 그렇게 얻은 완벽한 적합은 아무 정보도 주지 않습니다. 를 보다 충분히 크게 유지하세요. 변경 전 전략이 30일 동안 실행되었고 후보 기부자가 20개라면 SCM 문제가 아니라 보간 문제입니다. 완화책은 다음과 같습니다.
- 정규화: SparseSC는 가중치 최적화에 및 페널티를 추가합니다.
- 기부자 풀 제한: 더 적고 근거가 확실한 기부자가 더 많은 기부자보다 낫습니다. 단, 가 최소 p값도 정하므로 순열 격자가 거칠어지는 대가가 있습니다. 이 긴장은 실제이며 깔끔한 해법이 없습니다.
- 증강 SCM: Ben-Michael, Feller, Rothstein(2021)은 완벽한 개입 전 적합이 불가능할 때 결과 모형을 사용해 편향을 보정합니다.
비정상성
SCM은 가격 수준이 아니라 누적 수익률에서 실행하세요. 가격 수준은 허위 적합을 부릅니다. 개입 후 분리를 믿기 전에 개입 전 차이가 평균 0이고 정상인지 확인하세요. 관측 시계열이 아닌 적합 잔차를 검정할 때 필요한 보정 임계값을 포함한 ADF와 Engle-Granger 도구는 통계적 차익거래와 페어 트레이딩에 설명되어 있습니다.
여러 처리 단위
여러 자산에 동시에 배포하면 단일 처리 단위 설계가 깨집니다. 확장으로는 풀링 SCM(처리 단위 전체의 평균 효과)과 서로 다른 시점에 처리된 단위를 위한 시차 도입 설계(Ben-Michael 등, 2022)가 있습니다.
SCM을 사용하지 말아야 할 때

적합한 경우는 알려진 날짜의 불연속적이고 명확한 개입, 비교 가능한 미처리 단위로 된 합리적인 기부자 풀, 그리고 사전에 예상되지 않은 개입입니다. 예상은 이전에 처리 단위를 움직여 식별을 위반하기 때문입니다.
부적합한 경우는 수주에 걸쳐 움직이는 매개변수 같은 연속 처리, 진정한 미처리 비교 대상이 없는 환경, 신호대잡음비가 가중치 추정을 압도하는 고빈도 평가입니다.
결론

선택과 혼란은 서로 다른 실패 모드이므로 서로 다른 도구가 필요합니다. DSR과 PBO는 검색에 가격을 매깁니다. SCM은 반사실에 가격을 매기며, 이 시리즈에서 정확히 한 번의 실험을 실행했는데도 결과를 믿을 수 없는 상황을 다루는 유일한 도구입니다.
임의의 벤치마크보다 SCM을 선호하는 이유는 더 큰 효과를 내기 때문이 아닙니다. 오히려 더 작은 효과를 자주 냅니다. SCM은 거부할 수 있기 때문입니다. 개입 전에 당신의 도구를 추적하는 기부자의 볼록 조합이 없다면 SCM이 그렇게 말하고 멈춥니다. 처리 차이가 기부자 구름 안에 있으면 위약 p값이 그렇게 말하고 귀무 결과를 보고합니다. 이 두 가지 종료 지점이 방법의 가치입니다. 보고된 개입 전 MSPE와 위약 p값이 없는 SCM 보고서는 두 선이 벌어지는 차트를 넘어서는 유일한 부분을 건너뛴 것입니다.
추가 읽을거리
- Abadie, A., Diamond, A., & Hainmueller, J. (2010). "비교 사례 연구를 위한 합성 통제 방법." Journal of the American Statistical Association, 105(490), 493-505.
- Abadie, A. (2021). "합성 통제 사용: 실행 가능성, 데이터 요구 사항 및 방법론적 측면." Journal of Economic Literature, 59(2), 391-425.
- Ben-Michael, E., Feller, A., & Rothstein, J. (2021). "증강 합성 통제 방법." Journal of the American Statistical Association, 116(536), 1789-1803.
- Cunningham, S. (2021). 인과 추론: The Mixtape. 10장: 합성 통제. mixtape.scunning.com에서 볼 수 있습니다.
- Microsoft Research. SparseSC: 희소 합성 통제. github.com/microsoft/SparseSC.
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.