Skip to content

Commit 9342dcd

Browse files
authored
Merge pull request #438 from easygap/feat/pocket-basket
feat: 소액 적립 바스켓 kr_pocket 신설 — KODEX200 50/50, 30만원
2 parents 9e168b3 + 5efc275 commit 9342dcd

3 files changed

Lines changed: 52 additions & 0 deletions

File tree

config/baskets.yaml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,29 @@ baskets:
8080
"051910": 0.20
8181
# 합계 = 1.00 (이 비중은 "주식 슬리브 내부" 기준; 주식 슬리브 자체가 총자산의 50%)
8282

83+
# --- 소액 적립 트랙 (용돈 엔진) — docs/POCKET_TRACK_PLAN.md ---
84+
# 소액(30만 시작 + 월 10만 적립)의 유일한 분산 수단은 ETF다: KODEX 200 1주가
85+
# 그 자체로 200종목 분산이고, 국내 주식형 ETF는 매도 거래세 면제라 비용도 유리.
86+
# 주식 50/현금 50으로 낙폭을 절반으로 누른다(STATIC_ALLOCATION.md).
87+
# 파라미터 주의(소액 특화):
88+
# - max_turnover_ratio 0.6: 15%면 회전 한도(30만×15%=4.5만)가 ETF 1주가(약 12.6만)보다
89+
# 작아 영원히 매수 불가. 0.6이면 첫 사이클에 주식 슬리브(50%)를 채울 수 있다.
90+
# - min_trade_amount 50000: 기본 20만이면 슬롯(15만)이 최소거래 미달로 미체결 판정.
91+
# 월 적립은 tools/record_deposit.py --basket kr_pocket --amount 100000 으로 기록
92+
# (입금은 TWR이 중화 — 수익률 왜곡 없음). 다음 사이클이 새 현금을 흡수한다.
93+
kr_pocket:
94+
name: "소액 적립 (KODEX200 50/50)"
95+
enabled: true
96+
initial_capital: 300000 # 시작 자본 30만원 (paper — live 전환 시 실자본과 일치)
97+
target_stock_weight: 0.5 # 주식 50% / 현금 50% — 낙폭 절반이 목적
98+
rebalance:
99+
trigger: drift
100+
drift_threshold: 0.08
101+
min_trade_amount: 50000
102+
max_turnover_ratio: 0.6
103+
holdings:
104+
"069500": 1.0 # KODEX 200 — 주식 슬리브 전체(슬리브 자체가 총자산의 50%)
105+
83106
# --- 예시 2: 신호 가중 리밸런싱 바스켓 ---
84107
# signal_weighted: true 시, 스코어링 전략 점수에 따라 목표 비중을 동적 조정
85108
kr_signal_weighted:

docs/POCKET_TRACK_PLAN.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,12 @@
5050
- 드리프트 8%p·저회전 유지. 리밸런싱보다 **적립 매수가 주된 거래**가 된다.
5151
- 페이퍼로 시작 → 기존 60일 게이트·일일/주간 리포트·관측성 전부 재사용(계정 키
5252
`basket_rebalance:kr_pocket` 격리). live 전환도 기존 런북 그대로.
53+
- **실측 참고 (069500, 2021-12~2026-07, 4.6년)**: 100% 보유 CAGR +32.6%·MDD -28.4%,
54+
50/50(일 리밸 근사) CAGR +16.2%·MDD -15.1%. **주의 — 이 구간은 2025~26 강세장 편향**
55+
크므로 계획 수치로는 §2의 보수 기대(연 ~9%)를 유지한다. 실측은 상한 참고일 뿐이다.
56+
- **1주 단위 절사(구조적)**: 30만 시점 배치율 42.7%/50%(-7.3%p)는 ETF 1주(약 12.8만)
57+
단위 때문이며 결함이 아니다 — 적립이 쌓이면 자연 수렴(예: 50만 시점 2주=51%).
58+
헬스의 배치율 ATTENTION(허용 -5%p)에 이 구간이 걸릴 수 있음을 인지하고 볼 것.
5359

5460
## 4. 선행 구현 — 적립(입금) + 시간가중수익률(TWR) [필수, 첫 PR]
5561

tests/test_basket_rebalancer.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,29 @@ def test_diversified_hold_basket_is_low_turnover(self):
339339
# --confirm-live + basket_rebalance:<name> readiness gate)를 통과해야 한다.
340340
assert b["enabled"] is True
341341

342+
def test_pocket_basket_small_capital_invariants(self):
343+
"""소액 적립 바스켓(docs/POCKET_TRACK_PLAN.md §3) — 소액 특화 불변식.
344+
345+
이 값들이 '일반' 기본값으로 되돌아가면 바스켓이 조용히 죽는다:
346+
min_trade 20만이면 슬롯(15만)이 미체결 판정, 회전상한 15%면 한도(4.5만)가
347+
ETF 1주가(약 12.6만)보다 작아 영원히 매수 불가.
348+
"""
349+
baskets = self._load()
350+
assert "kr_pocket" in baskets, "소액 적립 바스켓 누락"
351+
b = baskets["kr_pocket"]
352+
assert b["enabled"] is True
353+
capital = float(b["initial_capital"])
354+
assert capital == 300_000
355+
tsw = float(b["target_stock_weight"])
356+
assert tsw == 0.5
357+
rb = b["rebalance"]
358+
slot = capital * tsw # 단일 종목이므로 슬리브 전체가 한 슬롯
359+
assert float(rb["min_trade_amount"]) <= slot, "최소거래가 슬롯보다 크면 미체결"
360+
# 첫 사이클에 주식 슬리브를 채울 수 있어야 한다(회전 한도 ≥ 슬리브 비중)
361+
assert float(rb["max_turnover_ratio"]) >= tsw, "회전 한도 < 슬리브면 초기 매수 불가"
362+
# ETF 단일 구성(1주 = 200종목 분산)
363+
assert list(b["holdings"].keys()) == ["069500"]
364+
342365
def test_all_basket_symbols_are_6digit_kr_codes(self):
343366
baskets = self._load()
344367
for name, cfg in baskets.items():

0 commit comments

Comments
 (0)