전체 recipe
중급 · Live

REIT 분기 portfolio benchmark

운용 자산 portfolio 의 자산별 vs 시장 median premium/discount 를 자동 계산. Python 노트북 한 번 실행으로 분기 보고서 / IC report 에 들어갈 표 + 차트가 완성됩니다.

약 30분 (초기 셋업), 이후 분기마다 5분 REIT 운용사 · 자산운용 부동산본부

When to use this

언제 쓰나

분기말 운용 보고서 / 위탁자 보고서 / IC review 작성 시 portfolio 자산이 시장 평균 대비 어디 위치하는지 보여줘야 합니다. 자체 자산 가격은 운용사 내부 valuation 모델에서 나오지만, 비교 baseline 인 시장 median 은 외부 source 가 필요. JLL/CBRE 보고서는 license-restricted PDF, 한국부동산원 R-ONE 은 권역 인덱스 (자산 매핑 어려움).

Veacon API 는 sigungu_code × property_type × transaction_type × period 단위로 RTMS-grade aggregate 를 즉시 제공. 자체 자산 list 를 for-loop 로 돌려 자산별 cohort 를 매핑하면 portfolio 전체의 market benchmark 가 나옵니다. pandas DataFrame 으로 join 하면 Excel 출력 + 차트까지 한 번에.

What you'll get

결과물

  • 자산별 행 + (자체 valuation, market median, premium %, confidence, source_mix) 컬럼이 들어간 DataFrame
  • Portfolio-level 가중평균 premium (자산 평형 기준 weighted)
  • confidence=high 자산 vs low 자산 분리 차트. 어느 자산은 신뢰 가능한 비교, 어느 자산은 sample 부족인지 명확
  • Excel export ready · 분기 보고서 첨부 양식
  • 분기 자동화: 같은 노트북 다음 분기에 그대로 재실행

Workflow

5-step recipe

1

환경 셋업

Python 3.10+, pandas, requests, matplotlib. Veacon API key 는 환경 변수로.

pip install pandas requests matplotlib
export VEACON_API_KEY=veacon_pk_...
bash
2

Portfolio 정의

운용 자산 list 를 CSV (asset_name, sigungu_code, property_type, transaction_type, internal_valuation_krw, area_m2) 로 준비. Excel 에서 export 가능.

asset_name,sigungu_code,property_type,transaction_type,internal_valuation_krw,area_m2
강남파이낸스 (역삼),11680,office,sale,4500000000,250
영등포 IFC,11560,office,sale,4200000000,280
명동 retail flagship,11140,retail,sale,8500000000,180
csv
3

시장 데이터 fetch (per-asset loop)

자산별로 Veacon pulse 호출. property_type + transaction_type + sigungu_code 매칭. period 는 last_3m (분기 보고서) 또는 YYYY-Q1 (특정 분기) 사용.

import os, requests, pandas as pd

API_KEY = os.environ['VEACON_API_KEY']
BASE = 'https://veacon.io/api/v1/real-estate/pulse'

portfolio = pd.read_csv('portfolio.csv')

def fetch_market(row):
    r = requests.get(BASE, headers={'X-API-Key': API_KEY}, params={
        'sigungu_code':     row['sigungu_code'],
        'property_type':    row['property_type'],
        'transaction_type': row['transaction_type'],
        'period':           'last_3m',
        'geo_precision':    'sigungu',
    })
    if r.status_code == 404:
        return {'sample_count': 0, 'median_price': None, 'confidence': None}
    body = r.json()['data'][0]
    return {
        'sample_count':  body['sample_count'],
        'median_price':  body['median_price'],
        'p25_price':     body['p25_price'],
        'p75_price':     body['p75_price'],
        'confidence':    body['confidence'],
        'source_mix':    body['source_mix'],
    }

market = portfolio.apply(fetch_market, axis=1, result_type='expand')
df = pd.concat([portfolio, market], axis=1)
python
4

Premium 계산 + roll-up

자산별 premium = (자체 valuation - market median) / market median. Portfolio-level 은 면적 기준 가중평균.

df['premium_pct'] = (
    (df['internal_valuation_krw'] - df['median_price']) / df['median_price']
)

# 면적 기준 가중평균
total_area = df['area_m2'].sum()
df['weight'] = df['area_m2'] / total_area
portfolio_premium = (df['premium_pct'] * df['weight']).sum()

print(f"Portfolio weighted premium: {portfolio_premium:+.1%}")
print(f"  high-confidence assets: {(df['confidence']=='high').sum()}")
print(f"  low-confidence assets:  {(df['confidence']=='low').sum()}")
python
5

Excel export + 차트

xlsxwriter 또는 openpyxl 로 Excel 출력. 시각화는 matplotlib bar chart (자산별 premium bar) + confidence-색상 분리.

분기 자동화: CSV 에 자산만 추가하면 같은 노트북 재실행. 다음 분기에는 5분.

# Excel export
with pd.ExcelWriter('portfolio-benchmark-2026Q1.xlsx', engine='xlsxwriter') as w:
    df.to_excel(w, sheet_name='Detail', index=False)
    summary = pd.DataFrame([{
        'as_of':                pd.Timestamp.utcnow().date(),
        'portfolio_size':       len(df),
        'weighted_premium_pct': portfolio_premium,
        'high_conf_count':      (df['confidence'] == 'high').sum(),
        'low_conf_count':       (df['confidence'] == 'low').sum(),
    }])
    summary.to_excel(w, sheet_name='Summary', index=False)

# Chart
import matplotlib.pyplot as plt
colors = df['confidence'].map({'high':'#10b981','medium':'#f59e0b','low':'#ef4444'})
df.plot.bar(x='asset_name', y='premium_pct', color=colors, figsize=(10,5),
    title=f'Portfolio premium vs market (weighted: {portfolio_premium:+.1%})')
plt.axhline(0, color='black', linewidth=0.5)
plt.tight_layout()
plt.savefig('portfolio-benchmark-chart.png', dpi=150)
python

Caveats

알아두실 사항

  • Rate limit: Pro tier 60 req/min, Team 200 req/min. Portfolio 20개 자산 = 한 번에 fetch 가능. 100개 자산 portfolio 는 분당 분할 또는 Team tier 권장.
  • 404 처리: 매칭 cohort 가 없으면 endpoint 가 404 + envelope 반환. Python 코드에서 graceful fallback (median=NaN) 처리. NaN 자산은 portfolio 가중평균에서 제외.
  • 자체 valuation 정의 일관성: Premium 계산이 의미 있으려면 internal_valuation_krw 가 sale 가격 기준이어야 함 (장부가 X). REIT 분기말 valuation 사용 권장.
  • confidence 가 low 인 자산 은 별도 표 또는 회색 처리. 분기 보고서 첨부 시 위탁자가 신뢰도 의문 제기 가능. 미리 footnote.
  • RTMS coverage 는 실제 거래의 약 40-60%. R-ONE + 공시지가 cross-reference 가 confidence 점수에 반영됩니다 (Phase 1.5 라이브).상세.