抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

缘起

量化环境搭建得差不多了,还剩最后一步——把模拟交易实时展现在网页上。好处是一目了然:树莓派跑在局域网,策略产生的每一笔交易、K 线形态、净值曲线都能随时随地查看。这算是整个量化架构的最后一块拼图,也是走向专业量化的敲门砖。

本文记录完整的实现过程,附带所有踩过的坑。


前置准备

硬件

  • 树莓派 4B 8G(建议 SSD 作为系统盘,运行更稳定)→ 迁移教程

软件 & 账号

  • Cloudflare 账号(免费 KV + Worker)
  • 自己的域名(基于 Hexo + Cloudflare Pages)

前置教程

本文是初阶系列的终极篇,需要以下前置知识:

序号 内容 链接
1 Freqtrade Docker 运行环境 从 BeeQuant 到 Freqtrade
2 Mihomo 代理配置 绕过墙与限制:Freqtrade 代理实战
3 远程 Jupyter 开发(可选) 树莓派远程开发 Freqtrade 策略
4 Hexo 建站 记一次 Hexo 建站过程

架构总览

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
┌─ 树莓派 ─────────────────────────────────────────┐
│ │
│ freqtrade docker (模拟交易) │
│ ├─ API :8080 (余额/持仓/交易记录) │
│ └─ user_data/data/*.feather (K线数据) │
│ │ │
│ download_latest.sh (cron 每 15min) │
│ └─ docker download-data → 更新 feather │
│ │ │
│ push_worker.py (systemd 常驻) │
│ ├─ 从 freqtrade API 读取 balance/trades │
│ ├─ 从 feather 读取 K线 │
│ └─ POST /update/dashboard → 远程 API │
│ │ │
└─────────┼──────────────────────────────────────────┘


┌─ Cloudflare Worker ────────────────────────────────┐
│ /update/dashboard → KV 存储 │
│ /data/dashboard → JSON 读取 │
└─────────┼──────────────────────────────────────────┘


┌─ Hexo 网页 ────────────────────────────────────────┐
│ /freqtrade/ → Plotly 图表 │
│ ├─ KPI 卡片 (余额/收益/胜率) │
│ ├─ 净值曲线 │
│ ├─ K线 + MACD 双面板 │
│ └─ 交易记录 │
└────────────────────────────────────────────────────┘

核心思路:数据 5 合 1 批量上传,既减少 Cloudflare KV 写入量(免费额度 1000 次/天),又让网页一次请求拿到全部数据。


一、Cloudflare 端配置

整个链路需要一个「中转站」——把树莓派推送的数据存储起来,再提供给 Hexo 网页读取。Cloudflare 的 Worker + KV 刚好满足这个需求,而且是免费的。

1.1 创建 Worker

进入 Cloudflare 控制台:

计算Workers 和 Pages创建应用程序从 Hello World 开始部署

1.2 设置密钥(⚠️ 坑1:不要在「绑定」里设)

点击 Worker 的 设置添加

  • 类型选择:密钥
  • 变量名称:SECRET
  • 值:设置一个复杂密码(push_worker 用这个 token 认证)

图片加载失败

1.3 创建 KV 命名空间

左侧栏 存储和数据库Workers KVCreate Instance,命名为 FREQTRADE_KV

回到 Worker 页面,绑定添加绑定 → 选择 KV 命名空间

  • 变量名称:FREQTRADE_KV(与代码中引用一致)
  • KV 命名空间:选择刚创建的那个

图片加载失败

图片加载失败

1.4 绑定自定义域名

Worker 主页面 → 自定义域和路由添加域名,填入你自己的域名(例如 freqtrade-api.contangoai.com)。

1.5 Worker 源码

进入 Worker 的 </>编辑源码,粘贴以下代码后点击 部署

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
export default {
async fetch(req, env) {
const url = new URL(req.url);

const corsHeaders = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type, token",
};

// --- CORS preflight ---
if (req.method === "OPTIONS") {
return new Response(null, { headers: corsHeaders });
}

// ============================
// POST /update/xxxx
// ============================
if (req.method === "POST" && url.pathname.startsWith("/update/")) {
const token = req.headers.get("token");
if (token !== env.SECRET) {
return new Response("unauthorized", { status: 401, headers: corsHeaders });
}

const key = url.pathname.replace("/update/", "");
const data = await req.json();
data.timestamp = Date.now();

await env.FREQTRADE_KV.put(key, JSON.stringify(data));

return new Response("ok", { headers: corsHeaders });
}

// ============================
// GET /data/xxxx
// ============================
if (req.method === "GET" && url.pathname.startsWith("/data/")) {
const key = url.pathname.replace("/data/", "");
const data = await env.FREQTRADE_KV.get(key);

return new Response(data || "{}", {
headers: { ...corsHeaders, "Content-Type": "application/json" },
});
}

return new Response("Not Found", { status: 404, headers: corsHeaders });
},
};

Worker 的作用

  • POST /update/dashboard — 接收树莓派推送的批量数据,安全写入 KV
  • GET /data/dashboard — 返回 KV 中存储的 JSON,供网页前端消费
  • 所有响应带 CORS 头,防止跨域问题

Cloudflare Workers 免费版每天 1000 次写入。push_worker 采用「5合1批量上传 + 120 秒间隔」,日写入量仅 720 次,绰绰有余。


二、树莓派端配置

2.1 freqtrade 配置文件

Docker 需要开启 API 接口。以下是可以直接用的 user_data/config.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
{
"$schema": "https://schema.freqtrade.io/schema.json",
"max_open_trades": 1,
"stake_currency": "USDT",
"stake_amount": 20,
"tradable_balance_ratio": 0.99,
"dry_run": true,
"dry_run_wallet": 1000,
"cancel_open_orders_on_exit": false,
"trading_mode": "futures",
"margin_mode": "isolated",
"unfilledtimeout": {
"entry": 10,
"exit": 10,
"exit_timeout_count": 0,
"unit": "minutes"
},
"entry_pricing": {
"price_side": "same",
"use_order_book": true,
"order_book_top": 1,
"price_last_balance": 0.0,
"check_depth_of_market": { "enabled": false, "bids_to_ask_delta": 1 }
},
"exit_pricing": {
"price_side": "same",
"use_order_book": true,
"order_book_top": 1
},
"exchange": {
"name": "binance",
"key": "",
"secret": "",
"ccxt_config": {
"enableRateLimit": true,
"timeout": 60000,
"httpProxy": "http://127.0.0.1:7897",
"options": { "defaultType": "future" }
},
"ccxt_async_config": {
"enableRateLimit": true,
"timeout": 60000,
"httpProxy": "http://127.0.0.1:7897"
},
"pair_whitelist": ["BTC/USDT:USDT"],
"pair_blacklist": ["BNB/.*"]
},
"pairlists": [
{
"method": "StaticPairList",
"pairs": ["BTC/USDT", "ETH/USDT", "SOL/USDT", "XRP/USDT", "DOGE/USDT"]
}
],
"telegram": {
"enabled": false
},
"api_server": {
"enabled": true,
"listen_ip_address": "0.0.0.0",
"listen_port": 8080,
"verbosity": "error",
"enable_openapi": false,
"CORS_origins": [],
"username": "freqtrade",
"password": "your_password_here"
},
"bot_name": "freqtrade",
"initial_state": "running",
"force_entry_enable": false,
"internals": { "process_throttle_secs": 5 },
"fiat_converter": { "enable": false }
}

2.2 配置中的坑

编号 问题 原因 解决
⚠️2 CoinGecko 超时导致重启循环 fiat_converter 默认走 CoinGecko API,不走代理 设置 "enable": false
⚠️3 容器内 127.0.0.1:7897 连不上代理 Docker 默认网络隔离,容器内 127.0.0.1 不等于宿主机 docker-compose.ymlnetwork_mode: "host"
⚠️4 Mihomo 代理不生效 Mihomo 默认只监听 127.0.0.1,Docker host 模式请求被拒 Mihomo 配置加 allow-lan: true
⚠️5 Active pair whitelist is empty futures 模式必须用 BTC/USDT:USDT 格式,不能写 BTC/USDT 白名单加 :USDT 后缀

2.3 docker-compose.yml

Mihomo 代理配置(~/.config/mihomo/config.yaml)需加一行:

1
allow-lan: true

Docker Compose 文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
---
services:
freqtrade:
image: freqtradeorg/freqtrade:stable
restart: unless-stopped
container_name: freqtrade
network_mode: "host"
volumes:
- "./user_data:/freqtrade/user_data"
command: >
trade
--logfile /freqtrade/user_data/logs/freqtrade.log
--db-url sqlite:////freqtrade/user_data/tradesv3.sqlite
--config /freqtrade/user_data/config.json
--strategy MACDStrategy

2.4 验证 freqtrade API

启动后测试:

1
curl -u freqtrade:your_password http://127.0.0.1:8080/api/v1/trades

三、Dashboard 代码

树莓派上的 dashboard/ 目录包含以下脚本:

文件 职责
push_worker.py 主程序,采集数据并批量上传到 Cloudflare Worker
fetch_freqtrade.py 从 freqtrade API (127.0.0.1:8080) 读取余额/持仓/交易
fetch_klines.py 从本地 feather 文件读取 K 线数据(不再调 Binance API)
download_latest.sh 利用 docker 下载当前月 K 线数据
start_push_worker.sh 保活脚本

3.1 push_worker.py 核心设计

批量上传:把所有数据合并为一个 JSON,一次 POST 到 /update/dashboard

1
2
before:  5 × upload(key) → 5 KV writes per cycle
after: 1 × upload(dashboard) → 1 KV write per cycle

容灾队列:KV 写入超限(HTTP 429/507)时自动暂停上传,数据暂存到本地 push_queue.jsonl,配额恢复后自动补发。

1
2
3
4
5
# 正常模式:每 120s 上传一次
UPLOAD_INTERVAL = 120 # → 720 writes/day

# 暂停模式:每 600s 探测一次是否恢复
RETRY_INTERVAL = 600

状态转换图:

1
2
3
正常采集 → 上传成功 → 等120s → 下一轮
→ 上传失败(429) → 暂停 + 本地队列 → 每600s探测
→ 恢复 → 排空队列 → 正常

3.2 fetch_klines.py — 本地读 Feather

不再调 Binance API,而是从 freqtrade 已下载的本地 feather 文件读取:

1
user_data/data/binance/futures/BTC_USDT_USDT-15m-futures.feather

带 mtime 缓存 + 路径自动发现,兼容 spot/futures 两种目录布局。

3.3 download_latest.sh — 定时下载

利用已有的 docker 基础设施,每 15 分钟下载当月 K 线:

1
2
3
# crontab -e
*/15 * * * * /bin/bash /home/zhshang/github/freqtrade/dashboard/download_latest.sh \
>> /home/zhshang/github/freqtrade/dashboard/download_latest.log 2>&1

