4、重复拍卖模型:重复销售法(Repeat Sales Model)
聊到艺术品估值,很多人第一反应是「找个专家看一眼」。但专家也有看走眼的时候,对吧?我做了这么多年量化模型,最深的体会是:数据不会骗人,但数据太少会骗人。
重复销售法,说白了就是盯住同一件作品,看它两次拍卖之间的价格变化。你想想看,同一幅画,2010年卖了100万,2015年卖了150万,这50%的涨幅就是最真实的回报率。不用管什么流派、尺寸、艺术家名气,因为作品本身没变,变的只是市场情绪。
4.1 原理:为什么重复销售法靠谱?
传统hedonic模型需要控制一大堆特征——尺寸、题材、技法、艺术家年龄……说实话,我早期做hedonic模型时,光特征工程就折腾了两个月。而且有些特征根本量化不了,比如「这幅画的构图是否和谐」。
重复销售法绕开了这个坑。它的逻辑很简单:
- 同一件作品,所有物理特征不变
- 两次拍卖,价格变化只反映市场波动
- 多件作品,取平均就能得到市场指数
数学上,我们假设作品 i 在时间 t 的价格为 Pit,那么:
ln(P_it) = α_t + β_i + ε_it
其中 αt 就是我们要估计的市场指数,βi 是作品个体效应。两次拍卖相减,βi 就消掉了:
ln(P_i,t2) - ln(P_i,t1) = α_t2 - α_t1 + (ε_i,t2 - ε_i,t1)
嗯,这里要注意:这个模型假设两次拍卖之间的时间间隔内,作品没有发生任何变化。比如没有修复、没有重新装裱、没有损坏。我在项目中遇到过一幅画,第一次拍卖时品相完好,第二次拍卖前被水泡过,价格直接腰斩。这种数据必须剔除。
4.2 构建重复拍卖数据集
这一步是体力活,也是技术活。我习惯把流程分成四个阶段:
- 数据清洗:从拍卖行数据库提取所有成交记录,去重、标准化作品名称
- 匹配对生成:按作品唯一标识(如作品ID、艺术家+名称+尺寸)进行自连接
- 时间筛选:只保留两次拍卖时间不同的记录,且间隔至少6个月
- 异常剔除:剔除价格变化超过10倍或低于0.1倍的极端值
给你看一段我常用的Python代码,用于生成重复销售对:
import pandas as pd
import numpy as np
# 假设df包含字段:work_id, sale_date, price_usd
# 按作品ID分组,筛选出现次数≥2的作品
repeat_works = df.groupby('work_id').filter(lambda x: len(x) >= 2)
# 对每个作品,按时间排序,生成相邻拍卖对
def generate_pairs(group):
group = group.sort_values('sale_date')
pairs = []
for i in range(len(group)-1):
pairs.append({
'work_id': group.iloc[i]['work_id'],
't1': group.iloc[i]['sale_date'],
't2': group.iloc[i+1]['sale_date'],
'p1': group.iloc[i]['price_usd'],
'p2': group.iloc[i+1]['price_usd']
})
return pd.DataFrame(pairs)
pairs_df = repeat_works.groupby('work_id').apply(generate_pairs).reset_index(drop=True)
# 计算对数收益率
pairs_df['log_return'] = np.log(pairs_df['p2'] / pairs_df['p1'])
pairs_df['interval_years'] = (pairs_df['t2'] - pairs_df['t1']).dt.days / 365.25
4.3 计算市场指数
有了重复销售对,接下来就是回归。我们用OLS估计每个时间段的 αt:
import statsmodels.api as sm
# 构建哑变量矩阵:每个时间段一个哑变量
# 假设我们有T个时间段(比如按季度划分)
T = 40 # 10年,每季度一个
dummies = np.zeros((len(pairs_df), T))
for idx, row in pairs_df.iterrows():
t1_idx = get_quarter_index(row['t1']) # 自定义函数
t2_idx = get_quarter_index(row['t2'])
dummies[idx, t1_idx] = -1 # 卖出时哑变量为-1
dummies[idx, t2_idx] = 1 # 买入时哑变量为1
# 去掉第一个时间段作为基准
X = dummies[:, 1:]
y = pairs_df['log_return'].values
model = sm.OLS(y, X).fit()
alpha_estimates = model.params
# 计算指数值(基准时间段设为100)
index_values = np.zeros(T)
index_values[0] = 100
for t in range(1, T):
index_values[t] = index_values[t-1] * np.exp(alpha_estimates[t-1])
跑完回归,你会得到一条市场指数曲线。我2018年用这个方法算过中国当代艺术市场,发现2008-2012年那波暴涨暴跌,指数从100冲到280,又跌回120。跟实际市场感受完全吻合。
4.4 个体作品收益率计算
市场指数是「平均」,个体作品是「个体」。两者的差值,就是超额收益:
# 计算每件作品的超额收益
pairs_df['market_return'] = pairs_df.apply(
lambda row: np.log(index_values[get_quarter_index(row['t2'])] /
index_values[get_quarter_index(row['t1'])]),
axis=1
)
pairs_df['excess_return'] = pairs_df['log_return'] - pairs_df['market_return']
# 按作品聚合,得到每件作品的长期超额收益
work_performance = pairs_df.groupby('work_id')['excess_return'].mean().reset_index()
work_performance.columns = ['work_id', 'avg_excess_return']
这个超额收益,才是我们真正关心的东西。它告诉你:这幅画跑赢市场了吗?
我个人的经验是,超额收益在±5%以内的作品,基本可以视为「跟随市场」。超过±15%的,要么是黑马,要么是泡沫。2019年我跟踪过一批年轻艺术家的作品,其中一位的超额收益连续三年超过20%,后来果然被大画廊签约了。
4.5 知识体系总览
下面这张图,是我自己梳理的重复销售法完整流程。你看一眼就能明白整个逻辑链条:
4.6 几个必须注意的坑
做重复销售法这么多年,我踩过的坑不少。挑几个典型的说说:
- 样本选择偏差:只有频繁交易的作品才能进入模型。那些「只卖过一次」的作品被排除在外。这意味着指数可能偏向流动性好的作品,而低估了冷门作品的表现。
- 时间聚集效应:同一件作品在短期内频繁交易,可能是投机行为。我建议剔除一年内重复交易的数据,这些大多是短线炒作。
- 价格区间偏差:高价作品和低价作品的波动率不同。如果样本中高价作品占比过高,指数会被拉偏。可以按价格分层计算,再加权平均。
好了,重复销售法就讲到这里。这套方法虽然简单,但非常实用。我自己的投资组合里,有30%的仓位就是靠这个模型筛选出来的。数据不会说谎,关键是你怎么用它。