Skip to content

Split View: 코어-새틀라이트 ETF 자동 리밸런싱

✨ Learn with Quiz
|

코어-새틀라이트 ETF 자동 리밸런싱

코어-새틀라이트 ETF 자동 리밸런싱

코어-새틀라이트 전략이란

코어-새틀라이트(Core-Satellite)는 포트폴리오를 두 가지 역할로 나누는 자산 배분 전략이다. **코어(Core)**는 시장 전체를 추종하는 저비용 인덱스 ETF로 구성해 안정적인 수익을 추구하고, **새틀라이트(Satellite)**는 특정 섹터, 테마, 스타일 ETF로 구성해 초과 수익(알파)을 노린다.

이 전략은 100% 인덱스 투자와 100% 액티브 투자의 중간점이다. David Swensen(예일대 기금 운용)이 Unconventional Success에서 제안한 개인 투자자용 자산 배분의 핵심이기도 하다.

코어-새틀라이트의 기본 원칙:

  • 코어 비중 60-80%, 새틀라이트 비중 20-40%
  • 코어는 거의 손대지 않는다 (연 1-2회 리밸런싱)
  • 새틀라이트는 시장 상황에 따라 교체 가능 (분기 단위 검토)
  • 전체 포트폴리오 보수율(TER)을 0.3% 이하로 유지

코어와 새틀라이트의 역할 분리

코어: 시장 베타를 확보한다

코어의 목표는 단순하다. 시장 수익률을 가장 낮은 비용으로 얻는 것이다. "시장을 이기려" 하지 않는다.

코어 ETF 선정 기준:

  1. 운용 보수 0.1% 이하 (보수는 확실한 마이너스 수익)
  2. 순자산 1,000억원 이상 (유동성 확보)
  3. 추적 오차(Tracking Error) 최소 (지수와 괴리 적음)
  4. 배당 재투자 구조 (TR 지수 추종 우선)

한국에서 쓸 수 있는 코어 ETF 후보 (2026년 기준):

ETF종목코드추종 지수보수순자산용도
TIGER 미국S&P500360750S&P 5000.07%6.5조원미국 대형주
KODEX 미국S&P500TR379800S&P 500 TR0.05%3.2조원미국 대형주 (배당 재투자)
KODEX 200069500KOSPI 2000.05%5.8조원한국 대형주
TIGER 미국나스닥100133690NASDAQ-1000.07%4.1조원미국 기술주
KODEX 미국채울트라30년선물(H)304660US Treasury 30Y0.09%2,800억원미국 장기채
TIGER 미국채10년선물305080US Treasury 10Y0.09%1.2조원미국 중기채
KODEX 종합채권(AA-이상)액티브443160국내 우량 채권0.05%3,500억원국내 채권

새틀라이트: 테마와 알파를 추구한다

새틀라이트는 특정 섹터, 지역, 스타일에 집중 투자해 시장 대비 초과 수익을 노린다. 코어와 달리 교체가 가능하며, 확신이 없으면 비중을 줄이거나 현금으로 유지할 수 있다.

새틀라이트 ETF 유형:

유형예시특징위험도
섹터반도체, 2차전지, 바이오특정 산업에 집중높음
테마AI, 로봇, 클린에너지성장 트렌드 추종높음
지역인도, 일본, 베트남특정 국가 성장중간-높음
스타일고배당, 가치주, 소형주팩터 기반 투자중간
대체자산금, 원자재, 리츠상관관계 낮음, 분산 효과중간

새틀라이트 교체 판단 기준:

  • 투자 논리(thesis)가 여전히 유효한가?
  • 지난 분기 벤치마크 대비 성과는?
  • 더 낮은 보수의 유사 ETF가 출시되었는가?
  • 포트폴리오 내 상관관계가 과도하게 높아지지 않았는가?

실전 포트폴리오 설계: 3가지 시나리오

시나리오 1: 안정 성장형 (보수적 투자자)

# 목표: 연 6-8% 수익, 최대 낙폭 15% 이내
# 대상: 은퇴 10-15년 전, 원금 보존 중시
portfolio:
  name: '안정 성장형'
  core_weight: 0.80
  satellite_weight: 0.20

  core:
    - ticker: 'KODEX 미국S&P500TR'
      weight: 0.30
      role: '미국 시장 베타'
    - ticker: 'KODEX 200'
      weight: 0.15
      role: '한국 시장 베타'
    - ticker: 'TIGER 미국채10년선물'
      weight: 0.20
      role: '금리 하락 시 방어'
    - ticker: 'KODEX 종합채권(AA-이상)액티브'
      weight: 0.15
      role: '안정적 이자 수익'

  satellite:
    - ticker: 'TIGER 금은선물(H)'
      weight: 0.10
      role: '인플레이션 헤지'
    - ticker: 'TIGER 미국배당다우존스'
      weight: 0.10
      role: '배당 수익'

  expected:
    annual_return: '6-8%'
    max_drawdown: '-15%'
    total_expense_ratio: '0.07%'

