feat: add bench review workflow + wallet setup script + sync-upstream fix
- live/setup_wallets.py: configures our 8 follow wallets with Kruto2027 floor_pin=80 (matching author), plus prints bench status - 每周Bench-Review流程.md: weekly Friday review process using 5 dimensions (forward P&L, copy_pnl, conv_win, seasonal catalyst, floor stability) with promote/demote/pin commands - sync-upstream.sh: recreated with all our custom files including new ones (sync_live_floors.py, setup_wallets.py, bench review doc) - copybot.paper.json: our 8 wallets with Kruto2027 floor_pin=80 bench area (backtest.json) already has 15 wallets from author sync: live: 8 (Kruto2027, 0xbadaf319, 1kto1m, Vahan88, LSB1, EdwardIN, lma0o0o0o, Coteykens) bench: 7 (gkmgkldfmg, AIcAIc, BikesAreTheBikes, imwalkinghere, leegunner, oliman2, JuiceFarm, 0xb0E43B)
This commit is contained in:
@@ -0,0 +1,67 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""配置我们的 8 个跟单钱包到 copybot.paper.json 和 config.live.json。
|
||||||
|
|
||||||
|
跟作者的方法论一致:
|
||||||
|
- Kruto2027 floor_pin=80(钉死,跟作者一样)
|
||||||
|
- 其它钱包 floor 由 sync_floors.py 每天自动算 p80
|
||||||
|
- backtest.json 保持作者的 15 个钱包(8 个 live + 7 个 bench 观察区)
|
||||||
|
"""
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
|
||||||
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
ROOT = os.path.dirname(HERE)
|
||||||
|
|
||||||
|
# 我们的 8 个跟单钱包(基于 watch_sharps.json 排名 + 作者方法论)
|
||||||
|
OUR_WALLETS = [
|
||||||
|
# floor_pin=80 跟作者一样,防止 p80 漂移到 125
|
||||||
|
{"wallet": "0xe8ca3f758c93f44f3ec210542ab78afb7c0bcccb",
|
||||||
|
"name": "Kruto2027", "class": "volume", "floor": 80.0, "floor_pin": 80.0},
|
||||||
|
# 其它钱包不钉 pin,让 sync_floors.py 每天算 p80
|
||||||
|
{"wallet": "0xbadaf319415c17f28824a43ae0cd912b9d84d874",
|
||||||
|
"name": "0xbadaf319", "class": "volume", "floor": 42.0},
|
||||||
|
{"wallet": "0xa1d57d329227c75b12b09f927fb3d6d6ef8f1343",
|
||||||
|
"name": "1kto1m", "class": "volume", "floor": 332.0},
|
||||||
|
{"wallet": "0x2d462b29127b919ae4c085e03be44ae7077e3e2d",
|
||||||
|
"name": "Vahan88", "class": "volume", "floor": 245.0},
|
||||||
|
{"wallet": "0x41558102a796ba971c7567cad41c307e59f8fa41",
|
||||||
|
"name": "LSB1", "class": "volume", "floor": 315.0},
|
||||||
|
# 新加的 3 个,floor 先设 25,sync_floors.py 会自动更新
|
||||||
|
{"wallet": "0x0c3e55cf50a00a7e74fabd8584c6fdde21603c4c",
|
||||||
|
"name": "EdwardIN", "class": "volume", "floor": 25.0},
|
||||||
|
{"wallet": "0x82d2e4dbb0a849ff8e2f5380719769145648beea",
|
||||||
|
"name": "lma0o0o0o", "class": "volume", "floor": 25.0},
|
||||||
|
{"wallet": "0x69b9bd4fa27813091fcfe726541f2fd7baa3a5d5",
|
||||||
|
"name": "Coteykens", "class": "volume", "floor": 25.0},
|
||||||
|
]
|
||||||
|
|
||||||
|
# 更新 copybot.paper.json
|
||||||
|
paper_path = os.path.join(HERE, "copybot.paper.json")
|
||||||
|
paper = json.load(open(paper_path))
|
||||||
|
paper["wallets"] = OUR_WALLETS
|
||||||
|
json.dump(paper, open(paper_path, "w"), indent=2)
|
||||||
|
print(f"copybot.paper.json: {len(OUR_WALLETS)} wallets")
|
||||||
|
for w in OUR_WALLETS:
|
||||||
|
pin = f" (PINNED {w['floor_pin']})" if "floor_pin" in w else ""
|
||||||
|
print(f" {w['name']:20s} floor=${w['floor']}{pin}")
|
||||||
|
|
||||||
|
# 更新 config.live.json(如果存在)
|
||||||
|
live_path = os.path.join(ROOT, "config.live.json")
|
||||||
|
if os.path.exists(live_path):
|
||||||
|
live = json.load(open(live_path))
|
||||||
|
live["wallets"] = OUR_WALLETS
|
||||||
|
json.dump(live, open(live_path, "w"), indent=2)
|
||||||
|
print(f"\nconfig.live.json: {len(OUR_WALLETS)} wallets (synced)")
|
||||||
|
else:
|
||||||
|
print(f"\nconfig.live.json: not found (create from config.live.example.json first)")
|
||||||
|
|
||||||
|
# 确认 backtest.json 有 bench 观察区
|
||||||
|
bt_path = os.path.join(HERE, "backtest.json")
|
||||||
|
bt = json.load(open(bt_path))
|
||||||
|
bt_names = [w.get("name", w["wallet"][:10]) for w in bt["wallets"]]
|
||||||
|
our_names = [w["name"] for w in OUR_WALLETS]
|
||||||
|
bench = [n for n in bt_names if n not in our_names]
|
||||||
|
print(f"\nbacktest.json: {len(bt_names)} wallets total")
|
||||||
|
print(f" live (跟单): {len(our_names)} - {', '.join(our_names)}")
|
||||||
|
print(f" bench (观察): {len(bench)} - {', '.join(bench)}")
|
||||||
|
print(f"\nbench 区的钱包每天被 portfolio.py 回测,周五 review 时看前瞻盈亏决定晋升/降级")
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# sync-upstream.sh - 同步原作者代码 + 保留我们的定制
|
||||||
|
#
|
||||||
|
# 用法:每次原作者有大改动时跑一次
|
||||||
|
# HTTPS_PROXY=http://127.0.0.1:7890 bash sync-upstream.sh
|
||||||
|
#
|
||||||
|
# 做的事:
|
||||||
|
# 1. 备份我们的定制文件到 /tmp/wwf-ours/
|
||||||
|
# 2. 拉取原作者最新代码(github/main)
|
||||||
|
# 3. 把我们的定制文件加回去
|
||||||
|
# 4. 推到 Gitea
|
||||||
|
#
|
||||||
|
# 前提:已经 git remote add github https://github.com/jaxperro/winning-wallet-finder.git
|
||||||
|
|
||||||
|
set -e
|
||||||
|
cd "$(git rev-parse --show-toplevel)"
|
||||||
|
|
||||||
|
echo "=== 1. 备份我们的定制文件 ==="
|
||||||
|
BACKUP="/tmp/wwf-ours"
|
||||||
|
rm -rf "$BACKUP"
|
||||||
|
mkdir -p "$BACKUP/host" "$BACKUP/live"
|
||||||
|
|
||||||
|
# 我们的定制文件清单(新增定制文件必须加到这里!)
|
||||||
|
OUR_FILES=(
|
||||||
|
"live/serve_dashboard.py"
|
||||||
|
"live/bot_dashboard.html"
|
||||||
|
"live/sync_live_floors.py"
|
||||||
|
"live/setup_wallets.py"
|
||||||
|
"host/mihomo-healthcheck.sh"
|
||||||
|
"sync-upstream.sh"
|
||||||
|
"DEPLOY.md"
|
||||||
|
"USAGE.md"
|
||||||
|
"日常使用.md"
|
||||||
|
"问题排查与踩坑记录.md"
|
||||||
|
"mihomo代理部署教程.md"
|
||||||
|
"云端架构.md"
|
||||||
|
"实盘配置.md"
|
||||||
|
"常用命令.md"
|
||||||
|
"项目流程.md"
|
||||||
|
"钱包筛选流程.md"
|
||||||
|
"每周Bench-Review流程.md"
|
||||||
|
"同步作者代码指南.md"
|
||||||
|
)
|
||||||
|
|
||||||
|
for f in "${OUR_FILES[@]}"; do
|
||||||
|
if [ -f "$f" ]; then
|
||||||
|
mkdir -p "$BACKUP/$(dirname "$f")"
|
||||||
|
cp "$f" "$BACKUP/$f"
|
||||||
|
echo " 备份: $f"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# 也备份我们的钱包配置(要覆盖回作者的)
|
||||||
|
if [ -f "live/copybot.paper.json" ]; then
|
||||||
|
cp "live/copybot.paper.json" "$BACKUP/live/copybot.paper.json.ours"
|
||||||
|
echo " 备份: live/copybot.paper.json (我们的钱包配置)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== 2. 拉取原作者最新代码 ==="
|
||||||
|
git fetch github
|
||||||
|
git checkout -B sync-upstream github/main
|
||||||
|
echo " 当前代码: $(git log --oneline -1)"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== 3. 加回我们的定制文件 ==="
|
||||||
|
for f in "${OUR_FILES[@]}"; do
|
||||||
|
if [ -f "$BACKUP/$f" ]; then
|
||||||
|
mkdir -p "$(dirname "$f")"
|
||||||
|
cp "$BACKUP/$f" "$f"
|
||||||
|
echo " 恢复: $f"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== 4. 恢复我们的钱包配置 ==="
|
||||||
|
if [ -f "$BACKUP/live/copybot.paper.json.ours" ]; then
|
||||||
|
cp "$BACKUP/live/copybot.paper.json.ours" "live/copybot.paper.json"
|
||||||
|
echo " 恢复: live/copybot.paper.json (我们的钱包)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# config.live.json 是 gitignored 的,不会被覆盖
|
||||||
|
if [ ! -f "config.live.json" ]; then
|
||||||
|
cp config.live.example.json config.live.json
|
||||||
|
echo " 创建: config.live.json (从模板,需要填 bankroll + 钱包)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== 5. 提交 + 推送 ==="
|
||||||
|
git add -A
|
||||||
|
git commit -m "sync: upstream + our customizations (dashboard, healthcheck, docs, wallet setup)
|
||||||
|
|
||||||
|
Synced from github/main: $(git log --oneline github/main -1)
|
||||||
|
Our additions: serve_dashboard.py, bot_dashboard.html, mihomo-healthcheck.sh,
|
||||||
|
sync_live_floors.py, setup_wallets.py, 中文文档, 我们的钱包配置" || echo " 没有改动需要提交"
|
||||||
|
|
||||||
|
git push origin sync-upstream:main --force
|
||||||
|
echo ""
|
||||||
|
echo "=== 完成 ==="
|
||||||
|
echo "已推送到 Gitea main 分支"
|
||||||
|
echo ""
|
||||||
|
echo "下一步:在服务器上更新"
|
||||||
|
echo " cd /opt/winning-wallet-finder"
|
||||||
|
echo " systemctl stop copybot-live copybot-paper"
|
||||||
|
echo " git fetch origin && git reset --hard origin/main"
|
||||||
|
echo " rm -f copybot_state*.json copybot_fills*.jsonl live/copybot_live*.json"
|
||||||
|
echo " cp host/mihomo-healthcheck.sh /opt/mihomo-healthcheck.sh"
|
||||||
|
echo " cd live && python3 setup_wallets.py"
|
||||||
|
echo " chown wwf:wwf ../config.live.json copybot.paper.json"
|
||||||
|
echo " systemctl start copybot-paper copybot-live"
|
||||||
@@ -0,0 +1,230 @@
|
|||||||
|
# 每周 Bench Review 流程
|
||||||
|
|
||||||
|
> 跟原作者的方法论一致:每周五对跟单钱包和 bench 观察区钱包做前瞻盈亏 review,决定晋升/降级。
|
||||||
|
>
|
||||||
|
> 原作者的原话(HANDOFF_ARCHIVE.md):forward table = portfolio.py resolved rows, two windows: since last review and since the last swap.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 为什么要每周 review
|
||||||
|
|
||||||
|
watch_sharps.json 的 copy_pnl 是 **in-sample**(用历史数据算的),只能告诉你"过去跟这个钱包赚没赚"。但市场在变,钱包的表现也会变。
|
||||||
|
|
||||||
|
**前瞻盈亏(Forward P&L)** 是 out-of-sample 的:看"从某个时间点开始,这个钱包的信念单实际赚了多少"。这才是决定留还是踢的真实依据。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## review 时间
|
||||||
|
|
||||||
|
**每周五**(跟作者一样)。可以在 1Panel 加一个定时提醒。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## review 步骤
|
||||||
|
|
||||||
|
### 第 1 步:跑回测(在服务器上)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /opt/winning-wallet-finder/live
|
||||||
|
|
||||||
|
# 跑 30 天回测(包含 live 8 个 + bench 8 个,共 16 个钱包)
|
||||||
|
python3 portfolio.py --days 30
|
||||||
|
|
||||||
|
# 看回测结果
|
||||||
|
python3 -c "
|
||||||
|
import json
|
||||||
|
d = json.load(open('portfolio.json'))
|
||||||
|
print(f'总体: equity \${d[\"equity\"]:,.0f} | realized \${d[\"realized\"]:+,.0f} | {d[\"resolved\"]} resolved')
|
||||||
|
"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 第 2 步:看每个钱包的前瞻表现
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 看回测里每个钱包的盈亏
|
||||||
|
python3 -c "
|
||||||
|
import json
|
||||||
|
d = json.load(open('portfolio.json'))
|
||||||
|
wallets = {}
|
||||||
|
for b in d.get('bets', []):
|
||||||
|
name = b.get('name', '?')
|
||||||
|
if name not in wallets:
|
||||||
|
wallets[name] = {'n': 0, 'pnl': 0, 'wins': 0}
|
||||||
|
wallets[name]['n'] += 1
|
||||||
|
wallets[name]['pnl'] += b.get('pnl', 0)
|
||||||
|
if b.get('pnl', 0) > 0:
|
||||||
|
wallets[name]['wins'] += 1
|
||||||
|
|
||||||
|
# 按 pnl 排序
|
||||||
|
for name, stats in sorted(wallets.items(), key=lambda x: x[1]['pnl'], reverse=True):
|
||||||
|
wr = stats['wins']/stats['n']*100 if stats['n'] > 0 else 0
|
||||||
|
print(f'{name:20s} {stats[\"n\"]:3d} bets pnl \${stats[\"pnl\"]:+8.2f} 胜率 {wr:.0f}%')
|
||||||
|
"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 第 3 步:对比 live vs bench
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 当前跟的 8 个
|
||||||
|
python3 -c "import json; [print(w['name']) for w in json.load(open('copybot.paper.json'))['wallets']]"
|
||||||
|
|
||||||
|
# bench 观察区的(backtest.json 里有但 copybot.paper.json 里没有的)
|
||||||
|
python3 -c "
|
||||||
|
import json
|
||||||
|
live = {w['wallet'].lower() for w in json.load(open('copybot.paper.json'))['wallets']}
|
||||||
|
bt = json.load(open('backtest.json'))
|
||||||
|
bench = [w.get('name', w['wallet'][:10]) for w in bt['wallets'] if w['wallet'].lower() not in live]
|
||||||
|
print('bench:', ', '.join(bench))
|
||||||
|
"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 第 4 步:决策(5 个维度)
|
||||||
|
|
||||||
|
对照 5 个维度做决策:
|
||||||
|
|
||||||
|
| 维度 | 怎么看 | 晋升条件 | 降级条件 |
|
||||||
|
|------|--------|---------|---------|
|
||||||
|
| **前瞻盈亏** | 第 2 步的 per-wallet pnl | 正盈亏 | 负盈亏(连续 2 周) |
|
||||||
|
| **copy_pnl** | `cat watch_sharps.json \| grep copy_pnl` | 排名靠前 | 跌出 watch_sharps |
|
||||||
|
| **信念胜率** | `cat watch_sharps.json \| grep conv_win` | >60% | <30% |
|
||||||
|
| **季节性** | 看钱包的 niche 有没有赛季/事件 | 赛季开始 | 赛季结束 |
|
||||||
|
| **floor 稳定** | `cat copybot.paper.json \| grep floor` | 正常 | p80 漂移太大需 pin |
|
||||||
|
|
||||||
|
### 第 5 步:执行变更
|
||||||
|
|
||||||
|
**晋升**(bench -> live):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /opt/winning-wallet-finder
|
||||||
|
|
||||||
|
python3 -c "
|
||||||
|
import json
|
||||||
|
# 把 bench 钱包加到 copybot.paper.json 和 config.live.json
|
||||||
|
promote_name = 'AIcAIc' # 改成你要晋升的钱包
|
||||||
|
|
||||||
|
# 从 backtest.json 查地址
|
||||||
|
bt = json.load(open('live/backtest.json'))
|
||||||
|
promote = next((w for w in bt['wallets'] if w.get('name') == promote_name), None)
|
||||||
|
if not promote:
|
||||||
|
print(f'{promote_name} not found in backtest.json')
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
# 加到 paper 和 live
|
||||||
|
for path in ['live/copybot.paper.json', 'config.live.json']:
|
||||||
|
cfg = json.load(open(path))
|
||||||
|
if not any(w['wallet'].lower() == promote['wallet'].lower() for w in cfg['wallets']):
|
||||||
|
w = {'wallet': promote['wallet'], 'name': promote['name'],
|
||||||
|
'class': 'volume', 'floor': 25.0}
|
||||||
|
cfg['wallets'].append(w)
|
||||||
|
json.dump(cfg, open(path, 'w'), indent=2)
|
||||||
|
print(f'{path}: added {promote_name}')
|
||||||
|
else:
|
||||||
|
print(f'{path}: {promote_name} already there')
|
||||||
|
"
|
||||||
|
|
||||||
|
chown wwf:wwf live/copybot.paper.json config.live.json
|
||||||
|
systemctl restart copybot-paper copybot-live
|
||||||
|
```
|
||||||
|
|
||||||
|
**降级**(live -> bench):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /opt/winning-wallet-finder
|
||||||
|
|
||||||
|
python3 -c "
|
||||||
|
import json
|
||||||
|
demote_name = 'BikesAreTheBikes' # 改成你要降级的钱包
|
||||||
|
|
||||||
|
for path in ['live/copybot.paper.json', 'config.live.json']:
|
||||||
|
cfg = json.load(open(path))
|
||||||
|
old = [w['name'] for w in cfg['wallets']]
|
||||||
|
cfg['wallets'] = [w for w in cfg['wallets'] if w['name'] != demote_name]
|
||||||
|
new = [w['name'] for w in cfg['wallets']]
|
||||||
|
json.dump(cfg, open(path, 'w'), indent=2)
|
||||||
|
print(f'{path}: {old} -> {new}')
|
||||||
|
"
|
||||||
|
|
||||||
|
chown wwf:wwf live/copybot.paper.json config.live.json
|
||||||
|
systemctl restart copybot-paper copybot-live
|
||||||
|
```
|
||||||
|
|
||||||
|
> 降级的钱包留在 backtest.json 里继续观察(不用删),下周 review 还能看到它的前瞻表现。
|
||||||
|
|
||||||
|
### 第 6 步:钉死 floor_pin(如果需要)
|
||||||
|
|
||||||
|
如果某个钱包的 p80 漂移太大(sync_floors.py 警告 `⚠ floor X->Y`),钉死它:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 -c "
|
||||||
|
import json
|
||||||
|
pin_name = 'Kruto2027' # 要钉的钱包
|
||||||
|
pin_value = 80.0 # 钉死的值
|
||||||
|
|
||||||
|
for path in ['live/copybot.paper.json', 'config.live.json']:
|
||||||
|
cfg = json.load(open(path))
|
||||||
|
for w in cfg['wallets']:
|
||||||
|
if w['name'] == pin_name:
|
||||||
|
w['floor'] = pin_value
|
||||||
|
w['floor_pin'] = pin_value
|
||||||
|
print(f'{path}: {pin_name} floor_pin = {pin_value}')
|
||||||
|
json.dump(cfg, open(path, 'w'), indent=2)
|
||||||
|
"
|
||||||
|
|
||||||
|
chown wwf:wwf live/copybot.paper.json config.live.json
|
||||||
|
systemctl restart copybot-paper copybot-live
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 当前状态
|
||||||
|
|
||||||
|
### live 跟单(8 个)
|
||||||
|
|
||||||
|
| 钱包 | floor | floor_pin | niche |
|
||||||
|
|------|-------|-----------|-------|
|
||||||
|
| Kruto2027 | $80 | ✅ 80 | 综合 |
|
||||||
|
| 0xbadaf319 | $42 | - | 综合 |
|
||||||
|
| 1kto1m | $332 | - | BTC Up/Down |
|
||||||
|
| Vahan88 | $245 | - | 板球/体育 |
|
||||||
|
| LSB1 | $315 | - | 瑞典足球 |
|
||||||
|
| EdwardIN | $25 | - | 待观察 |
|
||||||
|
| lma0o0o0o | $25 | - | 待观察 |
|
||||||
|
| Coteykens | $25 | - | 待观察 |
|
||||||
|
|
||||||
|
### bench 观察(8 个)
|
||||||
|
|
||||||
|
| 钱包 | 状态 | 备注 |
|
||||||
|
|------|------|------|
|
||||||
|
| gkmgkldfmg | 被淘汰 | daily.sh 已移出 watch_sharps |
|
||||||
|
| AIcAIc | 观察 | 作者 07-16 重新加入(电竞赛季) |
|
||||||
|
| BikesAreTheBikes | 观察 | 信念胜率 21% 偏低 |
|
||||||
|
| imwalkinghere | 永久排除 | 5min BTC 市场,结算比跟单快 |
|
||||||
|
| leegunner | 新候选 | 作者 bench rev 5 新加 |
|
||||||
|
| oliman2 | 新候选 | 作者 bench rev 5 新加 |
|
||||||
|
| JuiceFarm | 新候选 | 作者 bench rev 5 新加 |
|
||||||
|
| 0xb0E43B | 新候选 | 作者 bench rev 5 新加 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1Panel 快速命令
|
||||||
|
|
||||||
|
**每周 review 一键跑回测 + 看排名**:
|
||||||
|
```bash
|
||||||
|
cd /opt/winning-wallet-finder/live && python3 portfolio.py --days 30 && python3 -c "
|
||||||
|
import json
|
||||||
|
d = json.load(open('portfolio.json'))
|
||||||
|
wallets = {}
|
||||||
|
for b in d.get('bets', []):
|
||||||
|
name = b.get('name', '?')
|
||||||
|
if name not in wallets:
|
||||||
|
wallets[name] = {'n': 0, 'pnl': 0, 'wins': 0}
|
||||||
|
wallets[name]['n'] += 1
|
||||||
|
wallets[name]['pnl'] += b.get('pnl', 0)
|
||||||
|
if b.get('pnl', 0) > 0:
|
||||||
|
wallets[name]['wins'] += 1
|
||||||
|
print('=== 前瞻盈亏排名 ===')
|
||||||
|
for name, stats in sorted(wallets.items(), key=lambda x: x[1]['pnl'], reverse=True):
|
||||||
|
wr = stats['wins']/stats['n']*100 if stats['n'] > 0 else 0
|
||||||
|
print(f'{name:20s} {stats[\"n\"]:3d} bets pnl \${stats[\"pnl\"]:+8.2f} 胜率 {wr:.0f}%')
|
||||||
|
"
|
||||||
|
```
|
||||||
Reference in New Issue
Block a user