《自己动手做套利机器人》课程第 16 课,也是最后一课。机器人会扫描、确定交易大小、模拟成交、以纸面方式运行,并在触及限制时停止。今天你会把它变成其他人可以阅读、运行和判断的东西。
直接的方法
展示机器人的直白方式,是发一张终端截图,上面有一个绿色数字,或发一条消息:“它这周赚了 40 美元”。截图无法核验、无法复现,还会隐藏一切重要信息:交易有多少笔,最差的一笔有多大,设置是什么,是否用了真钱。
发布会在别人提问前回答这些问题。它有四部分:日志、报告、README 和仓库。每一部分都不到一小时。
第 1 部分:记录每个决策的日志
机器人会为每一笔纸面交易向文件追加一行。一行是一个小型 JSON 对象。这种格式称为 JSON Lines:每行一个对象,容易追加,也容易读回。记录的不只是利润:时间、交易对、大小、价格和当时启用的设置。没有记录的内容,以后就无法解释。
第 2 部分:读取日志的报告
一个小脚本读取日志,并先打印访问者最想看到的数字:交易数、盈利交易数、总计和最差的单笔交易。最差交易和总计一样重要。接着同一个脚本会写出一个网页。
import json, random, html
random.seed(21)
# 1. the bot appends one line per decision to trades.jsonl (here we fake 12 paper trades)
with open("trades.jsonl", "w") as f:
for i in range(12):
size = round(random.choice([0.5, 1.0]), 1)
profit = round(random.gauss(0.03, 0.12) * size, 4)
f.write(json.dumps({"n": i + 1, "pair": "BTC/USDT", "size": size, "profit": profit}) + "\n")
# 2. the report reads the log and prints the numbers a visitor wants first
trades = [json.loads(line) for line in open("trades.jsonl")]
total = sum(t["profit"] for t in trades)
wins = sum(t["profit"] > 0 for t in trades)
worst = min(t["profit"] for t in trades)
print(f"trades {len(trades)} wins {wins} win rate {wins/len(trades):.0%}")
print(f"total profit {total:+.4f} worst trade {worst:+.4f}")
# 3. the same numbers as a page you can open in a browser or publish
rows = "".join(f"<tr><td>{t['n']}</td><td>{html.escape(t['pair'])}</td><td>{t['size']}</td><td>{t['profit']:+.4f}</td></tr>" for t in trades)
page = f"""<!doctype html><meta charset="utf-8"><title>Paper bot report</title>
<h1>Paper bot report</h1>
<p>Trades: {len(trades)}. Win rate: {wins/len(trades):.0%}. Total profit: {total:+.4f}. Worst trade: {worst:+.4f}.</p>
<p><b>Paper mode: no real money.</b></p>
<table border="1" cellpadding="4"><tr><th>#</th><th>pair</th><th>size</th><th>profit</th></tr>{rows}</table>"""
open("report.html", "w").write(page)
print("wrote report.html,", len(page), "bytes")
输出:
trades 12 wins 6 win rate 50%
total profit +0.0432 worst trade -0.2725
wrote report.html, 1088 bytes
在浏览器中打开 report.html。把这两个数字放在一起看。一半交易盈利,总计略为正数,但最差的一笔交易(−0.2725)是总利润(0.0432)的六倍。一笔坏交易就会抹掉整个运行。一条像“profit +0.0432”这样的单独数字会掩盖这一点。这就是报告将最差交易放在总计旁边打印的原因。
这里的虚构交易是随机数,只是为了给脚本一些可读取的内容。在你的机器人中,日志来自第 12 课的纸面账户。
第 3 部分:README
README 是仓库的第一页。访问者读它十秒,就会决定继续还是离开。一个好的 README 应按这个顺序包含以下部分:
- 一句话:机器人做什么。“一个在两个交易所之间寻找价格缺口的纸面交易机器人。”
- 状态行:“纸面模式。没有真钱。不是财务建议。”
- 如何运行:三条可在干净机器上运行的命令,安装、复制示例配置、启动。
- 你会看到什么:一次运行的输出,或报告页面的链接。
- 它如何工作:第 3 课的各个阶段,每个阶段一行。
- 已知限制:纸面账户没有建模什么,延迟、其他机器人、第 11 课的交易腿失败。写下这些比隐藏它们更能赢得信任。
- 结果:报告中的数字,附上日期和设置。
像陌生人那样测试 README:新建一个空文件夹,克隆仓库,按自己的步骤操作,修复每一个你卡住的地方。
第 4 部分:干净的公开仓库
在公开仓库前,检查以下事项。泄漏的密钥无法收回。
- 没有秘密信息。 密钥、令牌和密码放在环境变量中,或放在列入 .gitignore 的文件中,绝不放在代码或你提交的配置中。应提交带有空值或假值的 config.example.json。
- 搜索历史。 如果密钥曾被提交过,在新提交中删除它并不能从历史里抹掉它。在交易所创建新密钥,撤销旧密钥,并从一段全新的历史开始公开仓库。
- 一个 .gitignore,排除日志文件、虚拟环境和包含真实值的配置。
- 一个许可证文件,让人们知道能否复用代码。
- 小而易读的提交,提交信息说明改了什么。
文件夹准备好后,运行这些命令:
git init
git add .
git status # read this list: no keys, no logs
git commit -m "Paper-trading arbitrage bot: first release"
在你使用的代码托管网站上创建一个空仓库,GitHub 或 GitLab,然后把它的地址添加为远程并推送。你可以让智能体提供该网站准确的两条命令,但在首次推送前,自己阅读 git status。
如何谈论结果
- 每次都说“纸面”。纸面利润是上限,因为真实市场的成交更差,第 11 课已经说明。
- 展示最差交易和交易数量,并同时展示总计。
- 给出日期和设置。没有它们的结果无法复现。
- 不要把一次短暂的好结果称为证明。十二笔交易说明不了多少,不同市场日的一百笔交易能说明更多。
- 用具体方式征求反馈:“纸面账户里的哪个假设看起来有问题?”比“你怎么看?”得到的回答更好。
让 AI 智能体编写并测试代码
一个含糊的请求:
为我的机器人准备发布。
一个自带检查条件的请求:
完成这些步骤,并展示每一步的输出。1. 搜索整个仓库,包括 git 历史,查找看起来像 API 密钥或令牌的字符串,列出每个匹配项,暂时不要修复任何内容。2. 编写 report.py,它读取 trades.jsonl,打印交易数、盈利数、总利润和最差交易,并写入 report.html;在第 16 课示例上,它必须打印“trades 12 wins 6”和“worst trade -0.2725”。3. 编写一个 README,其中包含第 16 课的七个部分和一个干净机器上的运行部分;在一个新空文件夹中运行命令,并报告它们在哪里失败。4. 编写 .gitignore 和 config.example.json。不要推送,不要创建任何远程仓库。
亲自阅读第 1 步的列表。只有你决定什么算作秘密。
练习
- 运行报告代码并打开 report.html。将打印的各行与本课比较。
- 将 seed 改为 22,再改为 23。胜率、总利润和最差交易变化多少?这说明从十二笔交易中展示一个数字有什么问题?
- 连接你自己的机器人:让它的纸面账户写入 trades.jsonl,并针对真实的纸面输出运行报告。
- 写 README 并交给一个从未见过该项目的朋友。观察他们在哪里卡住,修 README,不要修朋友。
- 发布仓库。把链接发给三个人,并问每个人同一个具体问题。
课程结束,接下来是什么
你从一个需要一小时的扫描器开始,最后得到一个能寻找价差、沿订单簿计算、考虑费用和大小、预期失败、在限制内以纸面方式运行、交易第二种策略,并能让其他人阅读和运行的机器人。真钱是另一个决定:它需要长期的纸面运行、小规模,以及你信任的风险限制。慢慢来。
如果你在任何地方卡住,前面的课程都遵循同一个模式:先是一个直白版本,再给出修复它的思路,最后是一个带内置检查的智能体请求。回到让你难受的那一课,重新做它的练习。