시나리오 2: 성장 추구형 (30대 직장인)

# 목표: 연 10-12% 수익, 최대 낙폭 25% 감수
# 대상: 투자 기간 15년 이상, 적극적 성장 추구
portfolio:
  name: '성장 추구형'
  core_weight: 0.65
  satellite_weight: 0.35

  core:
    - ticker: 'TIGER 미국S&P500'
      weight: 0.35
      role: '미국 시장 베타'
    - ticker: 'TIGER 미국나스닥100'
      weight: 0.15
      role: '기술주 성장'
    - ticker: 'KODEX 200'
      weight: 0.10
      role: '한국 시장 베타'
    - ticker: 'TIGER 미국채10년선물'
      weight: 0.05
      role: '최소 채권 배분'

  satellite:
    - ticker: 'TIGER 반도체'
      weight: 0.12
      role: '반도체 사이클 알파'
    - ticker: 'KODEX 2차전지산업'
      weight: 0.08
      role: '2차전지 성장'
    - ticker: 'TIGER Fn반도체TOP10'
      weight: 0.08
      role: '한국 반도체 집중'
    - ticker: 'TIGER 인도니프티50'
      weight: 0.07
      role: '인도 시장 성장'

  expected:
    annual_return: '10-12%'
    max_drawdown: '-25%'
    total_expense_ratio: '0.15%'

시나리오 3: 올웨더형 (변동성 최소화)

# 목표: 어떤 시장 환경에서도 양의 수익 추구
# 대상: 변동성을 극도로 싫어하는 투자자
# 참고: Ray Dalio의 All Weather 전략 변형
portfolio:
  name: '올웨더형'
  core_weight: 0.85
  satellite_weight: 0.15

  core:
    - ticker: 'TIGER 미국S&P500'
      weight: 0.25
      role: '경제 성장기 수익'
    - ticker: 'TIGER 미국채10년선물'
      weight: 0.25
      role: '경기 침체 방어'
    - ticker: 'KODEX 미국채울트라30년선물(H)'
      weight: 0.15
      role: '디플레이션 방어'
    - ticker: 'KODEX 종합채권(AA-이상)액티브'
      weight: 0.10
      role: '안정적 이자'
    - ticker: 'TIGER 금은선물(H)'
      weight: 0.10
      role: '인플레이션 헤지'

  satellite:
    - ticker: 'KODEX 200'
      weight: 0.10
      role: '한국 시장 노출'
    - ticker: 'TIGER 원유선물Enhanced(H)'
      weight: 0.05
      role: '원자재 인플레이션 대응'

  expected:
    annual_return: '5-7%'
    max_drawdown: '-10%'
    total_expense_ratio: '0.09%'

자동 리밸런싱 구현

코어 vs 새틀라이트 분리 리밸런싱

코어와 새틀라이트는 리밸런싱 주기와 기준이 다르다. 하나의 규칙으로 묶지 않는다.

"""
코어-새틀라이트 분리 리밸런싱 엔진
- 코어: 연 2회 + 편차 5% 초과 시 긴급
- 새틀라이트: 분기 1회 + 편차 7% 초과 시 긴급
"""
from dataclasses import dataclass


@dataclass
class RebalanceRule:
    """자산 유형별 리밸런싱 규칙."""
    section: str            # "core" or "satellite"
    scheduled_frequency: str  # "semi-annual" or "quarterly"
    drift_threshold: float    # 편차 임계치
    min_trade_krw: int        # 최소 거래 금액


RULES = {
    "core": RebalanceRule(
        section="core",
        scheduled_frequency="semi-annual",
        drift_threshold=0.05,
        min_trade_krw=200_000,
    ),
    "satellite": RebalanceRule(
        section="satellite",
        scheduled_frequency="quarterly",
        drift_threshold=0.07,
        min_trade_krw=100_000,
    ),
}


