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

树莓派上的 Freqtrade 模拟盘/实盘跑了几个月,一切正常。然而一次意外断电后,重启发现 dashboard 上所有数据都不见了——交易记录、持仓信息、K线历史、资金曲线,全部清零。

这个问题很严重:断电是常态(特别是树莓派这种嵌入式设备),如果每次断电后数据都丢失,那整个量化系统就不可用了。

本文记录了完整的排查过程和解决方案,核心思路是数据持久化 + 自动恢复


问题分析

1. 交易信息丢失

现象:断电重启后,持仓显示为 0,交易记录为空。

原因

  • fetch_freqtrade.py 优先从 REST API 读取持仓和交易
  • 断电后 freqtrade docker 还没启动,API 不可用
  • 直接返回空列表,导致 dashboard 显示为空

根本问题:REST API 数据是易失性的,而 freqtrade 的 SQLite 数据库才是持久化存储。

2. 资金曲线丢失

现象:断电后 equity 曲线从零开始。

原因

  • push_worker.pyequity_history 只在内存中
  • 重启后内存清空,没有持久化到磁盘

3. K线历史丢失

现象:断电后 K线从某个时间点重新开始,没有之前的历史数据。

原因

  • download_latest.sh 只下载当前月的数据
  • 断电跨月后,之前的月份 K线数据丢失
  • feather 文件损坏后无法恢复

解决方案总览

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
┌─ 树莓派 ─────────────────────────────────────────┐
│ │
│ 断电重启流程: │
│ │
│ 1. mihomo.timer (4min) → 启动代理 │
│ 2. freqtrade-auto-restart.timer (5min) │
│ → docker stop freqtrade & rm freqtrade │
│ → docker run freqtrade (加载数据库) │
│ 3. freqtrade-push.timer (6min) │
│ → 加载 equity_history.json │
│ → 从 SQLite 读取 trades/positions │
│ → 计算 K线范围(基于运行天数) │
│ │
│ 数据来源策略: │
│ │
│ trades: SQLite (优先) → API (回退) │
│ positions: SQLite (基础) → API (补充实时价格) │
│ equity: equity_history.json (持久化) │
│ klines: feather 文件 (最近3个月) │
│ │
└───────────────────────────────────────────────────┘

核心改进

  1. 数据来源优先级调整:SQLite 持久化数据优先,API 作为实时补充
  2. equity 持久化:保存到 equity_history.json
  3. K线范围扩大:下载最近3个月,显示范围动态计算
  4. 自动启动:systemd timer 延迟启动,确保网络和代理就绪

一、数据持久化改造

1.1 fetch_freqtrade.py — 持仓获取逻辑优化

修改前:优先 API → 失败回退 SQLite

1
2
3
4
5
6
7
def get_positions():
try:
api_positions = _local_get("/api/v1/status")
# 转换并返回
return result
except:
return get_positions_from_db() # 失败才回退

问题:API 返回空(freqtrade 未启动)时,直接返回空列表,SQLite 不会被读取。

修改后:先 SQLite → 再用 API 补充

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def get_positions():
# 步骤1:从 SQLite 读取(持久化数据,重启肯定有)
db_positions = get_positions_from_db()
if not db_positions:
return []

# 步骤2:尝试用 API 补充实时价格
try:
api_positions = _local_get("/api/v1/status")
# 用 API 的 current_rate 更新 SQLite 数据
for pos in db_positions:
# 根据 pair + side 匹配
if api_map[key]:
pos["current_rate"] = api_map[key]["current_rate"]
except:
pass # API 不可用,使用 SQLite 数据

return db_positions

效果

  • ✅ 断电重启后,即使 API 不可用,也能显示持仓(来自 SQLite)
  • ✅ API 可用时,自动补充实时价格信息
  • ✅ 完全解决了”断电后持仓显示为0”的问题

1.2 fetch_freqtrade.py — 交易记录优先 SQLite

1
2
3
4
5
6
7
8
def get_trades():
# 优先 SQLite(持久化数据源)
db_trades = get_trades_from_db()
if db_trades:
return db_trades

