PyCryptoBot / 回测与成本
两处偏差方向相反:模拟按最高手续费,却没有滑点模型
关于机器人的回测,最容易犯的错是只看一个数字。这一页把 PyCryptoBot 的成本口径拆成三层核查:数据与周期、手续费、滑点,并且如实报告一个反直觉的结论——它的非实盘模拟在摘要里直接写着 assuming highest fees(按最高手续费计算),未平仓交易还会被排除在保证金之外,看起来是偏保守的;但同一次核查也发现,3241 行的主控制器里 slippage、slippage_pct、slippage_model、latency 全部 0 命中,这一侧又偏乐观。两处偏差方向相反,意味着不能用一个「保守」或「乐观」的标签概括它的回测。
- PyCryptoBot v8.2.4(main=1fa9aaef,2024-03-04)
- Python 3.11(官方 Dockerfile 基线)
- 交易所范围:Binance / Coinbase / KuCoin
- 验证日期 2026-09-21
- 本站未实机运行机器人
requirements.txt、两份 config 样本,以及 controllers/PyCryptoBot.py 与 models/exchange/binance/api.py 的方法核查整理)。这是本站按源码整理的分层,不是官方口径;本站未实机运行回测。回测与模拟在代码里分别叫什么
先解决命名问题:这个项目里没有单独的「回测」模块,也没有一个叫 backtest 的命令。模拟是一组开关,散落在命令行参数与 config.json 里。下表是逐项核对的结果。
| 你要做的事 | 对应的开关或入口 | 依据 | 注意点 |
|---|---|---|---|
| 把机器人从实盘切回非实盘 | live(0/1) | 两份 config 样本 | 先确认它是 0 再谈其它,否则后面的成本讨论都没有意义 |
| 指定模拟的时间区间 | simstartdate / simenddate(ISO8601 字符串) | controllers/PyCryptoBot.py | simenddate 可以写 now;区间在数据里定位不到时程序会提示 Simulation data is invalid, unable to locate interval using date key. 并直接退出 |
| 只要结果、不要逐轮输出 | simresultonly | controllers/PyCryptoBot.py | 长区间模拟时能显著减少输出量 |
| 模拟里的周期自动切换 | sim_smartswitch | controllers/PyCryptoBot.py | CHANGELOG 里有多条针对 sim smartswitch 的修复记录,说明这一路径历史上不稳 |
| 导出逐笔交易明细 | 写入 csv/ 目录,默认文件名 trades.csv | controllers/PyCryptoBot.py(目录创建与 to_csv 调用处) | 这是你唯一能逐笔核对的产物,比看汇总数字有用得多 |
| 生成图表 | graphs | 两份 config 样本 | 依赖 matplotlib;容器里官方已单独设了 MPLCONFIGDIR |
| 只监控不下单 | manual_trades_only | config / CHANGELOG 6.4.0 | 适合先观察信号质量,交易由你手动做 |
| 覆盖卖出触发条件 | --selltriggeroverride | controllers/PyCryptoBot.py | 注意 CHANGELOG 里历史写法少了一个字母(selltriggeroveride),照抄历史文档可能对不上 |
| 批量扫市场而不是单币对 | scanner.py(129 行)与 screener.py(421 行,用 tradingview_ta) | 仓库文件实测 | 这两个脚本没有命令行参数,靠 JSON 配置驱动,与主机器人是**并行进程** |
手续费这一层从哪里来?为什么不是本地硬编码
这一层的实现比多数人以为的细致:费率不是写死的常量,而是从交易所取回、并在成交后回读实际扣费。真正需要你注意的是非实盘模拟用的是什么费率。
| 成本项 | 项目里的对应实现 | 依据 | 现状与注意点 |
|---|---|---|---|
| 买入侧费率 | 买入分支里调用 api.get_maker_fee() 参与金额计算 | controllers/PyCryptoBot.py 的 market_buy() | 不同交易所实现不同,需按你用的交易所核对 |
| 卖出侧费率 | use_sell_fee 被传进 api.market_sell(..., use_fees=...) | controllers/PyCryptoBot.py + 两份 config 样本 | use_sell_fee 在标准样本里就有,属于常用开关而非隐藏项 |
| 实际成交费率回收 | 从交易所订单回执里读 fee,写入 state.last_buy_fee | controllers/PyCryptoBot.py 中比较 last_buy_fee 与 exchange_last_buy["fee"] 的逻辑 | 说明它关心的是**真实扣费**,不是估算值 |
| 费率查询方法族 | get_fees / get_maker_fee / get_taker_fee / get_trade_fee | models/exchange/binance/api.py(1458 行)方法清单 | 四家交易所的 api.py 都有各自实现,覆盖度需逐个核对 |
| 费率端点缺失时的回退 | 某交易所缺少 tradeFee 端点时返回默认费率 | CHANGELOG 3.2.13 | 这条说明「拿不到费率」不一定报错,可能静默用一个默认值——留档时要写清 |
| 非实盘模拟使用的费率 | 模拟摘要行文本为 (non-live simulation, assuming highest fees) | controllers/PyCryptoBot.py 的汇总输出 | 这是本页最重要的一条:模拟不是按你的实际费率,而是按最高费率 |
| 费用累计字段 | state.feetracker | controllers/PyCryptoBot.py 的 state 字段 | 可与逐笔明细交叉验证 |
滑点这一层为什么仓库里没有?偏差方向又会怎样
本站对 controllers/PyCryptoBot.py(3241 行)做了关键词核查:slippage、slippage_pct、slippage_model、latency 全部 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:待填写
运行时间:待填写
怎么跑一次口径清楚的模拟?五步
下面五步的目标不是拿到好看的数字,而是拿到一份你能向别人解释清楚的记录。所有步骤都在非实盘模式下进行。
从样本复制一份配置,别直接改样本
命令:把
config.json.sample复制成你自己的config.json。说明:样本是仓库文件,改动会在下次git pull时产生冲突。预期输出:目录下出现你自己的config.json,样本保持原样。确认
live为 0,并只启用一个交易所与一个币对命令:在你的
config.json里核对live的取值,并只保留你要测的那一个交易所段。说明:一次只测一个变量,否则后面出问题无法归因。预期输出:启动日志里显示的交易所与币对只有一个。固定周期,写好模拟区间
命令:设置
granularity(清单见本页第四张表所依据的 7 档枚举)并设置simstartdate/simenddate。说明:区间端点建议用绝对日期,避免用now造成每次结果不同。预期输出:日志中的迭代从你指定的起始时间开始推进。跑一轮,保留日志与
csv/目录命令:以非实盘模式启动,必要时开启
simresultonly降低输出量。说明:csv/trades.csv是后续逐笔核对的基础,日志是排查「为什么没交易」的唯一线索。预期输出:模拟结束后打印汇总表,摘要中出现assuming highest fees与未平仓相关提示,csv/目录下生成交易明细。按卡片填记录,再换一个变量重跑
命令:把上一步的回测卡片模板逐项填满,然后只改一个变量(例如周期或区间)重跑。说明:一次只改一个变量才能看出每个选择带来的变化;这也是判断结果是否稳健的最省力方法。预期输出:两份字段齐全、只有一处不同的卡片,可并排比较。
关于回测与成本口径的高频问题
本页结论来自对 controllers/PyCryptoBot.py(3241 行)、两份 config 样本、requirements.txt 与各交易所 api.py 的源码核查;本站没有实机运行过模拟,也没有做过与实盘的对照,具体行为请以你机器上的实际输出为准。
PyCryptoBot 有真正的回测吗?
它提供的是非实盘的模拟模式,由一组开关驱动(live、simstartdate、simenddate、simresultonly 等),而不是一个独立的回测命令或引擎。你需要自己指定区间、周期、币对,模拟结束后自行导出成交明细并留档。把它当作「可复现的模拟运行」比当作「回测框架」更准确——两者在字段留档与基准对照上的要求不一样。
手续费到底从哪来?会不会被写死?
不是写死的常量。项目里有费率查询方法族(get_fees / get_maker_fee / get_taker_fee / get_trade_fee),买入分支会用到 maker 费率,卖出侧由 use_sell_fee 控制,成交后还会从交易所订单回执里回读实际扣费存入状态字段。但要注意两点:一是某交易所缺少费率端点时会回退到默认费率(见 CHANGELOG 3.2.13);二是非实盘模拟摘要明确写着按最高费率计算,与你的实际档位不一定一致。
为什么说它没有滑点参数?
因为在主控制器(3241 行)里,slippage、slippage_pct、slippage_model、latency 这几个关键词全部 0 命中;两份 config 样本与 requirements.txt 里也没有对应参数。也就是说它不建模滑点,也不建模成交延迟。这不代表代码有错,而是说这一层成本必须由你在记录里显式补上,或者至少标注为「未计入」。
既然模拟按最高费率,是不是说明模拟结果一定偏保守?
不能这样概括,因为还有反方向的一层。手续费侧按最高费率算,确实偏保守;但滑点侧完全没有模型,对高换手策略是明显偏乐观。两处偏差方向相反,最终偏向哪一侧取决于你的策略换手频率与单笔规模。低频低换手的策略更容易落在「保守」一侧,高频高换手则更容易落在「乐观」一侧。本站没有做实盘对照,无法给出具体数值。
模拟结果和实盘会差多少?
本站无法回答这个数字——既没有实机跑过模拟,也没有任何实盘记录。可以明确的是差异至少来自三处:非实盘使用最高费率、没有滑点与延迟模型、以及实盘还有密钥权限、最小下单量、订单精度、拒单与断线等模拟里不存在的环节。要做对照,建议用同一区间与同一币对,按「费率」与「成交价」两部分分别记录差异,而不是比较一个总收益数字。
怎么避免未来函数?
这个项目的结构里有一点对避免未来函数有利:数据刷新与判断都发生在K 线收盘时(主控制器里有 closed_candle_row 与「只在收盘刷新」的注释),而不是用未走完的当根 K 线做决策。但结构有利不等于你的自定义策略就没问题——如果自己在自定义策略里引入了未来数据,框架不会替你检查。建议的做法是把信号整体延后一期重跑,看结论是否大幅变化。
本站对这些结论验证到什么程度?
只做到源码与配置层面的核对:读文件、数关键词、比对方法名与摘要文本。没有实机安装、没有运行模拟、没有连接任何交易所账户、没有下过任何委托、没有做过与实盘的对照。凡是需要运行才能确认的行为(例如按哪一档费率取值、模拟耗时、输出字段的实际数值),本页一律只描述字段与文本,不给数值结论。