def check_section(
    section: str,
    holdings: dict,
    targets: list[dict],
    total_value: int,
) -> dict:
    """특정 섹션의 리밸런싱 필요 여부를 판단한다."""
    rule = RULES[section]
    drift_alerts = []

    for target in targets:
        code = target["code"]
        current_value = holdings.get(code, {}).get("value_krw", 0)
        current_weight = current_value / total_value if total_value > 0 else 0
        target_weight = target["weight"]
        drift = abs(current_weight - target_weight)

        if drift > rule.drift_threshold:
            drift_alerts.append({
                "ticker": target["ticker"],
                "code": code,
                "target": target_weight,
                "current": round(current_weight, 4),
                "drift": round(drift, 4),
                "action_needed": True,
            })

    return {
        "section": section,
        "rule": rule,
        "needs_rebalancing": len(drift_alerts) > 0,
        "alerts": drift_alerts,
    }


def run_core_satellite_rebalance(portfolio_config: dict, holdings: dict):
    """코어와 새틀라이트를 분리하여 각각 리밸런싱을 실행한다."""
    total_value = sum(h["value_krw"] for h in holdings.values())

    print(f"총 자산: ₩{total_value:,}\n")

    for section in ["core", "satellite"]:
        targets = portfolio_config[section]
        result = check_section(section, holdings, targets, total_value)

        section_label = "코어" if section == "core" else "새틀라이트"
        print(f"=== {section_label} 점검 ===")
        print(f"리밸런싱 주기: {result['rule'].scheduled_frequency}")
        print(f"편차 임계치: ±{result['rule'].drift_threshold * 100}%")

        if result["needs_rebalancing"]:
            print(f"상태: 리밸런싱 필요")
            for alert in result["alerts"]:
                print(
                    f"  - {alert['ticker']}: "
                    f"목표 {alert['target']*100:.1f}% / "
                    f"현재 {alert['current']*100:.1f}% / "
                    f"편차 {alert['drift']*100:.1f}%p"
                )
        else:
            print("상태: 정상 범위 내")
        print()


# 사용 예시
config = {
    "core": [
        {"ticker": "TIGER 미국S&P500", "code": "360750", "weight": 0.35},
        {"ticker": "KODEX 200", "code": "069500", "weight": 0.15},
        {"ticker": "TIGER 미국채10년선물", "code": "305080", "weight": 0.15},
    ],
    "satellite": [
        {"ticker": "TIGER 반도체", "code": "091230", "weight": 0.12},
        {"ticker": "KODEX 2차전지산업", "code": "305720", "weight": 0.08},
        {"ticker": "TIGER 인도니프티50", "code": "453810", "weight": 0.07},
    ],
}

holdings = {
    "360750": {"value_krw": 4_500_000},
    "069500": {"value_krw": 1_500_000},
    "305080": {"value_krw": 1_200_000},
    "091230": {"value_krw": 1_800_000},
    "305720": {"value_krw": 600_000},
    "453810": {"value_krw": 400_000},
}

run_core_satellite_rebalance(config, holdings)

새틀라이트 교체 로직

새틀라이트는 분기마다 성과를 검토하고, 투자 논리가 약해진 종목을 교체한다.

"""새틀라이트 ETF 성과 검토 및 교체 판단."""

def evaluate_satellite(
    ticker: str,
    quarter_return: float,
    benchmark_return: float,
    thesis_valid: bool,
    cheaper_alternative: str | None = None,
) -> dict:
    """새틀라이트 ETF의 교체 필요 여부를 판단한다."""
    excess_return = quarter_return - benchmark_return

    recommendation = "HOLD"
    reasons = []

    # 투자 논리가 무효화된 경우
    if not thesis_valid:
        recommendation = "REPLACE"
        reasons.append("투자 논리(thesis)가 더 이상 유효하지 않음")

    # 3분기 연속 벤치마크 대비 부진한 경우
    if excess_return < -0.05:
        recommendation = "REVIEW"
        reasons.append(
            f"벤치마크 대비 {excess_return*100:.1f}%p 부진"
        )

    # 더 저렴한 대안이 있는 경우
    if cheaper_alternative:
        reasons.append(f"보수가 더 낮은 대안 존재: {cheaper_alternative}")

    return {
        "ticker": ticker,
        "quarter_return": f"{quarter_return*100:.1f}%",
        "excess_return": f"{excess_return*100:.1f}%p",
        "recommendation": recommendation,
        "reasons": reasons,
    }


# 분기 검토 예시
reviews = [
    evaluate_satellite(
        ticker="TIGER 반도체",
        quarter_return=0.12,
        benchmark_return=0.08,
        thesis_valid=True,
    ),
    evaluate_satellite(
        ticker="KODEX 2차전지산업",
        quarter_return=-0.03,
        benchmark_return=0.08,
        thesis_valid=True,
    ),
    evaluate_satellite(
        ticker="TIGER 인도니프티50",
        quarter_return=0.06,
        benchmark_return=0.08,
        thesis_valid=True,
        cheaper_alternative="KODEX 인도Nifty50(합성)",
    ),
]