# 回退 REST API
return get_trades_from_api()

SQLite 是 freqtrade 的持久化存储,交易记录不会因重启丢失。

1.3 push_worker.py — equity 持久化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 持久化文件路径
EQUITY_FILE = DASHBOARD_DIR / "equity_history.json"

# 启动时加载
def _load_equity():
global equity_history
if EQUITY_FILE.exists():
with open(EQUITY_FILE, "r") as f:
equity_history = json.load(f)

# 每次更新后保存
def _save_equity():
with open(EQUITY_FILE, "w") as f:
json.dump(equity_history, f)

# 启动时加载
_load_equity()

while True:
equity = build_equity(latest) # 追加新数据
_save_equity() # 持久化到磁盘

效果

  • ✅ 断电重启后,equity 曲线自动恢复
  • ✅ 资金曲线不再从零开始

二、K线数据优化

2.1 download_latest.sh — 扩大下载范围

修改前:只下载当前月

1
2
3
4
5
CURRENT_MONTH=$(date +"%Y%m")
docker run --rm ... freqtradeorg/freqtrade:stable download-data \
--days 1 \
--timeframes 15m \
--timerange ${CURRENT_MONTH}010000-${CURRENT_MONTH}312359

修改后:下载最近3个月

1
2
3
4
5
6
7
8
9
10
11
12
CURRENT_MONTH=$(date +"%Y%m")
# 计算前两个月
PREV_MONTH1=$(date -d "$(date +%Y-%m-15) -1 month" +"%Y%m")
PREV_MONTH2=$(date -d "$(date +%Y-%m-15) -2 month" +"%Y%m")

# 下载三个月数据
for MONTH in $PREV_MONTH2 $PREV_MONTH1 $CURRENT_MONTH; do
docker run --rm ... freqtradeorg/freqtrade:stable download-data \
--days 1 \
--timeframes 15m \
--timerange ${MONTH}010000-${MONTH}312359
done

重要freqtrade download-data 是增量合并,不会覆盖已有数据。

2.2 push_worker.py — K线范围动态计算

修改前:定死 200 根(约2天)

1
klines = get_klines("BTCUSDT", "15m", 200)

修改后:基于 equity_history 的运行天数动态计算

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# K线应该从 equity_history 的起点开始
if len(equity_history) >= 2:
start_time = equity_history[0]["time"]
current_time = int(time.time() * 1000)
days_running = (current_time - start_time) / (24 * 60 * 60 * 1000)

# 1天 = 96根15分钟K线,加上10%缓冲
kline_limit = int(days_running * 96 * 1.1) + 100

# 最多3个月(8640根),最少200根
kline_limit = max(200, min(kline_limit, 8640))
print(f"[klines] 运行 {days_running:.1f} 天,获取 {kline_limit} 根K线")
else:
kline_limit = 200

klines = get_klines("BTCUSDT", "15m", kline_limit)

效果

策略运行天数 K线数量(15分钟)
1天 约200根
7天 约800根
30天 约3200根
90天 8640根(上限)

2.3 fetch_klines.py — feather 损坏容错

1
2
3
4
5
6
7
8
9
10
def _read_feather_safe(path):
""" feather 损坏时自动重试 """
for attempt in range(3):
try:
return pd.read_feather(path)
except Exception as e:
if attempt == 2:
raise
print(f"[fetch_klines] feather 读取失败,重试 {attempt + 1}/3")
time.sleep(1)

三、Systemd 自动启动配置

树莓派断电重启后,需要按顺序启动服务:

  1. 4 分钟后:启动 mihomo 代理(网络依赖)
  2. 5 分钟后:重启 freqtrade docker(代理依赖)
  3. 6 分钟后:启动 push_worker(freqtrade API 依赖)

3.1 mihomo.timer — 延迟4分钟启动

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 停止现有服务
sudo systemctl disable mihomo.service
sudo systemctl stop mihomo.service

# 创建 timer
sudo cat > /etc/systemd/system/mihomo.timer << 'EOF'
[Unit]
Description=Run mihomo proxy 4 minutes after boot

