资管产品不是建个档案就一劳永逸的实体,它有明确的生死线:什么时候开始算份额、什么时候能申赎、什么时候必须终止。把这条生死线画出来,就是一个生命周期状态机。看似是业务流程文档的事,实际是所有运营系统的地基——《开放式基金 TA 系统总览》篇的份额登记、《估值核算总览》篇的净值流水线,都挂在状态的某一个分支上。状态定义错了,后面的系统再精细也是错得一致。
生命周期的七个状态
以一只契约型产品为例,状态可以归纳为:
草稿 -> 募集中 -> 已成立 -> 封闭运作 -> 开放运作 -> 清盘中 -> 已终止
每个迁移事件都有触发条件、账务动作和对外义务三样东西:
| 迁移 | 触发 | 账务动作 | 对外义务 |
|---|---|---|---|
| 募集中→已成立 | 募集达标、验资完成、合同生效 | 认购款转实收资本,生成初始份额 | 成立公告 |
| 已成立→封闭运作 | 合同约定的建仓期结束 | 建仓成本入账,开始每日估值 | 建仓期报告 |
| 封闭运作→开放运作 | 开放日窗口到 | 开放申赎登记,冻结解冻规则生效 | 开放公告 |
| 开放运作→清盘中 | 到期终止 / 持有人大会决议 / 触及清盘线 | 停止申赎,资产变现 | 清盘公告 |
| 清盘中→已终止 | 资产变现完毕、负债清偿、剩余财产分配 | 最后一笔净值,结清账务 | 清算报告 |
清盘线是个值得单独说的触发器:连续 N 个工作日持有人数不足、净资产低于合同阈值、或单位净值跌破预警线后未按期回复,系统要自动拉起清盘流程——这个"自动"很多团队用批任务扫描状态+触发条件实现,状态机框架里就是迁移守卫条件(guard condition)。
为什么必须显式建模状态
没有显式状态机的系统,生命周期信息散落在各个表的日期字段里:成立日期在 fund.found_date,开放日在赎回规则的字段里,清盘标记是个布尔位。问题是状态是互斥且有序的,散字段表达不了"这个产品已经清盘中不能再收申购"这种约束——申购接口要去查三个字段才能拼出当前状态,每加一个字段就多一个漏判点。
显式状态机的做法是 product_status 单字段 + 合法迁移表:
TRANSITIONS = {
"RAISING": ["FOUNDED", "RAISE_FAILED"],
"FOUNDED": ["CLOSED_OP"],
"CLOSED_OP":["OPEN_OP", "LIQUIDATING"],
"OPEN_OP": ["LIQUIDATING"],
"LIQUIDATING": ["TERMINATED"],
}
def transit(product, event):
nxt = TRANSITIONS.get(product.status, [])
if event.target not in nxt:
raise IllegalTransition(product.code, product.status, event.target)
...
所有 TA、估值、信披接口进入时先校验产品状态,非法状态直接拒绝——这比在每个接口写日期比较代码健壮得多。《委托生命周期状态机》篇在交易侧讲过同样的思想:状态机是异常分支的清单,迁移表写出来,漏掉的分支就无处藏身。
清盘:账务上最重的迁移
开放运作→清盘中是账务动作最密集的一段,可以拆成五步:
- 冻结申赎:TA 停止受理新申请,在途申请按合同约定处理(常见为确认最后一笔);
- 资产变现:持仓全部卖出(或协议转让),这是唯一一个由市场决定进度的步骤,清盘期长短主要看这里;
- 负债清偿:应付管理费、托管费、税费从变现资金中优先支付——顺序在合同里,系统按优先级队列出款;
- 剩余财产分配:按终止日份额比例分配,最后一笔净值的计算是"剩余财产 / 总份额",注意这时候净值和日常净值含义不同:日常净值是继续经营假设,终止净值是清算假设(《公允价值与估值模型》篇的资产变现折价在这里真实发生);
- 销户归档:TA 份额清零,估值系统归档,账套关闭,档案按年限留存。
第四步的分配有个工程细节:清盘最后一笔分配通常含尾差——剩余财产除不尽总份额。尾差处理要事先约定(常见是给最大持有人或归管理人),系统在最后一笔分录里手工指定差额归属,并留痕说明。这和《错账处理:红冲与蓝补》篇的"差额不许悄悄抹掉"是同一个原则。
状态机的运维价值
状态机还有一个被低估的用途:运营看板。把所有产品的状态分布按日统计,"募集中 12 只、清盘中 3 只"这类数字直接反映运营负载——清盘中的产品是每日手工操作的重灾区,看板能提前预警人力。再进一步,状态迁移的历史是产品档案的一部分,监管现场检查时调产品全生命周期记录(《压力测试与监管检查》篇的检查场景),状态机日志就是最完整的证据链。