print("=== 새틀라이트 분기 검토 ===")
for r in reviews:
    print(f"\n{r['ticker']}")
    print(f"  분기 수익률: {r['quarter_return']}")
    print(f"  초과 수익: {r['excess_return']}")
    print(f"  판정: {r['recommendation']}")
    if r["reasons"]:
        for reason in r["reasons"]:
            print(f"  - {reason}")

상관관계 관리: 분산 효과 유지

코어-새틀라이트 전략의 함정 중 하나는 새틀라이트끼리 상관관계가 높아지는 것이다. 반도체 ETF와 나스닥100 ETF를 동시에 보유하면 실질적으로 기술주에 과도하게 집중된다.

상관관계 모니터링

"""포트폴리오 내 ETF 상관관계를 계산한다."""
import numpy as np


def check_correlation(returns_matrix: dict, threshold: float = 0.8) -> list:
    """높은 상관관계를 가진 ETF 쌍을 찾는다.

    Args:
        returns_matrix: {종목코드: [일별 수익률 리스트]}
        threshold: 경고 기준 상관계수

    Returns:
        상관계수가 threshold를 넘는 ETF 쌍 목록
    """
    tickers = list(returns_matrix.keys())
    data = np.array([returns_matrix[t] for t in tickers])
    corr = np.corrcoef(data)

    high_corr_pairs = []
    for i in range(len(tickers)):
        for j in range(i + 1, len(tickers)):
            if abs(corr[i][j]) > threshold:
                high_corr_pairs.append({
                    "pair": (tickers[i], tickers[j]),
                    "correlation": round(corr[i][j], 3),
                    "warning": "분산 효과 약화 - 한쪽 비중 축소 검토",
                })

    return high_corr_pairs


# 예시: 60일 일별 수익률 (시뮬레이션)
np.random.seed(42)
market = np.random.normal(0.0005, 0.01, 60)

sample_returns = {
    "S&P500": market + np.random.normal(0, 0.002, 60),
    "NASDAQ100": market * 1.3 + np.random.normal(0, 0.003, 60),
    "반도체": market * 1.5 + np.random.normal(0, 0.005, 60),
    "미국채10년": -market * 0.3 + np.random.normal(0, 0.003, 60),
    "금": np.random.normal(0.0002, 0.008, 60),
}

alerts = check_correlation(sample_returns, threshold=0.7)

print("=== 상관관계 경고 ===")
for alert in alerts:
    print(f"  {alert['pair'][0]} <-> {alert['pair'][1]}: "
          f"상관계수 {alert['correlation']}")
    print(f"  -> {alert['warning']}")

코어-새틀라이트 운영 연간 달력

코어새틀라이트기타
1월정기 리밸런싱분기 검토 + 연간 전략 점검연간 수익률 정산, 세금 확인
2-3월편차 모니터링-배당 기준일 확인
4월-분기 검토ISA/IRP 납입 상황 점검
5-6월편차 모니터링-중간 점검
7월정기 리밸런싱분기 검토하반기 전략 조정
8-9월편차 모니터링--
10월-분기 검토 + 내년 전략 수립ISA/IRP 연말 납입 계획
11-12월편차 모니터링-세금 최적화 매매 (해외 ETF)

퀴즈

Q1. 코어-새틀라이트 전략에서 코어의 역할은? 정답: ||시장 전체 수익률(베타)을 저비용으로 확보하는 것이다. 시장을 이기려 하지 않고, 인덱스 ETF로 안정적 수익을 추구한다.||

Q2. 코어와 새틀라이트의 리밸런싱 주기가 다른 이유는? 정답: ||코어는 장기 보유가 원칙이므로 연 1-2회 정기 리밸런싱이면 충분하다. 새틀라이트는 시장 상황에 따라 교체가 가능하므로 분기 단위로 검토한다. 각각의 성격에 맞는 관리 주기를 적용해야 불필요한 매매를 줄일 수 있다.||

Q3. 새틀라이트 ETF 교체를 판단하는 가장 중요한 기준은? 정답: ||투자 논리(thesis)가 여전히 유효한가이다. 단기 성과 부진만으로 교체하면 감정적 매매가 되고, 투자 논리가 무효화되었는데도 보유하면 손실이 확대된다.||

Q4. 포트폴리오 내 ETF 상관관계가 높으면 왜 문제인가? 정답: ||상관관계가 높은 자산을 여러 개 보유하면 분산 효과가 사라진다. 예를 들어 S&P500 + NASDAQ100

  • 반도체 ETF를 동시에 보유하면 기술주 하락 시 세 종목이 동시에 하락하여 포트폴리오 전체가 큰 타격을 받는다.||

