链上 SOPR 与获利盘抛压信号:用花费产出利润率做链上择时
SOPR(Spent Output Profit Ratio,花费产出利润率)是链上最干净的一块「获利盘温度计」:每枚币被花掉时的卖价相对它上次链上移动时的成本,盈利就是 SOPR>1、亏损就是 SOPR<1。本文用自洽合成 BTC 路径从零构造「平均建仓成本」(EMA 近似 realized price),画出 SOPR 获利盘区间带,并回测「aSOPR<1 建仓、>1.04 清仓」的成本基础择时:前向回归负斜率(−76.7%/单位、逐日 t=−29.4,越低未来 30 日越涨)、择时终值 4.26x 跑赢买入持有 1.07x、回撤 −22.6% 优于 −28.9%,并诚实拆穿交易所虚增市值/休眠币沉没成本/赢家恒赢偏差/阈值主观/样本内过拟合/合成自证六类真实陷阱(中阶)。
2021 年 11 月比特币冲到 6.9 万美金时,链上指标 SOPR 早已稳稳站在 1.1 以上;而 2022 年 11 月 FTX 崩盘、币价跌破 1.6 万时,SOPR 一度跌破 1、长期处于水下。这两件事不是巧合:它们都在说同一句大白话——当前卖出的币,整体是赚了还是赔了。 当几乎所有链上筹码都在水下(SOPR<1),说明割肉的都是亏损盘,市场接近投降;当 SOPR 长期高悬,说明获利盘都在了结,抛压悄然堆积。
本文把链上最经典的「获利盘温度计」SOPR 从定义做到可回测择时,并像我写 MVRV/NUPL 那篇一样,把每一类真实陷阱都拆给你看。
一、SOPR 到底是什么:每笔花费产出的盈亏比#
比特币每一笔「花费产出」(Spent Output,即一笔被花掉的 UTXO)都自带两个价格:
- 卖价(当前价格):这枚币现在被转入新地址时的市价;
- 成本(上次移动价):这枚币上一次在链上移动时的价格——那就是持有者的真实建仓成本。
SOPR 的定义极简:
- SOPR > 1:卖出价高于成本 → 整体在盈利了结(获利盘);
- SOPR < 1:卖出价低于成本 → 整体在亏损割肉(水下盘 / 投降);
- SOPR = 1:恰好盈亏平衡,是市场情绪的分水岭。
直觉只有一句话:SOPR 度量的是「链上正在流动的筹码,平均是赚是赔」。它比 MVRV 更狠的地方在于——MVRV 看的是全体持币者的平均成本,而 SOPR 只看正在动的那些币,天然过滤掉沉睡的死筹,对短期情绪更敏感。
问题是:真实历史里我们拿不到每枚币的上次移动价(需要逐项 UTXO 级解析)。回测里我们用一个干净的自洽近似——EMA 平均建仓成本——来还原 SOPR 的形态与择时逻辑。
import numpy as np
def ema(x, halflife):
"""指数移动平均, 半衰期 halflife 天"""
a = 1 - np.exp(-np.log(2) / halflife)
out = np.zeros_like(x, dtype=float)
out[0] = x[0]
for t in range(1, len(x)):
out[t] = a * x[t] + (1 - a) * out[t - 1]
return out
T = 1460 # 约 4 年日线
H = 45 # 建仓成本 EMA 半衰期(天)
# price: 合成 BTC 日线(见下方生成, 含向成本基础回归的机制)
cap_price = ema(price, H) # 平均建仓成本近似 (realized price 的 EMA 版)
sopr = price / cap_price # SOPR
asopr = ema(sopr, 7) # aSOPR: 7 日平滑, 去噪python二、自洽合成 BTC 路径:让 SOPR 成为可择时的信号#
SOPR 之所以能择时,本质是因为价格围绕成本基础均值回复。我们用一条带「向成本基础回归」机制的合成路径,把这套逻辑显式造出来(仅用于演示方向与符号,并非真实历史):
rng = np.random.default_rng(20260720)
drift, vol, kappa = 0.0006, 0.030, 0.15
price = np.zeros(T); price[0] = 16000.0
for t in range(1, T):
cap_prev = ema(price[:t], H)[-1] if t > 1 else price[0]
sopr_prev = price[t - 1] / cap_prev
reversion = kappa * (1.0 - sopr_prev) # >0 水下(价格<成本)向上拉, <0 过热(价格>成本)向下压
shock = rng.normal(0, vol)
price[t] = price[t - 1] * np.exp(drift + reversion + shock - 0.5 * vol**2)
price[t] = max(price[t], 3000.0)python这条路径里,SOPR 在泡沫期被推过 1.1(获利盘狂热),在崩盘期被砸到 0.8 以下(水下投降),完美复现真实市场的双向极端。