[Timer]
OnBootSec=4min
Unit=mihomo.service

[Install]
WantedBy=timers.target
EOF

# 启用 timer
sudo systemctl daemon-reload
sudo systemctl enable mihomo.timer
sudo systemctl start mihomo.timer

3.2 freqtrade-auto-restart.timer — 延迟5分钟重启

创建重启脚本 ~/github/freqtrade/scripts/restart_freqtrade.sh

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
cat > ~/github/freqtrade/scripts/restart_freqtrade.sh << 'EOF'
#!/bin/bash
set -e

FREQTRADE_DIR="$HOME/github/freqtrade"

echo "[restart_freqtrade] $(date): 开始重启 freqtrade..."

cd "$FREQTRADE_DIR" || exit 1

# 停止并删除旧容器
echo "[restart_freqtrade] 停止旧容器..."
docker stop freqtrade 2>/dev/null || true
docker rm freqtrade 2>/dev/null || true

# 启动新容器
echo "[restart_freqtrade] 启动新容器..."
docker run -d \
--name freqtrade \
--network host \
-v "$FREQTRADE_DIR/user_data:/freqtrade/user_data" \
freqtradeorg/freqtrade:stable \
trade \
--config /freqtrade/user_data/config.json \
--strategy MACDStrategy

echo "[restart_freqtrade] $(date): freqtrade 已启动"
EOF

chmod +x ~/github/freqtrade/scripts/restart_freqtrade.sh

创建 systemd 服务和 timer:

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
# 服务文件
sudo cat > /etc/systemd/system/freqtrade-auto-restart.service << 'EOF'
[Unit]
Description=Auto restart freqtrade after boot
After=network-online.target docker.service

[Service]
Type=oneshot
User=zhshang
WorkingDirectory=/home/zhshang/github/freqtrade
ExecStart=/home/zhshang/github/freqtrade/scripts/restart_freqtrade.sh
StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target
EOF

# Timer 文件
sudo cat > /etc/systemd/system/freqtrade-auto-restart.timer << 'EOF'
[Unit]
Description=Run freqtrade restart 5 minutes after boot

[Timer]
OnBootSec=5min
Unit=freqtrade-auto-restart.service

[Install]
WantedBy=timers.target
EOF

# 启用
sudo systemctl daemon-reload
sudo systemctl enable freqtrade-auto-restart.timer
sudo systemctl start freqtrade-auto-restart.timer

注意:将 zhshang 替换为你的实际用户名。