Q5. 올웨더형 포트폴리오가 주식, 채권, 금을 동시에 보유하는 이유는? 정답: ||경제 성장기(주식 상승), 경기 침체기(채권 상승), 인플레이션기(금 상승), 디플레이션기(장기채 상승) 등 어떤 환경에서도 일부 자산이 수익을 내도록 설계하기 위해서다. Ray Dalio의 All Weather 전략의 핵심 원리다.||

Q6. 코어 ETF 선정 시 보수(TER)를 가장 중요하게 보는 이유는? 정답: ||코어 ETF는 장기간(10년 이상) 보유하므로 보수가 복리로 누적된다. 보수 0.05%와 0.5%의 차이는 20년 후 약 9%의 수익률 차이를 만든다. 코어는 시장 수익률을 추종하므로 보수가 낮을수록 실질 수익이 높다.||

참고 자료

Core-Satellite ETF Automated Rebalancing

Core-Satellite ETF Automated Rebalancing

What is the Core-Satellite Strategy

Core-Satellite is an asset allocation strategy that divides the portfolio into two roles. The Core consists of low-cost index ETFs that track the overall market, pursuing stable returns. The Satellite consists of specific sector, theme, and style ETFs, aiming for excess returns (alpha).

This strategy sits between 100% index investing and 100% active investing. It is also the essence of the personal investor asset allocation proposed by David Swensen (Yale endowment manager) in Unconventional Success.

Core Principles of Core-Satellite:

  • Core weight 60-80%, Satellite weight 20-40%
  • Core is rarely touched (rebalancing 1-2 times per year)
  • Satellites can be swapped based on market conditions (reviewed quarterly)
  • Keep total portfolio expense ratio (TER) below 0.3%

Separating the Roles of Core and Satellite

Core: Capturing Market Beta

The core's objective is simple: obtaining market returns at the lowest possible cost. It doesn't try to "beat the market."

Core ETF Selection Criteria:

  1. Expense ratio 0.1% or lower (fees are a guaranteed negative return)
  2. AUM of 100 billion KRW or more (ensuring liquidity)
  3. Minimal Tracking Error (low divergence from the index)
  4. Dividend reinvestment structure (prefer Total Return index tracking)

Core ETF Candidates Available in Korea (as of 2026):

ETFCodeTracking IndexExpenseAUMPurpose
TIGER US S&P500360750S&P 5000.07%6.5T KRWUS large-cap
KODEX US S&P500TR379800S&P 500 TR0.05%3.2T KRWUS large-cap (dividend reinvest)
KODEX 200069500KOSPI 2000.05%5.8T KRWKorean large-cap
TIGER US NASDAQ100133690NASDAQ-1000.07%4.1T KRWUS tech stocks
KODEX US Treasury Ultra 30Y Futures (H)304660US Treasury 30Y0.09%280B KRWUS long-term bonds
TIGER US Treasury 10Y Futures305080US Treasury 10Y0.09%1.2T KRWUS mid-term bonds
KODEX Composite Bond (AA- and above) Active443160Korean bonds0.05%350B KRWDomestic bonds

Satellite: Pursuing Themes and Alpha

Satellites concentrate on specific sectors, regions, or styles to seek excess returns beyond the market. Unlike core holdings, they can be swapped, and if conviction is low, their weight can be reduced or held in cash.

Types of Satellite ETFs:

TypeExamplesCharacteristicsRisk Level
SectorSemiconductors, batteries, biotechFocused on specific industryHigh
ThemeAI, robotics, clean energyFollowing growth trendsHigh
RegionIndia, Japan, VietnamSpecific country growthMedium-High
StyleHigh dividend, value, small-capFactor-based investingMedium
AlternativeGold, commodities, REITsLow correlation, diversifiedMedium

Satellite Replacement Decision Criteria:

  • Is the investment thesis still valid?
  • What was the performance vs benchmark last quarter?
  • Has a similar ETF with lower fees been launched?
  • Has the correlation within the portfolio become excessively high?

Portfolio Design in Practice: 3 Scenarios

Scenario 1: Stable Growth (Conservative Investor)

