用 Python 在韩国股市白捡超额收益:Magic Formula 实战指南
如果你在美股大市值股票上跑过 Magic Formula(ROIC + 盈利收益率排名),大概率已经发现它被量化基金和散户玩烂了,超额收益从30%+掉到了5-10%。但在韩国股市(KOSPI 和 KOSDAQ)情况完全不同——散户占比高、小盘股信息不对称、2300+上市公司,直到2025年学术回测还能跑出12-18%年化超额。
实现它的最大门槛不是策略本身,而是韩国特色的会计规则和数据来源。这篇指南带你从零开始,用韩国官方免费 API (DART) 加上 Python,把整条 pipeline 搭出来。
数据源:DART 和 OpenDartReader
美国查财报用 EDGAR,韩国查财报用 DART(Data Analysis, Retrieval and Transfer System),由韩国金融监督院运营,提供免费 API。
Python 包 OpenDartReader 让你直接调用:
import OpenDartReader
import pandas as pd
# 从 https://opendart.fss.or.kr/uss/umt/login/loginPage.do 免费申请 API 密钥
dart = OpenDartReader(api_key='YOUR_DART_API_KEY')
# 获取所有上市公司列表(KOSPI + KOSDAQ + KONEX)
companies = dart.list_date('20251231')
listed = companies[companies['corp_cls'].isin(['Y', 'K'])] # Y=KOSPI, K=KOSDAQ
print(f"待筛选公司数量: {len(listed)} 家")
返回大约 2300 家公司,这就是你的初筛池。
把 Magic Formula 翻译成韩国会计科目
Greenblatt 的两个因子:盈利收益率 (EBIT / Enterprise Value) 和 ROIC (EBIT / (净运营资本 + 净固定资产))。
在韩国 K-IFRS(韩国版国际财务报告准则)下,你需要把科目对应好。下面是关键映射(原文里的表格改成更易懂的无序列表):
- EBIT(营业利润):K-IFRS 科目
영업이익,DART 字段OperatingProfitLoss - Enterprise Value:需要自己算:市值 + 总负债 – 现金及等价物(市值来自行情数据,不是 DART)
- 净运营资本:
유동자산 - 유동부채(流动资产 – 流动负债) - 净固定资产:
유형자산 + 무형자산(PropertyPlantAndEquipment+IntangibleAssets)
大部分新手实现会栽在两个坑里:
1. 计算 EV 时忘了加减债务和现金,结果变成 PE 比率而不是 EBIT/EV。
2. 净运营资本可能是负数(尤其控股公司),算 ROIC 时会爆表,必须做下限(floor)处理。
def compute_factors(corp_code, year):
# 获取利润表
fs = dart.finstate(corp_code, year, reprt_code='11011') # 11011 = 年报
if fs is None or fs.empty:
return None
income_statement = fs[fs['sj_div'] == 'IS'] # 利润表
balance_sheet = fs[fs['sj_div'] == 'BS'] # 资产负债表
# 营业利润(영업이익)
ebit = float(income_statement.loc[
income_statement['account_nm'] == '영업이익', 'thstrm_amount'
].iloc[0].replace(',', ''))
# 还需要解析资产负债表获取:总资产、流动资产、流动负债、总负债、现金
# 下面省略具体解析代码(篇幅原因)
return {'ebit': ebit, ...}
注意 DART 返回的数字是韩国格式字符串(如“1,234,567,890”),必须去掉逗号再转 float。
因子计算
拿到原始数字后,排个名:
def magic_formula_rank(universe_df):
# universe_df 需包含:corp_code, ebit, ev, net_working_capital, net_fixed_assets
# 盈利收益率
universe_df['earnings_yield'] = universe_df['ebit'] / universe_df['ev']
# ROIC,对净运营资本做下限保护,防止负数爆表
invested_capital = (
universe_df['net_working_capital'].clip(lower=0) +
universe_df['net_fixed_assets']
)
universe_df['roic'] = universe_df['ebit'] / invested_capital
# 两个因子分别排名(越小越优秀)
universe_df['ey_rank'] = universe_df['earnings_yield'].rank(ascending=False)
universe_df['roic_rank'] = universe_df['roic'].rank(ascending=False)
# 联合排名(得分相加)
universe_df['combined_rank'] = universe_df['ey_rank'] + universe_df['roic_rank']
return universe_df.sort_values('combined_rank')
韩国股市特有的 5 个大坑
坑 1:控股公司结构
韩国财阀有复杂的控股子公司结构,母公司利润表里投资收益多、营业利润少,Magic Formula 会给它们极低评分。需要排除行业代码为 64(控股公司)的公司。
坑 2:优先股 / 双类股
很多韩国公司(三星电子、LG 系)有优先股(名称带“우”),其 EBIT 属于整个公司而非该股份类别。要把名称带“우”的股票剔除。
坑 3:小盘股数据质量
KOSDAQ 小盘股有时晚交年报、或者重述后不更新 DART 接口。大约 5-10% 的小盘股数据需要人工修正。排在最前面的“高 ROIC”公司往往是因为数据错误(比如一次性收益未从 EBIT 中剔除)。
坑 4:财年时间不一致
约 80% 公司财年结束在 12 月,但 20% 在 3 月或 6 月。如果你在 12 月 31 日使用年报数据,这 20% 公司看到的是 6 个月前的旧数据。建议使用 TTM(滚动 12 个月)数据,DART 提供季度报告(reprt_code='11013' 等),可累加 4 个季度。
坑 5:存活者偏差更严重
韩国交易所会主动对不符合标准的公司进行退市,2024 年 KOSDAQ 就退了 30-40 家。如果只选“当前仍上市”的公司,就等于自动排除了表现最差的股票。做历史回测时,必须用 dart.delisted() 把当年退市的公司也纳入。
回测结果(示意)
如果你在 2015-2024 年按以下条件运行:
– KOSPI + KOSDAQ
– 排除控股公司、金融业、房地产
– 市值 > 500 亿韩元(约 3500 万美元)
– 取联合排名前 10%,等权持仓
– 月频调仓,假设 15 个基点交易成本
– 使用无存活者偏差的完整历史名单
得到年化回报约 13.8%,同期 KOSPI 指数约 6.2%,夏普比率 0.72 vs 0.41。
注意这些数字只是示意——具体数值取决于你的筛选条件、调仓日期和大坑处理方式。核心结论是:Magic Formula 在韩国股市的剩余阿尔法远大于美股大盘股,因为这里机构套利活动少。
策略能持续发挥作用的前提是市场效率低,一旦大量机构资金开始跑同一策略,超额收益就会消失。把它当作“趁别人还没意识到时赚一笔”,而不是永动机。
部署你需要准备的
- DART API 密钥:免费申请,韩语界面需 5-10 分钟
- OpenDartReader Python 包:
pip install opendartreader - 股票行情数据:KIS Developers(免费但需韩国券商账户)、Yahoo Finance(基础可用)、或 NaverFinance 爬虫(灰色地带)
- 历史上市名单:通过 DART 的
list_date和delisted接口自行保存往年数据
整套堆栈完全免费,唯一的麻烦是 DART 的韩语注册界面和 K-IFRS 科目处理。
结论
2026 年,在韩国股市运行 Magic Formula 仍是一个可行的散户量化策略,特别是你能读韩文财报或愿意花时间适配 K-IFRS。它的预期超额收益远高于在美股大盘股上跑同一策略。
难的不是公式本身,而是数据基础架构。一旦你搭好了干净的 DART 数据管线,你还可以在这同一数据集上跑价值、质量、动量等其他因子策略。韩国股市奖励那些愿意做 K-IFRS 脏活累活的散户量化交易者。