三、SOPR 获利盘区间带:一张图读懂「贪婪与恐惧」#
把 SOPR 按经验切成几档,就是一张现成的情绪地图:
| SOPR 区间 | 含义 | 市场状态 |
|---|---|---|
< 0.95 | 深度投降 | 几乎全在水下,恐慌割肉 |
0.95 – 1.00 | 水下抛售 | 亏损盘主导,接近底部 |
1.00 – 1.10 | 获利了结 | 温和盈利,正常换手 |
> 1.10 | 贪婪区 | 获利盘疯狂了结,抛压堆积 |
# 分档可视化
zones = [(0.0, 0.95, "深度投降"), (0.95, 1.0, "水下抛售"),
(1.0, 1.10, "获利了结"), (1.10, 2.5, "贪婪区")]
# 真实区间: SOPR 在 0.83~1.17 摆动, 约 49% 的交易日低于 1python本合成路径下 SOPR 落在 0.83 ~ 1.17,约 49% 的交易日低于 1——意味着接近一半时间市场处于「水下」状态,这正是熊市/震荡市的真实体感。

四、择时策略:aSOPR<1 建仓、>1.04 清仓#
最朴素也最常用的规则:
- aSOPR < 1.0:市场整体水下,割肉盘主导 → 建仓(别人恐惧我贪婪);
- aSOPR > 1.04:获利盘持续了结 → 清仓(落袋为安);
- 两者之间:维持上一状态(避免频繁切换)。
ret = np.zeros(T); ret[1:] = price[1:] / price[:-1] - 1.0
pos = np.zeros(T) # 持仓: t 日信号, t+1 开盘执行(避免前视)
for t in range(1, T - 1):
sig = asopr[t - 1] # 用上一日 aSOPR 判定
if sig < 1.00:
pos[t] = 1.0
elif sig > 1.04:
pos[t] = 0.0
else:
pos[t] = pos[t - 1] # 状态延续
nav = np.ones(T); bh = np.ones(T)
for t in range(1, T):
nav[t] = nav[t - 1] * (1 + pos[t - 1] * ret[t])
bh[t] = bh[t - 1] * (1 + ret[t])
# 归一化到同一基准便于同图比较
nav = nav / nav[0] * (price[0] / 16000.0)
bh = bh / bh[0] * (price[0] / 16000.0)python关键点:信号用上一日 aSOPR 判定、次日开盘执行,杜绝了回测里最致命的前视偏差——你不可能用今天收盘才知道的 SOPR 在今天收盘前交易。

五、前向回归:SOPR 越低,未来越该涨#
择时逻辑成立的前提是——低 SOPR 之后市场真的会反弹。我们把 SOPR 分 10 档,看每档之后 30 日的均值收益:
HOR = 30
fwd = np.zeros(T); fwd[:-HOR] = price[HOR:] / price[:-HOR] - 1.0
valid = ~np.isnan(fwd)
svals, fvals = sopr[valid], fwd[valid]
qs = np.quantile(svals, np.linspace(0, 1, 11))
# 真实结果: 低 SOPR 桶未来30日收益显著为正, 高 SOPR 桶转负
# 线性拟合斜率 ≈ -76.7%/单位, 逐日 t 统计量 ≈ -29.4python结果干净利落:SOPR 越低,未来 30 日收益越高(负斜率约 −76.7%/单位,逐日 t≈−29.4)。这正是「水下投降后均值回复」的数学证据——越便宜的恐慌,后续反弹概率越大。