四、Hexo 网页端

新建页面:

1
hexo new page freqtrade

写入 source/freqtrade/index.md(见仓库源码)。

前端设计要点

特性 说明
单接口 GET /data/dashboard 一次拉取全部数据
Plotly 图表 暗色主题,EMA15/30/60 叠加,MACD 双面板
KPI 卡片 Balance / Profit / Trades / WinRate 四张卡片
手机适配 响应式布局 + 禁用触控手势(防误触)
刷新频率 120s(与 push_worker 上传间隔一致)

五、正式运行

5.1 启动策略

MACD 策略写入 user_data/strategies/macd_strategy.py,使用 talib 计算 MACD,金叉买入、死叉卖出。

1
docker run -d --name freqtrade   --network host   -v $HOME/github/freqtrade/user_data:/freqtrade/user_data   freqtradeorg/freqtrade:stable   trade   --config /freqtrade/user_data/config.json   --strategy MACDStrategy

5.2 systemd 服务

push_worker 以 systemd 常驻运行(注意:Python 使用 conda 环境路径):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# /etc/systemd/system/freqtrade-push.service
[Unit]
Description=Freqtrade Dashboard Push Worker
After=network.target

[Service]
Type=simple
User=zhshang
WorkingDirectory=/home/zhshang/github/freqtrade/dashboard
ExecStart=/home/zhshang/miniforge3/envs/freqtrade/bin/python push_worker.py
Restart=always
RestartSec=10
Environment=PROXY=http://127.0.0.1:7897