# Target: 6-8% annual return, max drawdown within 15%
# For: 10-15 years before retirement, principal preservation priority
portfolio:
  name: 'Stable Growth'
  core_weight: 0.80
  satellite_weight: 0.20

  core:
    - ticker: 'KODEX US S&P500TR'
      weight: 0.30
      role: 'US market beta'
    - ticker: 'KODEX 200'
      weight: 0.15
      role: 'Korean market beta'
    - ticker: 'TIGER US Treasury 10Y Futures'
      weight: 0.20
      role: 'Defense during rate declines'
    - ticker: 'KODEX Composite Bond (AA- and above) Active'
      weight: 0.15
      role: 'Stable interest income'

  satellite:
    - ticker: 'TIGER Gold & Silver Futures (H)'
      weight: 0.10
      role: 'Inflation hedge'
    - ticker: 'TIGER US Dividend Dow Jones'
      weight: 0.10
      role: 'Dividend income'

  expected:
    annual_return: '6-8%'
    max_drawdown: '-15%'
    total_expense_ratio: '0.07%'

Scenario 2: Growth-Oriented (30s Office Worker)

# Target: 10-12% annual return, willing to accept 25% max drawdown
# For: 15+ year investment horizon, aggressive growth
portfolio:
  name: 'Growth-Oriented'
  core_weight: 0.65
  satellite_weight: 0.35

  core:
    - ticker: 'TIGER US S&P500'
      weight: 0.35
      role: 'US market beta'
    - ticker: 'TIGER US NASDAQ100'
      weight: 0.15
      role: 'Tech stock growth'
    - ticker: 'KODEX 200'
      weight: 0.10
      role: 'Korean market beta'
    - ticker: 'TIGER US Treasury 10Y Futures'
      weight: 0.05
      role: 'Minimum bond allocation'

  satellite:
    - ticker: 'TIGER Semiconductor'
      weight: 0.12
      role: 'Semiconductor cycle alpha'
    - ticker: 'KODEX Secondary Battery Industry'
      weight: 0.08
      role: 'Secondary battery growth'
    - ticker: 'TIGER Fn Semiconductor TOP10'
      weight: 0.08
      role: 'Korean semiconductor concentration'
    - ticker: 'TIGER India Nifty50'
      weight: 0.07
      role: 'India market growth'

  expected:
    annual_return: '10-12%'
    max_drawdown: '-25%'
    total_expense_ratio: '0.15%'

Scenario 3: All-Weather (Volatility Minimization)

# Target: Pursue positive returns in any market environment
# For: Investors who extremely dislike volatility
# Reference: Variation of Ray Dalio's All Weather strategy
portfolio:
  name: 'All-Weather'
  core_weight: 0.85
  satellite_weight: 0.15

  core:
    - ticker: 'TIGER US S&P500'
      weight: 0.25
      role: 'Returns during economic growth'
    - ticker: 'TIGER US Treasury 10Y Futures'
      weight: 0.25
      role: 'Recession defense'
    - ticker: 'KODEX US Treasury Ultra 30Y Futures (H)'
      weight: 0.15
      role: 'Deflation defense'
    - ticker: 'KODEX Composite Bond (AA- and above) Active'
      weight: 0.10
      role: 'Stable interest'
    - ticker: 'TIGER Gold & Silver Futures (H)'
      weight: 0.10
      role: 'Inflation hedge'

  satellite:
    - ticker: 'KODEX 200'
      weight: 0.10
      role: 'Korean market exposure'
    - ticker: 'TIGER Crude Oil Futures Enhanced (H)'
      weight: 0.05
      role: 'Commodity inflation response'

  expected:
    annual_return: '5-7%'
    max_drawdown: '-10%'
    total_expense_ratio: '0.09%'

Implementing Automated Rebalancing

Separate Rebalancing for Core vs Satellite

Core and satellite have different rebalancing cycles and criteria. They should not be combined under a single rule.

"""
Core-Satellite Separate Rebalancing Engine
- Core: Twice a year + emergency when drift exceeds 5%
- Satellite: Quarterly + emergency when drift exceeds 7%
"""
from dataclasses import dataclass


@dataclass
class RebalanceRule:
    """Rebalancing rules by asset type."""
    section: str            # "core" or "satellite"
    scheduled_frequency: str  # "semi-annual" or "quarterly"
    drift_threshold: float    # Drift threshold
    min_trade_krw: int        # Minimum trade amount


RULES = {
    "core": RebalanceRule(
        section="core",
        scheduled_frequency="semi-annual",
        drift_threshold=0.05,
        min_trade_krw=200_000,
    ),
    "satellite": RebalanceRule(
        section="satellite",
        scheduled_frequency="quarterly",
        drift_threshold=0.07,
        min_trade_krw=100_000,
    ),
}


