PyCryptoBot / 回测与成本

两处偏差方向相反:模拟按最高手续费,却没有滑点模型

关于机器人的回测,最容易犯的错是只看一个数字。这一页把 PyCryptoBot 的成本口径拆成三层核查:数据与周期、手续费、滑点,并且如实报告一个反直觉的结论——它的非实盘模拟在摘要里直接写着 assuming highest fees(按最高手续费计算),未平仓交易还会被排除在保证金之外,看起来是偏保守的;但同一次核查也发现,3241 行的主控制器里 slippageslippage_pctslippage_modellatency 全部 0 命中,这一侧又偏乐观。两处偏差方向相反,意味着不能用一个「保守」或「乐观」的标签概括它的回测

依据:controllers/PyCryptoBot.py(3241 行)依据:config 两份样本依据:binance/api.py 的费率方法本站未实机回测
本文验证环境
  • PyCryptoBot v8.2.4(main=1fa9aaef,2024-03-04)
  • Python 3.11(官方 Dockerfile 基线)
  • 交易所范围:Binance / Coinbase / KuCoin
  • 验证日期 2026-09-21
  • 本站未实机运行机器人
数据与周期交易所取数、K 线只在收盘刷新、周期只支持 7 档
手续费来源来自交易所接口,非本地硬编码;非实盘按最高费率
滑点(仓库无参数)主控制器内 slippage / latency 全部 0 命中
结果与基准保证金汇总、未平仓排除、交易明细导出
回测成本分层示意(依据 requirements.txt、两份 config 样本,以及 controllers/PyCryptoBot.pymodels/exchange/binance/api.py 的方法核查整理)。这是本站按源码整理的分层,不是官方口径;本站未实机运行回测。
入口与开关

回测与模拟在代码里分别叫什么

先解决命名问题:这个项目里没有单独的「回测」模块,也没有一个叫 backtest 的命令。模拟是一组开关,散落在命令行参数与 config.json 里。下表是逐项核对的结果。