[Install]
WantedBy=multi-user.target

启用:

1
2
3
sudo systemctl daemon-reload
sudo systemctl enable --now freqtrade-push
sudo systemctl status freqtrade-push

5.3 最终需要运行的进程

进程 方式 说明
freqtrade docker docker compose up -d 模拟交易引擎
push_worker systemd 数据采集 + 上传
download_latest.sh cron 每15分钟 K线数据更新

线上看板

实时网址https://www.contangoai.com/freqtrade/

目前展示:BTC/USDT:USDT 15 分钟 K 线,策略基于 MACD(金叉买入,死叉卖出)。

  • 🔄 K 线 + MACD:每 2 分钟刷新
  • 📈 Equity 净值曲线:每 2 分钟刷新
  • 🧾 Trades:每 2 分钟刷新

代码见 GitHub:后续公开


附录:常用命令

这里放一些上述过程常见命令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
docker exec -it freqtrade /bin/bash

curl -x http://127.0.0.1:7897 -I https://api.binance.com

docker logs freqtrade --tail -10

curl -x http://host.docker.internal:7897 -I https://api.binance.com

docker inspect -f "{{ .RestartCount }}" freqtrade


curl -u freqtrade:7SNSlZ4cIniw http://127.0.0.1:8080/api/v1/


docker run -d --name freqtrade --network host -v $HOME/github/freqtrade/user_data:/freqtrade/user_data freqtradeorg/freqtrade:stable trade --config /freqtrade/user_data/config.json --strategy SampleStrategy


docker stop freqtrade
docker rm freqtrade

后记

整个项目从「能跑策略」到「能随时随地看策略跑得怎么样」,中间经历了:

  • Cloudflare Worker 的 KV 写入配额优化(5合1 + 容灾队列)
  • Docker 网络与代理的互操作(host 模式 + allow-lan)
  • Feather 文件路径兼容(futures 扁平文件 + 递归扫描)
  • 手机端 UI 适配(dark theme + 禁用手势)

这只是个开始。后续会加入更多指标、多策略对比、交易信号回测结果展示等。欢迎交流反馈。

评论



Powered by Hexo | Theme keep Volantis

本站总访问量 总访客数 🌎