def check_section(
    section: str,
    holdings: dict,
    targets: list[dict],
    total_value: int,
) -> dict:
    """Determine if a specific section needs rebalancing."""
    rule = RULES[section]
    drift_alerts = []

    for target in targets:
        code = target["code"]
        current_value = holdings.get(code, {}).get("value_krw", 0)
        current_weight = current_value / total_value if total_value > 0 else 0
        target_weight = target["weight"]
        drift = abs(current_weight - target_weight)

        if drift > rule.drift_threshold:
            drift_alerts.append({
                "ticker": target["ticker"],
                "code": code,
                "target": target_weight,
                "current": round(current_weight, 4),
                "drift": round(drift, 4),
                "action_needed": True,
            })

    return {
        "section": section,
        "rule": rule,
        "needs_rebalancing": len(drift_alerts) > 0,
        "alerts": drift_alerts,
    }


def run_core_satellite_rebalance(portfolio_config: dict, holdings: dict):
    """Execute rebalancing for core and satellite separately."""
    total_value = sum(h["value_krw"] for h in holdings.values())

    print(f"Total assets: ₩{total_value:,}\n")

    for section in ["core", "satellite"]:
        targets = portfolio_config[section]
        result = check_section(section, holdings, targets, total_value)

        section_label = "Core" if section == "core" else "Satellite"
        print(f"=== {section_label} Check ===")
        print(f"Rebalancing frequency: {result['rule'].scheduled_frequency}")
        print(f"Drift threshold: ±{result['rule'].drift_threshold * 100}%")

        if result["needs_rebalancing"]:
            print(f"Status: Rebalancing needed")
            for alert in result["alerts"]:
                print(
                    f"  - {alert['ticker']}: "
                    f"Target {alert['target']*100:.1f}% / "
                    f"Current {alert['current']*100:.1f}% / "
                    f"Drift {alert['drift']*100:.1f}%p"
                )
        else:
            print("Status: Within normal range")
        print()


# Usage example
config = {
    "core": [
        {"ticker": "TIGER US S&P500", "code": "360750", "weight": 0.35},
        {"ticker": "KODEX 200", "code": "069500", "weight": 0.15},
        {"ticker": "TIGER US Treasury 10Y Futures", "code": "305080", "weight": 0.15},
    ],
    "satellite": [
        {"ticker": "TIGER Semiconductor", "code": "091230", "weight": 0.12},
        {"ticker": "KODEX Secondary Battery Industry", "code": "305720", "weight": 0.08},
        {"ticker": "TIGER India Nifty50", "code": "453810", "weight": 0.07},
    ],
}

holdings = {
    "360750": {"value_krw": 4_500_000},
    "069500": {"value_krw": 1_500_000},
    "305080": {"value_krw": 1_200_000},
    "091230": {"value_krw": 1_800_000},
    "305720": {"value_krw": 600_000},
    "453810": {"value_krw": 400_000},
}

run_core_satellite_rebalance(config, holdings)

Satellite Replacement Logic

Satellites are reviewed quarterly for performance, and holdings with weakening investment theses are replaced.

"""Satellite ETF performance review and replacement decision."""

def evaluate_satellite(
    ticker: str,
    quarter_return: float,
    benchmark_return: float,
    thesis_valid: bool,
    cheaper_alternative: str | None = None,
) -> dict:
    """Determine whether a satellite ETF should be replaced."""
    excess_return = quarter_return - benchmark_return

    recommendation = "HOLD"
    reasons = []

    # If the investment thesis is invalidated
    if not thesis_valid:
        recommendation = "REPLACE"
        reasons.append("Investment thesis is no longer valid")

    # If underperforming benchmark for 3 consecutive quarters
    if excess_return < -0.05:
        recommendation = "REVIEW"
        reasons.append(
            f"Underperforming benchmark by {excess_return*100:.1f}%p"
        )

    # If a cheaper alternative exists
    if cheaper_alternative:
        reasons.append(f"Lower-fee alternative available: {cheaper_alternative}")

    return {
        "ticker": ticker,
        "quarter_return": f"{quarter_return*100:.1f}%",
        "excess_return": f"{excess_return*100:.1f}%p",
        "recommendation": recommendation,
        "reasons": reasons,
    }


# Quarterly review example
reviews = [
    evaluate_satellite(
        ticker="TIGER Semiconductor",
        quarter_return=0.12,
        benchmark_return=0.08,
        thesis_valid=True,
    ),
    evaluate_satellite(
        ticker="KODEX Secondary Battery Industry",
        quarter_return=-0.03,
        benchmark_return=0.08,
        thesis_valid=True,
    ),
    evaluate_satellite(
        ticker="TIGER India Nifty50",
        quarter_return=0.06,
        benchmark_return=0.08,
        thesis_valid=True,
        cheaper_alternative="KODEX India Nifty50 (Synthetic)",
    ),
]