3.3 freqtrade-push.timer — 延迟6分钟启动

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
# 服务文件(假设已存在)
sudo cat > /etc/systemd/system/freqtrade-push.service << 'EOF'
[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

[Install]
WantedBy=multi-user.target
EOF

# Timer 文件
sudo cat > /etc/systemd/system/freqtrade-push.timer << 'EOF'
[Unit]
Description=Run freqtrade push worker 6 minutes after boot

[Timer]
OnBootSec=6min
Unit=freqtrade-push.service

[Install]
WantedBy=timers.target
EOF

# 启用
sudo systemctl daemon-reload
sudo systemctl enable freqtrade-push.timer
sudo systemctl start freqtrade-push.timer

3.4 验证配置

查看所有 timer 状态:

1
2
3
systemctl status mihomo.timer
systemctl status freqtrade-auto-restart.timer
systemctl status freqtrade-push.timer

应该显示:

1
2
Active: active (running)
Trigger: Boot Xmin later

查看日志:

1
journalctl -u freqtrade-auto-restart.service -f

四、完整启动流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
树莓派断电 → 重启

0 min: 系统启动

4 min: mihomo.timer 触发 → mihomo.service 启动(代理服务)

5 min: freqtrade-auto-restart.timer 触发
→ restart_freqtrade.sh 执行
→ docker stop freqtrade & docker rm freqtrade
→ docker run freqtrade(加载数据库)

6 min: freqtrade-push.timer 触发 → freqtrade-push.service 启动
→ 加载 equity_history.json
→ 从 SQLite 读取 trades/positions
→ 计算 K线范围(基于运行天数)
→ 开始推送数据到 Cloudflare Worker

最终: 所有数据恢复,dashboard 正常显示

五、测试验证

5.1 模拟断电重启

1
2
3
4
5
6
7
8
9
10
11
# 停止所有服务
docker stop freqtrade
sudo systemctl stop freqtrade-push

# 删除 equity_history.json(模拟数据丢失)
rm /home/zhshang/github/freqtrade/dashboard/equity_history.json

# 重启后检查
# 1. equity_history.json 应该自动创建
# 2. 持仓信息应该从 SQLite 恢复
# 3. K线范围应该基于历史数据计算

5.2 检查数据来源

查看 push_worker 日志:

1
journalctl -u freqtrade-push -f

正常输出:

1
2
3
4
[equity] loaded 17 points from equity_history.json
[fetch_freqtrade] 持仓:1 个(已更新实时价格)
[klines] 运行 0.1 天,获取 200 根K线
[dashboard] HTTP 200

5.3 手动测试重启

1
2
3
4
5
6
7
8
# 停止 freqtrade docker
docker stop freqtrade && docker rm freqtrade

# 手动触发重启
sudo systemctl start freqtrade-auto-restart.service

# 查看日志
journalctl -u freqtrade-auto-restart.service -f

六、常见问题

Q1: 为什么不用 docker 自带的 restart 策略?

A: docker 的 restart: unless-stopped 确实能自动重启容器,但无法保证启动顺序:

  • freqtrade docker 启动时,mihomo 代理可能还没就绪
  • push_worker 启动时,freqtrade API 可能还没就绪

使用 systemd timer 可以精确控制启动时间和顺序。

Q2: SQLite 数据会丢失吗?

A: 不会。push_worker.py 使用只读模式访问 SQLite:

1
conn = sqlite3.connect(f"file:{DB_PATH}?mode=ro", uri=True)

只有 freqtrade docker 容器会写入 SQLite(开仓/平仓时)。

Q3: feather 文件损坏后怎么办?

A: download_latest.sh 每 15 分钟下载最近 3 个月数据,是增量合并。如果 feather 损坏:

  1. fetch_klines.py 自动重试 3 次
  2. 下一次 download_latest.sh 执行时会重新下载
  3. 数据会自动恢复

附录:常用命令

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
# 查看所有 timer 状态
systemctl list-timers --all

# 查看特定 timer
systemctl status mihomo.timer
systemctl status freqtrade-auto-restart.timer
systemctl status freqtrade-push.timer

# 手动触发 timer
sudo systemctl start freqtrade-auto-restart.service
sudo systemctl start freqtrade-push.service

# 查看服务日志
journalctl -u freqtrade-auto-restart.service -f
journalctl -u freqtrade-push.service -f

# 停止 freqtrade docker
docker stop freqtrade && docker rm freqtrade

# 检查 docker 容器状态
docker ps -a

# 查看 SQLite 数据库
sqlite3 user_data/tradesv3.sqlite "SELECT pair, is_short, is_open FROM trades"

# 查看 equity_history.json
cat dashboard/equity_history.json

# 测试 freqtrade API
curl -u freqtrade:password http://127.0.0.1:8080/api/v1/trades

后记

经过这次优化,系统已经具备完整的断电恢复能力:

改进项 修改前 修改后
交易记录 API 优先,断电为空 SQLite 优先,持久化
持仓信息 API 优先,断电为空 SQLite 基础,API 补充
资金曲线 内存存储,重启清空 持久化 JSON,自动恢复
K线数据 当月数据,断电丢失 3个月数据,动态范围
自动启动 手动执行 systemd timer 延迟启动

现在即使树莓派意外断电,重启后 6 分钟内所有数据自动恢复,dashboard 正常显示。这对于模拟盘/实盘交易系统来说,是至关重要的可靠性保障。

评论



Powered by Hexo | Theme keep Volantis

本站总访问量 总访客数 🌎