你要做的事对应的开关或入口依据注意点
把机器人从实盘切回非实盘live(0/1)两份 config 样本先确认它是 0 再谈其它,否则后面的成本讨论都没有意义
指定模拟的时间区间simstartdate / simenddate(ISO8601 字符串)controllers/PyCryptoBot.pysimenddate 可以写 now;区间在数据里定位不到时程序会提示 Simulation data is invalid, unable to locate interval using date key. 并直接退出
只要结果、不要逐轮输出simresultonlycontrollers/PyCryptoBot.py长区间模拟时能显著减少输出量
模拟里的周期自动切换sim_smartswitchcontrollers/PyCryptoBot.pyCHANGELOG 里有多条针对 sim smartswitch 的修复记录,说明这一路径历史上不稳
导出逐笔交易明细写入 csv/ 目录,默认文件名 trades.csvcontrollers/PyCryptoBot.py(目录创建与 to_csv 调用处)这是你唯一能逐笔核对的产物,比看汇总数字有用得多
生成图表graphs两份 config 样本依赖 matplotlib;容器里官方已单独设了 MPLCONFIGDIR
只监控不下单manual_trades_onlyconfig / CHANGELOG 6.4.0适合先观察信号质量,交易由你手动做
覆盖卖出触发条件--selltriggeroverridecontrollers/PyCryptoBot.py注意 CHANGELOG 里历史写法少了一个字母(selltriggeroveride),照抄历史文档可能对不上
批量扫市场而不是单币对scanner.py(129 行)与 screener.py(421 行,用 tradingview_ta仓库文件实测这两个脚本没有命令行参数,靠 JSON 配置驱动,与主机器人是**并行进程**
为什么先讲命名:中文教程里常见的说法是「跑一次回测」,但在仓库里你要先决定三件事——用哪些开关、区间怎么写、结果存到哪。把这三件事写进记录,后面的口径对比才有基础。
成本第一层

手续费这一层从哪里来?为什么不是本地硬编码

这一层的实现比多数人以为的细致:费率不是写死的常量,而是从交易所取回、并在成交后回读实际扣费。真正需要你注意的是非实盘模拟用的是什么费率

成本项项目里的对应实现依据现状与注意点
买入侧费率买入分支里调用 api.get_maker_fee() 参与金额计算controllers/PyCryptoBot.pymarket_buy()不同交易所实现不同,需按你用的交易所核对
卖出侧费率use_sell_fee 被传进 api.market_sell(..., use_fees=...)controllers/PyCryptoBot.py + 两份 config 样本use_sell_fee 在标准样本里就有,属于常用开关而非隐藏项
实际成交费率回收从交易所订单回执里读 fee,写入 state.last_buy_feecontrollers/PyCryptoBot.py 中比较 last_buy_feeexchange_last_buy["fee"] 的逻辑说明它关心的是**真实扣费**,不是估算值
费率查询方法族get_fees / get_maker_fee / get_taker_fee / get_trade_feemodels/exchange/binance/api.py(1458 行)方法清单四家交易所的 api.py 都有各自实现,覆盖度需逐个核对
费率端点缺失时的回退某交易所缺少 tradeFee 端点时返回默认费率CHANGELOG 3.2.13这条说明「拿不到费率」不一定报错,可能静默用一个默认值——留档时要写清
非实盘模拟使用的费率模拟摘要行文本为 (non-live simulation, assuming highest fees)controllers/PyCryptoBot.py 的汇总输出这是本页最重要的一条:模拟不是按你的实际费率,而是按最高费率
费用累计字段state.feetrackercontrollers/PyCryptoBot.py 的 state 字段可与逐笔明细交叉验证
「按最高费率」的含义要讲准确:它意味着非实盘模拟的收益表现会比你按真实费率算出来的更差,也就是这一侧偏保守。这与「回测总是偏乐观」的常见印象相反。但保守不等于可信——它只解决了一侧偏差,下一层就是另一侧。另外本站只读到了摘要文本与相关字段,没有实机跑过一次模拟,具体按哪一档费率取值请以你机器上的输出为准。
成本第二层

滑点这一层为什么仓库里没有?偏差方向又会怎样

本站对 controllers/PyCryptoBot.py(3241 行)做了关键词核查:slippageslippage_pctslippage_modellatency 全部 0 命中;两份 config 样本与 requirements.txt 里也没有对应参数。也就是说,不仅没有滑点模型,连成交延迟也没有建模。

缺失滑点会让你看到什么为什么会这样你怎么自查可以怎么缓解
换手越高,结果越偏乐观每次进出都按理论价成交,摩擦成本为零统计区间内的交易笔数与换手频率按成交名义额加若干基点做一次压力测试,看结论是否还成立
单笔金额越大,偏差越大没有盘口深度与冲击成本模型看单笔名义额占该币对日成交量的比例主动降低单笔规模或拆分下单后再跑一次对比
震荡市段的偏差被放大震荡市信号密集、成交次数多,累计偏差随之放大把区间切成趋势段与震荡段分别模拟用足够长的区间覆盖至少一段震荡行情
流动性差的币对上尤其不可信价差与深度没有进入模型观察不同币对的买卖价差先在主流币对上验证逻辑,再考虑小币种
模拟与实盘的差异无法归因缺滑点,也缺延迟与部分成交模型用同一区间、同一币对做模拟与小额实盘的对照把差异拆成费率与成交价两部分分别记录
结论无法复现没有可留档的滑点参数检查你的记录里有没有「滑点假设」这一栏在回测卡片里显式写「本项未计入」,把它当成已知缺口而不是零成本
把两层偏差合起来看:手续费侧偏保守(按最高费率),滑点侧偏乐观(完全没有模型)。对低频、低换手的策略,第一层的影响更大,模拟结果可能比实盘还差;对高频、高换手的策略,第二层会迅速反超,模拟结果反而好于实盘。判断自己的策略落在哪一侧,比问「它的回测准不准」更有用。本站未实机回测,也没有做过与实盘的对照,以上为基于源码结构的推断。
成本第三层

结果与基准要一起留档什么?汇总数字之外还缺哪些

模拟结束时程序会打印一张汇总表,并在其中给出两条容易被忽略的限定说明。如果你只抄走一个数字,等于把这两条说明丢掉了。

要记录的东西为什么必须记录在项目里对应哪里
数据来源与交易所同一标的在不同交易所的价位与成交量不同,换交易所等于换数据你启用的那一个交易所的 models/exchange/*/api.py
K 线周期周期直接决定信号频率与换手models/exchange/Granularity.py,只支持 7 档(1m / 5m / 15m / 30m / 1h / 6h / 1d)
模拟区间与端点含义区间不同结论可能相反;端点写 now 会随运行时间漂移simstartdate / simenddate
计价币种与资金规模决定保证金与仓位的基数config 的 base_currency / quote_currency 与相关规模开关
手续费假设非实盘按最高费率,与你的实际档位不一定一致模拟摘要里的 assuming highest fees 标注
是否含未平仓交易未平仓交易会被排除在保证金计算之外,看汇总数字时必须知道这一点摘要中的 Open Trade Margin 行与结束提示;state 里的 open_trade_margin
滑点与延迟假设仓库没有对应参数,这一栏必须由你自己填无对应实现,需外部记录
基准对照没有基准就无法判断是在创造收益还是只是在跟随市场仓库内未发现基准参数,需自行另算
逐笔交易明细汇总数字看不出收益是否集中在少数几笔csv/trades.csv

回测卡片模板(复制到你的记录里,逐项填)

标的:待填写(例如 BTC/USDT)
交易所:待填写
K 线周期:待填写(7 档之一)
模拟区间:待填写(ISO8601,注明端点是否含 now)
计价币种 / 资金规模:待填写
策略与关键开关:待填写(买入过滤项、卖出风控项)
手续费假设:非实盘默认按最高费率(来源:模拟摘要标注)
滑点假设:未计入(仓库无对应参数)—— 待填写你自己补充的口径
成交延迟假设:未计入(仓库无对应参数)
基准对照:待填写(仓库无基准参数,需自行另算)
未平仓处理:结束时的未平仓交易不计入保证金汇总
版本与 commit:待填写
运行时间:待填写
为什么把模板放在页面上:这个项目的记录缺口不在数据,而在字段。它不会替你写下用了哪个周期、按什么费率、有没有把未平仓算进去。把这张卡片填满,你就已经比大多数只贴一张图的做法可信得多。
操作路径

怎么跑一次口径清楚的模拟?五步

下面五步的目标不是拿到好看的数字,而是拿到一份你能向别人解释清楚的记录。所有步骤都在非实盘模式下进行。

  1. 从样本复制一份配置,别直接改样本

    命令:把 config.json.sample 复制成你自己的 config.json。说明:样本是仓库文件,改动会在下次 git pull 时产生冲突。预期输出:目录下出现你自己的 config.json,样本保持原样。

  2. 确认 live 为 0,并只启用一个交易所与一个币对

    命令:在你的 config.json 里核对 live 的取值,并只保留你要测的那一个交易所段。说明:一次只测一个变量,否则后面出问题无法归因。预期输出:启动日志里显示的交易所与币对只有一个。

  3. 固定周期,写好模拟区间

    命令:设置 granularity(清单见本页第四张表所依据的 7 档枚举)并设置 simstartdate / simenddate。说明:区间端点建议用绝对日期,避免用 now 造成每次结果不同。预期输出:日志中的迭代从你指定的起始时间开始推进。

  4. 跑一轮,保留日志与 csv/ 目录

    命令:以非实盘模式启动,必要时开启 simresultonly 降低输出量。说明:csv/trades.csv 是后续逐笔核对的基础,日志是排查「为什么没交易」的唯一线索。预期输出:模拟结束后打印汇总表,摘要中出现 assuming highest fees 与未平仓相关提示,csv/ 目录下生成交易明细。

  5. 按卡片填记录,再换一个变量重跑

    命令:把上一步的回测卡片模板逐项填满,然后只改一个变量(例如周期或区间)重跑。说明:一次只改一个变量才能看出每个选择带来的变化;这也是判断结果是否稳健的最省力方法。预期输出:两份字段齐全、只有一处不同的卡片,可并排比较。

FAQ

关于回测与成本口径的高频问题

本页结论来自对 controllers/PyCryptoBot.py(3241 行)、两份 config 样本、requirements.txt 与各交易所 api.py 的源码核查;本站没有实机运行过模拟,也没有做过与实盘的对照,具体行为请以你机器上的实际输出为准。

PyCryptoBot 有真正的回测吗?

它提供的是非实盘的模拟模式,由一组开关驱动(livesimstartdatesimenddatesimresultonly 等),而不是一个独立的回测命令或引擎。你需要自己指定区间、周期、币对,模拟结束后自行导出成交明细并留档。把它当作「可复现的模拟运行」比当作「回测框架」更准确——两者在字段留档与基准对照上的要求不一样。

手续费到底从哪来?会不会被写死?

不是写死的常量。项目里有费率查询方法族(get_fees / get_maker_fee / get_taker_fee / get_trade_fee),买入分支会用到 maker 费率,卖出侧由 use_sell_fee 控制,成交后还会从交易所订单回执里回读实际扣费存入状态字段。但要注意两点:一是某交易所缺少费率端点时会回退到默认费率(见 CHANGELOG 3.2.13);二是非实盘模拟摘要明确写着按最高费率计算,与你的实际档位不一定一致。

为什么说它没有滑点参数?

因为在主控制器(3241 行)里,slippageslippage_pctslippage_modellatency 这几个关键词全部 0 命中;两份 config 样本与 requirements.txt 里也没有对应参数。也就是说它不建模滑点,也不建模成交延迟。这不代表代码有错,而是说这一层成本必须由你在记录里显式补上,或者至少标注为「未计入」。

既然模拟按最高费率,是不是说明模拟结果一定偏保守?

不能这样概括,因为还有反方向的一层。手续费侧按最高费率算,确实偏保守;但滑点侧完全没有模型,对高换手策略是明显偏乐观。两处偏差方向相反,最终偏向哪一侧取决于你的策略换手频率与单笔规模。低频低换手的策略更容易落在「保守」一侧,高频高换手则更容易落在「乐观」一侧。本站没有做实盘对照,无法给出具体数值。

模拟结果和实盘会差多少?

本站无法回答这个数字——既没有实机跑过模拟,也没有任何实盘记录。可以明确的是差异至少来自三处:非实盘使用最高费率、没有滑点与延迟模型、以及实盘还有密钥权限、最小下单量、订单精度、拒单与断线等模拟里不存在的环节。要做对照,建议用同一区间与同一币对,按「费率」与「成交价」两部分分别记录差异,而不是比较一个总收益数字。

怎么避免未来函数?

这个项目的结构里有一点对避免未来函数有利:数据刷新与判断都发生在K 线收盘时(主控制器里有 closed_candle_row 与「只在收盘刷新」的注释),而不是用未走完的当根 K 线做决策。但结构有利不等于你的自定义策略就没问题——如果自己在自定义策略里引入了未来数据,框架不会替你检查。建议的做法是把信号整体延后一期重跑,看结论是否大幅变化。

本站对这些结论验证到什么程度?

只做到源码与配置层面的核对:读文件、数关键词、比对方法名与摘要文本。没有实机安装、没有运行模拟、没有连接任何交易所账户、没有下过任何委托、没有做过与实盘的对照。凡是需要运行才能确认的行为(例如按哪一档费率取值、模拟耗时、输出字段的实际数值),本页一律只描述字段与文本,不给数值结论。

下一步:怎么让机器人能被远程控制?

模拟跑通之后,多数人会先接上通知与控制界面,而不是立刻接实盘。这个项目提供了三种控制面,职责差别不小,选错会让后面的排查变得困难。