print("=== Satellite Quarterly Review ===")
for r in reviews:
    print(f"\n{r['ticker']}")
    print(f"  Quarterly return: {r['quarter_return']}")
    print(f"  Excess return: {r['excess_return']}")
    print(f"  Verdict: {r['recommendation']}")
    if r["reasons"]:
        for reason in r["reasons"]:
            print(f"  - {reason}")

Correlation Management: Maintaining Diversification Benefits

One pitfall of the core-satellite strategy is when satellites become highly correlated with each other. Holding both a semiconductor ETF and a NASDAQ100 ETF simultaneously effectively results in excessive concentration in tech stocks.

Correlation Monitoring

"""Calculate ETF correlations within a portfolio."""
import numpy as np


def check_correlation(returns_matrix: dict, threshold: float = 0.8) -> list:
    """Find ETF pairs with high correlation.

    Args:
        returns_matrix: {ticker: [daily return list]}
        threshold: Warning threshold for correlation coefficient

    Returns:
        List of ETF pairs with correlation exceeding the threshold
    """
    tickers = list(returns_matrix.keys())
    data = np.array([returns_matrix[t] for t in tickers])
    corr = np.corrcoef(data)

    high_corr_pairs = []
    for i in range(len(tickers)):
        for j in range(i + 1, len(tickers)):
            if abs(corr[i][j]) > threshold:
                high_corr_pairs.append({
                    "pair": (tickers[i], tickers[j]),
                    "correlation": round(corr[i][j], 3),
                    "warning": "Weakened diversification - consider reducing one position",
                })

    return high_corr_pairs


# Example: 60-day daily returns (simulation)
np.random.seed(42)
market = np.random.normal(0.0005, 0.01, 60)

sample_returns = {
    "S&P500": market + np.random.normal(0, 0.002, 60),
    "NASDAQ100": market * 1.3 + np.random.normal(0, 0.003, 60),
    "Semiconductor": market * 1.5 + np.random.normal(0, 0.005, 60),
    "US Treasury 10Y": -market * 0.3 + np.random.normal(0, 0.003, 60),
    "Gold": np.random.normal(0.0002, 0.008, 60),
}

alerts = check_correlation(sample_returns, threshold=0.7)

print("=== Correlation Alerts ===")
for alert in alerts:
    print(f"  {alert['pair'][0]} <-> {alert['pair'][1]}: "
          f"Correlation {alert['correlation']}")
    print(f"  -> {alert['warning']}")

Core-Satellite Annual Operations Calendar

MonthCoreSatelliteOther
JanuaryScheduled rebalancingQuarterly review + annual strategyAnnual return settlement, tax review
Feb-MarDrift monitoring-Ex-dividend date check
April-Quarterly reviewISA/IRP contribution check
May-JunDrift monitoring-Mid-year review
JulyScheduled rebalancingQuarterly reviewH2 strategy adjustment
Aug-SepDrift monitoring--
October-Quarterly review + next year planISA/IRP year-end contribution plan
Nov-DecDrift monitoring-Tax-optimized trading (overseas ETF)

Quiz

Q1. What is the role of the core in a core-satellite strategy? Answer: To capture overall market returns (beta) at low cost. It doesn't try to beat the market, but pursues stable returns through index ETFs.

Q2. Why do core and satellite have different rebalancing cycles? Answer: Core follows a long-term holding principle, so semi-annual scheduled rebalancing is sufficient. Satellites can be swapped based on market conditions, so they are reviewed quarterly. Applying management cycles appropriate to each character reduces unnecessary trades.

Q3. What is the most important criterion for deciding whether to replace a satellite ETF?

Answer: Whether the investment thesis is still valid. Replacing based only on short-term underperformance leads to emotional trading, while holding despite an invalidated thesis expands losses.

Q4. Why is high correlation between ETFs in a portfolio a problem? Answer: Holding multiple highly correlated assets eliminates diversification benefits. For example, holding S&P500 + NASDAQ100 + Semiconductor ETFs simultaneously means all three drop together during a tech stock decline, causing significant damage to the entire portfolio.

Q5. Why does the All-Weather portfolio hold stocks, bonds, and gold simultaneously?

Answer: It is designed so that some assets generate returns in any environment: economic growth (stocks rise), recession (bonds rise), inflation (gold rises), deflation (long-term bonds rise). This is the core principle of Ray Dalio's All Weather strategy.

Q6. Why is expense ratio (TER) the most important factor when selecting core ETFs?

Answer: Core ETFs are held long-term (10+ years), so expenses compound over time. The difference between 0.05% and 0.5% in expense ratio creates approximately 9% return difference over 20 years. Since core tracks market returns, lower expenses mean higher real returns.

References