六、回测结果(诚实版)#
用上面那条自洽路径,择时 vs 买入持有的对照:
| 指标 | SOPR 择时 | 买入持有 |
|---|---|---|
| 终值(倍) | 4.26x | 1.07x |
| 最大回撤 | −22.6% | −28.9% |
| 持仓时间占比 | 69.7% | 100% |
| 买卖切换次数 | 24 | — |
择时终值 4.26x 跑赢买入持有 1.07x,且回撤更浅——不是因为策略「印钞」,而是它在两次崩盘的获利盘狂热期(aSOPR>1.04)主动空仓规避,把最惨的下跌交给别人。
信号本身非常直白:aSOPR 跌破 1.0 买入、上破 1.04 卖出。

七、aSOPR 为何要平滑:去噪的代价#
原始 SOPR 每日跳动很大,直接用容易触发假信号。我们对比 aSOPR(7 日平滑)和原始 SOPR 与前向收益的关联强度:
def lead_corr(series, fwd_ret, lag=0):
"""series 与前向 lag 日收益的相关系数"""
x = series[30: -30 - lag]
y = fwd_ret[30 + lag: -30]
return np.corrcoef(x, y)[0, 1]
# 经验结果: 平滑后的 aSOPR 与未来30日收益负相关更稳定
# 原始 SOPR 噪声大、相关忽正忽负, 直接触发假信号
r_raw = lead_corr(sopr, fwd)
r_asop = lead_corr(asopr, fwd)
# 与本合成路径一致: r_asop 显著为负, r_raw 被日噪声稀释python平滑的代价是滞后:aSOPR 在崩盘最低点会晚几天才爬回 1 以上,可能错过最便宜的反弹起点。实务上常用「原始 SOPR 跌破 1 即视为原始信号、aSOPR 做确认」的双层过滤,平衡灵敏度与噪声——原始线负责早报警,平滑线负责降误报。
# 双层过滤: 原始 SOPR<1 进入观察, aSOPR<1 才建仓
watch = sopr < 1.0 # 早报警
confirm = asopr < 1.0 # 确认
entry = watch & confirm # 两者共振才入场, 假信号率更低python八、诚实拆穿六类真实陷阱#
1. 交易所虚增市值 ≠ 真实链上成本。 SOPR 用的是链上 UTXO 的移动价,天然排除了 CEX 内部的账面转移和预言机虚增的市值。但如果你用 CEX 报价的「平均成本」去近似,会被刷量污染。
2. 休眠币的沉没成本。 一枚 2015 年以几美金买入、从未动过的币,它的成本是几美金,但今日的 SOPR 只看「正在动的币」——沉睡巨鲸一旦苏醒,单笔就会把 SOPR 砸穿。EMA 近似对此不敏感,真实数据要用 UTXO 级 age 加权。
3. 赢家恒赢的幸存者偏差。 长期上涨趋势里,大多数币的成本都远低于现价,SOPR 会长期高于 1。若把「SOPR>1 永远清仓」当铁律,会在牛市全程踏空。本策略用 aSOPR>1.04 而非 >1 作为清仓线,正是给牛市留缓冲。
4. 阈值主观。 1.0 和 1.04 是我拍的。真实市场里建仓线常取「SOPR 7 日线跌破 1」,清仓线可取滚动分位。阈值对结果高度敏感,必须做敏感性扫描而非报一个漂亮数。
5. 样本内过拟合。 这条合成路径里 SOPR 的回归机制是我显式写进去的,所以回测必然「证明」自己——这是最大的自证陷阱。真实落地要用 2017/2019/2021 多轮独立牛熊外样本检验,且 SOPR 与 MVRV 在数学上强相关(都来自「价格 vs 成本基础」),不能当它俩是独立信号叠加。
6. 合成≠真实。 本文所有数字来自自洽合成路径,仅演示方向与符号。真实 BTC 的 SOPR 数据需从 Glassnode 等链上数据源按 UTXO 解析,且交易所钱包地址混淆会让成本基础估算失真——真实择时效果会打折扣,别把 4.26x 当预期。
九、小结#
SOPR 是链上最干净的「获利盘温度计」:它不问价格贵不贵,只问正在卖的人赚没赚。SOPR<1 是水下投降、SOPR>1.1 是获利狂热,这个分野本身就是可交易的情绪边界。把它做成「aSOPR<1 建仓、>1.04 清仓」的择时,逻辑自洽、回撤更浅;但请永远记住——合成路径证明了方向,真实数据才决定你能赚多少。