Skip to content

回测引擎最小模型:现金、持仓与成交 ​

写出买卖规则以后,很多人会立刻问:“那到底赚不赚钱?”别急,先要有一台能按规则记账的机器。就像开小卖部,不是喊一句“今天卖得不错”就行,现金还剩多少、货架上还有多少货、每一笔买卖有没有记清,都要对得上。

这是什么 ​

回测引擎,就是用程序模拟现金、持仓、成交和账户净值变化的一套记账机器。它不负责替你发明好策略,只负责按你写好的量化策略规则,一天一天往前走:该买就扣现金加持仓,该卖就减持仓加现金,每天算一次账户还值多少钱。

如果把量化策略规则比作“进货和清货的规矩”,回测引擎就是收银台加库存本。规矩再漂亮,收银台记错钱、库存本记错货,最后看到的盈利和亏损都不可信。

一个最小回测引擎至少要管四张账:

账本记录什么为什么重要
cash现金还剩多少没现金就不能继续买入
position持仓数量和成本不知道持仓,就不知道能不能卖
trades每一笔成交方便复盘买卖是否按规则发生
equity账户净值现金 + 持仓按当日价格估算后的总价值

经验

第一版回测引擎越小越好。先让一只教学样本股票、几天行情、几笔买卖完整跑通,再扩大范围。

为什么不能只看买卖信号 ​

买卖信号只是“想做什么”,回测引擎要处理的是“做完以后账户变成什么”。这两个差得很远。

比如策略在 2024-01-10 给出买入信号,如果账户只有 1 万元现金,就不能按 5 万元去买;如果买入当天普通股票遵守 T+1,就不能假装当天又卖掉;如果已经满仓,再出现买入信号,也不能继续凭空加仓。

散户手动交易时,经常忽略这些细节,感觉“这里买、那里卖”好像挺顺。程序一旦认真记账,很多漂亮想法会露馅:现金不够、仓位超了、卖出日期不对、持仓数量对不上。

所以最小回测引擎的存在意义,不是让曲线好看,而是把交易规则和仓位控制落到每一天的账户变化里。

用一笔买卖看清账怎么走 ​

还是用贵州茅台(600519,白酒行业,上交所主板,2001 年 8 月 27 日上市)作为真实 A 股公司例子;下面数字是教学样本,不代表真实历史行情。

假设账户初始现金 10 万元,策略规定单只股票最多 20% 仓位。教学样本数据如下:

datecodeclosesignalcan_sell
2024-01-10SAMPLE100.00buyno
2024-01-11SAMPLE102.00holdyes
2024-01-12SAMPLE98.00sellyes

这里为了展示记账流程,假设第一天的信号可在同日按 100.00 元成交;真实回测如果使用收盘后数据生成信号,通常应在下一交易日执行,避免偷看当天收盘价。

第一天出现买入信号,20% 仓位就是 2 万元。按 100.00 元买入,先不考虑额外成本,能买 200 股。账户变化是:现金从 10 万元变成 8 万元,持仓从 0 股变成 200 股。

第二天没有卖出信号,持仓不变。账户净值大约是 8 万元现金 + 200 股 × 102.00 元 = 10.04 万元。

第三天出现卖出信号,且 can_sell 是 yes,说明这笔持仓已经不是当天刚买入,可以卖。按 98.00 元卖出 200 股,现金变成 9.96 万元,持仓归零。这次从买入到卖出亏了 400 元。

这就是回测引擎最朴素的价值:它把“我觉得这里可以买卖”变成一串能核对的账。

最小代码长什么样 ​

先别追求复杂结构。一个入门版可以把账户状态放在一个小字典里:

python
account = {
    "cash": 100_000,
    "shares": 0,
    "buy_date": None,
}

每天读一行行情和信号,然后按顺序处理:

python
def run_one_day(account, row):
    price = row["close"]

    if row["signal"] == "buy" and account["shares"] == 0:
        budget = account["cash"] * 0.2
        shares = int(budget // (price * 100)) * 100
        account["cash"] -= shares * price
        account["shares"] += shares
        account["buy_date"] = row["date"]

    elif row["signal"] == "sell" and account["shares"] > 0 and row["can_sell"]:
        account["cash"] += account["shares"] * price
        account["shares"] = 0
        account["buy_date"] = None

    equity = account["cash"] + account["shares"] * price
    return equity

这段代码故意很直白:先看现金,决定能买多少;再看持仓和 can_sell,决定能不能卖;最后用当天收盘价估算账户净值。这里把买入数量按 100 股整数倍取整,是为了更接近 A 股普通股票的买入习惯;更严格的版本还要预留交易费用,不能把每一分钱都假装能买成股票。

这里有三个关键点。

第一,买入要先扣现金。不能只记录“我买了”,却不减少现金。

第二,卖出要先检查持仓。没有持仓就不能卖,普通股票当天买入也不能假装当天卖出。

第三,每天都要算账户净值。只看最后一次卖出结果,会漏掉中间的浮盈、浮亏和回撤压力。

新手最容易踩的坑 ​

第一个坑,是只记录信号,不记录成交。信号说买,不代表一定买得到;现金不够、仓位限制、交易规则,都可能让这笔买入不能发生。

第二个坑,是忘了 T+1。当天买入的普通股票,通常不能当天卖出。如果回测引擎不检查 can_sell,就可能做出真实交易里做不到的动作。

第三个坑,是账户净值算错。有人只看现金,忘了持仓还值钱;有人只看持仓市值,忘了现金也属于账户。正确的最小口径是:现金 + 持仓按当日价格估算的价值。

风险

方向性做法:第一版回测引擎建议先保守,宁愿少算一点机会,也不要让程序做出现实里无法成交的动作。适用条件是你在搭最小模型;反例是为了让结果更好看,放宽 T+1、忽略仓位、让现金变成负数;风险是你会把程序漏洞误认为策略能力。

主力和机构的优势,不只是策略多,也在于每一笔账都有人盯。个人做不到那么复杂,但至少要做到:现金不能凭空多,持仓不能凭空变,成交必须有日期和价格,账户净值每天能对上。

大坑

不要让回测引擎“替你圆梦”。只要出现现金为负、没有持仓却能卖出、当天买当天卖普通股票这类情况,结果再漂亮也要先停下来查账。

总结 ​

回测引擎是量化研究里的记账机器:它按量化策略规则处理买入、卖出和仓位,把现金、持仓、成交和账户净值一天天算清楚。第一版不用复杂,重点是账要对:买入扣现金,卖出减持仓,T+1 不乱来,账户净值每天能核对。

自检 3 问:

  1. 为什么只有买卖信号还不够,必须记录现金和持仓?
  2. 普通股票 T+1 会怎样影响回测引擎里的卖出判断?
  3. 账户净值为什么要同时包含现金和持仓价值?

股市有风险,入市需谨慎。本文不构成任何投资建议。

本章新学到:回测引擎。

内容仅用于学习交流,不构成任何投资建议。股市有风险,入市需谨慎。