docs: update 每周Bench-Review流程.md - add step 7 (new candidates) + operations
- Restructured to 7 steps matching 每周五Review命令.md - Added step 7: find new candidates in watch_sharps not in backtest.json - Added '加新候选到 bench 观察区' operation command - Added '更新 setup_wallets.py' reminder after changes - Added per-step annotations (what it does + which files) - Replaced old 1Panel command with reference to 每周五Review命令.md - Added niche analysis to decision table (ITF tennis/esports = good, 5min BTC/long holds = bad)
This commit is contained in:
+137
-79
@@ -22,111 +22,159 @@ watch_sharps.json 的 copy_pnl 是 **in-sample**(用历史数据算的),
|
|||||||
|
|
||||||
## review 步骤
|
## review 步骤
|
||||||
|
|
||||||
### 第 1 步:跑回测(在服务器上)
|
> 完整 7 步命令见 `每周五Review命令.md`(含一键跑全部的脚本)。以下是每步说明 + 执行操作。
|
||||||
|
|
||||||
|
### 第 1 步:跑回测
|
||||||
|
|
||||||
|
> **做什么**:用最新 cache 数据对 backtest.json 里的所有钱包跑 30 天模拟跟单
|
||||||
|
> **相关文件**:`backtest.json`(输入)-> `portfolio.json`(输出)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd /opt/winning-wallet-finder/live
|
cd /opt/winning-wallet-finder/live
|
||||||
|
|
||||||
# 跑 30 天回测(包含 live 8 个 + bench 8 个,共 16 个钱包)
|
|
||||||
python3 portfolio.py --days 30
|
python3 portfolio.py --days 30
|
||||||
|
```
|
||||||
|
|
||||||
# 看回测结果
|
### 第 2 步:看前瞻盈亏排名
|
||||||
|
|
||||||
|
> **做什么**:读 portfolio.json 的 wallets 数组,按 30 天前瞻 realized 排序
|
||||||
|
> **相关文件**:`portfolio.json`(读)
|
||||||
|
> **这是决策的核心数据**
|
||||||
|
|
||||||
|
```bash
|
||||||
python3 -c "
|
python3 -c "
|
||||||
import json
|
import json
|
||||||
d = json.load(open('portfolio.json'))
|
d = json.load(open('portfolio.json'))
|
||||||
print(f'总体: equity \${d[\"equity\"]:,.0f} | realized \${d[\"realized\"]:+,.0f} | {d[\"resolved\"]} resolved')
|
live_names = {'Kruto2027','0xbadaf319','1kto1m','Vahan88','LSB1','EdwardIN','lma0o0o0o','Coteykens'}
|
||||||
|
ws = d.get('wallets', [])
|
||||||
|
print('=== 30天前瞻盈亏排名 ===')
|
||||||
|
for w in sorted(ws, key=lambda x: x.get('realized',0), reverse=True):
|
||||||
|
name = w.get('name','?')
|
||||||
|
tag = 'LIVE' if name in live_names else 'bench'
|
||||||
|
print(f'{name:20s} bets={w.get(\"bets\",0):3d} W={w.get(\"won\",0):2d} L={w.get(\"lost\",0):2d} S={w.get(\"sold\",0):2d} realized=\${w.get(\"realized\",0):+9.2f} [{tag}]')
|
||||||
"
|
"
|
||||||
```
|
```
|
||||||
|
|
||||||
### 第 2 步:看每个钱包的前瞻表现
|
### 第 3 步:看候选池排名
|
||||||
|
|
||||||
|
> **做什么**:读 watch_sharps.json,按 copy_pnl 排序,看信念胜率
|
||||||
|
> **相关文件**:`watch_sharps.json`(daily.sh 自动产出)
|
||||||
|
> **注意**:copy_pnl 是 in-sample 的,会骗人,只作参考
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 看回测里每个钱包的盈亏
|
cat watch_sharps.json | python3 -c "
|
||||||
python3 -c "
|
import sys,json
|
||||||
import json
|
data=json.load(sys.stdin)
|
||||||
d = json.load(open('portfolio.json'))
|
data.sort(key=lambda w: w.get('copy_pnl',0), reverse=True)
|
||||||
wallets = {}
|
for i,w in enumerate(data):
|
||||||
for b in d.get('bets', []):
|
name = w.get('name') or w.get('wallet','')[:10]
|
||||||
name = b.get('name', '?')
|
win = w.get('conv_win')
|
||||||
if name not in wallets:
|
winstr = f'{win:.0f}%' if win is not None else '?'
|
||||||
wallets[name] = {'n': 0, 'pnl': 0, 'wins': 0}
|
print(f'#{i+1:2d} {name:20s} copy_pnl+{w.get(\"copy_pnl\",0):5d} conv_win={winstr}')
|
||||||
wallets[name]['n'] += 1
|
print(f'总共 {len(data)} 个候选')
|
||||||
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
|
### 第 4 步:看 floor 漂移
|
||||||
|
|
||||||
|
> **做什么**:读 daily.log 里 sync_floors 的输出
|
||||||
|
> **相关文件**:`daily.log`(读)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 当前跟的 8 个
|
grep "floor" daily.log | tail -10
|
||||||
python3 -c "import json; [print(w['name']) for w in json.load(open('copybot.paper.json'))['wallets']]"
|
```
|
||||||
|
|
||||||
# bench 观察区的(backtest.json 里有但 copybot.paper.json 里没有的)
|
### 第 5 步:看本周跟单记录
|
||||||
|
|
||||||
|
> **做什么**:读 live bot 7 天日志
|
||||||
|
> **相关文件**:journalctl(systemd 日志)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
journalctl -u copybot-live --since "7 days ago" --no-pager | grep -E "FOLLOW|OPEN|skip" | tail -30
|
||||||
|
```
|
||||||
|
|
||||||
|
### 第 6 步:看链上余额
|
||||||
|
|
||||||
|
> **做什么**:查存款钱包的实际 pUSD 余额
|
||||||
|
> **相关文件**:`/etc/copybot/live.env`(读私钥)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export LIVE_PRIVATE_KEY=$(grep LIVE_PRIVATE_KEY /etc/copybot/live.env | cut -d= -f2-) && export HTTPS_PROXY=http://127.0.0.1:7890 && python3 -c "from polymarket import SecureClient; c=SecureClient.create(private_key='$LIVE_PRIVATE_KEY'); b=c.get_balance_allowance(asset_type='COLLATERAL'); print('链上 pUSD: %.2f' % (b.balance/1e6)); c.close()"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 第 7 步:找出新候选
|
||||||
|
|
||||||
|
> **做什么**:对比 watch_sharps.json 和 backtest.json,找出 daily.sh 新筛出来但还没在观察区的钱包
|
||||||
|
> **相关文件**:`watch_sharps.json`(全部候选)+ `backtest.json`(已在观察的)
|
||||||
|
> **用途**:发现新钱包,如果 copy_pnl 高 + 信念胜率好,加到 backtest.json 开始观察
|
||||||
|
|
||||||
|
```bash
|
||||||
python3 -c "
|
python3 -c "
|
||||||
import json
|
import json
|
||||||
live = {w['wallet'].lower() for w in json.load(open('copybot.paper.json'))['wallets']}
|
sharps = json.load(open('watch_sharps.json'))
|
||||||
bt = json.load(open('backtest.json'))
|
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]
|
bt_addrs = {w['wallet'].lower() for w in bt['wallets']}
|
||||||
print('bench:', ', '.join(bench))
|
new_candidates = [w for w in sharps if w.get('wallet','').lower() not in bt_addrs]
|
||||||
|
new_candidates.sort(key=lambda w: w.get('copy_pnl',0), reverse=True)
|
||||||
|
print(f'watch_sharps {len(sharps)} 个,backtest.json {len(bt[\"wallets\"])} 个,新候选 {len(new_candidates)} 个')
|
||||||
|
print()
|
||||||
|
for w in new_candidates[:15]:
|
||||||
|
name = w.get('name') or w.get('wallet','')[:10]
|
||||||
|
win = w.get('conv_win')
|
||||||
|
winstr = f'{win:.0f}%' if win is not None else '?'
|
||||||
|
print(f'{name:20s} copy_pnl+{w.get(\"copy_pnl\",0):5d} conv_win={winstr}')
|
||||||
"
|
"
|
||||||
```
|
```
|
||||||
|
|
||||||
### 第 4 步:决策(5 个维度)
|
---
|
||||||
|
|
||||||
对照 5 个维度做决策:
|
## 决策(5 个维度)
|
||||||
|
|
||||||
| 维度 | 怎么看 | 晋升条件 | 降级条件 |
|
对照 5 个维度做决策(详见 `AGENT.md`):
|
||||||
|------|--------|---------|---------|
|
|
||||||
| **前瞻盈亏** | 第 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 步:执行变更
|
| 维度 | 数据来源 | 晋升条件 | 降级条件 |
|
||||||
|
|------|---------|---------|---------|
|
||||||
|
| **前瞻盈亏** | 第 2 步 portfolio.json | 正盈亏 | 连续 2 周负 |
|
||||||
|
| **copy_pnl** | 第 3 步 watch_sharps.json | 排名靠前 | 跌出 watch_sharps |
|
||||||
|
| **信念胜率** | 第 3 步 watch_sharps.json | >60% | <30% |
|
||||||
|
| **季节性 + niche** | 第 5 步日志 + 人工判断 | 赛季开始 / ITF网球 / 三级电竞 | 赛季结束 / 5min BTC / 长持仓 |
|
||||||
|
| **floor 稳定** | 第 4 步 daily.log | 正常 | p80 漂移 > 15% 需 pin |
|
||||||
|
|
||||||
**晋升**(bench -> live):
|
---
|
||||||
|
|
||||||
|
## 执行操作
|
||||||
|
|
||||||
|
### 晋升(bench -> live)
|
||||||
|
|
||||||
|
> 从 backtest.json 里选一个表现好的,加到跟单配置
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd /opt/winning-wallet-finder
|
cd /opt/winning-wallet-finder
|
||||||
|
|
||||||
python3 -c "
|
python3 -c "
|
||||||
import json
|
import json
|
||||||
# 把 bench 钱包加到 copybot.paper.json 和 config.live.json
|
|
||||||
promote_name = 'AIcAIc' # 改成你要晋升的钱包
|
promote_name = 'AIcAIc' # 改成你要晋升的钱包
|
||||||
|
|
||||||
# 从 backtest.json 查地址
|
|
||||||
bt = json.load(open('live/backtest.json'))
|
bt = json.load(open('live/backtest.json'))
|
||||||
promote = next((w for w in bt['wallets'] if w.get('name') == promote_name), None)
|
promote = next((w for w in bt['wallets'] if w.get('name') == promote_name), None)
|
||||||
if not promote:
|
if not promote:
|
||||||
print(f'{promote_name} not found in backtest.json')
|
print(f'{promote_name} not found in backtest.json'); exit(1)
|
||||||
exit(1)
|
|
||||||
|
|
||||||
# 加到 paper 和 live
|
|
||||||
for path in ['live/copybot.paper.json', 'config.live.json']:
|
for path in ['live/copybot.paper.json', 'config.live.json']:
|
||||||
cfg = json.load(open(path))
|
cfg = json.load(open(path))
|
||||||
if not any(w['wallet'].lower() == promote['wallet'].lower() for w in cfg['wallets']):
|
if not any(w['wallet'].lower() == promote['wallet'].lower() for w in cfg['wallets']):
|
||||||
w = {'wallet': promote['wallet'], 'name': promote['name'],
|
cfg['wallets'].append({'wallet': promote['wallet'], 'name': promote['name'],
|
||||||
'class': 'volume', 'floor': 25.0}
|
'class': 'volume', 'floor': 25.0})
|
||||||
cfg['wallets'].append(w)
|
|
||||||
json.dump(cfg, open(path, 'w'), indent=2)
|
json.dump(cfg, open(path, 'w'), indent=2)
|
||||||
print(f'{path}: added {promote_name}')
|
print(f'{path}: added {promote_name}')
|
||||||
else:
|
|
||||||
print(f'{path}: {promote_name} already there')
|
|
||||||
"
|
"
|
||||||
|
|
||||||
chown wwf:wwf live/copybot.paper.json config.live.json
|
chown wwf:wwf live/copybot.paper.json config.live.json
|
||||||
systemctl restart copybot-paper copybot-live
|
systemctl restart copybot-paper copybot-live
|
||||||
```
|
```
|
||||||
|
|
||||||
**降级**(live -> bench):
|
### 降级(live -> bench)
|
||||||
|
|
||||||
|
> 从跟单配置里删除,但留在 backtest.json 继续观察
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd /opt/winning-wallet-finder
|
cd /opt/winning-wallet-finder
|
||||||
@@ -139,22 +187,47 @@ for path in ['live/copybot.paper.json', 'config.live.json']:
|
|||||||
cfg = json.load(open(path))
|
cfg = json.load(open(path))
|
||||||
old = [w['name'] for w in cfg['wallets']]
|
old = [w['name'] for w in cfg['wallets']]
|
||||||
cfg['wallets'] = [w for w in cfg['wallets'] if w['name'] != demote_name]
|
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)
|
json.dump(cfg, open(path, 'w'), indent=2)
|
||||||
print(f'{path}: {old} -> {new}')
|
print(f'{path}: {old} -> {[w[\"name\"] for w in cfg[\"wallets\"]]}')
|
||||||
"
|
"
|
||||||
|
|
||||||
chown wwf:wwf live/copybot.paper.json config.live.json
|
chown wwf:wwf live/copybot.paper.json config.live.json
|
||||||
systemctl restart copybot-paper copybot-live
|
systemctl restart copybot-paper copybot-live
|
||||||
```
|
```
|
||||||
|
|
||||||
> 降级的钱包留在 backtest.json 里继续观察(不用删),下周 review 还能看到它的前瞻表现。
|
### 加新候选到 bench 观察区
|
||||||
|
|
||||||
### 第 6 步:钉死 floor_pin(如果需要)
|
> 从第 7 步发现的新钱包里选一个,加到 backtest.json 开始追踪前瞻表现
|
||||||
|
|
||||||
如果某个钱包的 p80 漂移太大(sync_floors.py 警告 `⚠ floor X->Y`),钉死它:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
cd /opt/winning-wallet-finder/live
|
||||||
|
|
||||||
|
python3 -c "
|
||||||
|
import json
|
||||||
|
new_wallet = '0xNEW_WALLET_ADDRESS' # 从第 7 步输出里选
|
||||||
|
new_name = 'NewWalletName' # 对应的名字
|
||||||
|
|
||||||
|
bt = json.load(open('backtest.json'))
|
||||||
|
if not any(w['wallet'].lower() == new_wallet.lower() for w in bt['wallets']):
|
||||||
|
bt['wallets'].append({'wallet': new_wallet, 'name': new_name, 'class': 'volume'})
|
||||||
|
json.dump(bt, open('backtest.json', 'w'), indent=2)
|
||||||
|
print(f'已添加 {new_name} 到 backtest.json,总共 {len(bt[\"wallets\"])} 个')
|
||||||
|
print('下周 review 就能看到它的前瞻盈亏了')
|
||||||
|
else:
|
||||||
|
print(f'{new_name} 已在 backtest.json 里')
|
||||||
|
"
|
||||||
|
```
|
||||||
|
|
||||||
|
> 加完后不需要重启 bot(backtest.json 只被 daily.sh 的 portfolio.py 读,不影响 bot 运行)。
|
||||||
|
> 但需要手动跑一次 `python3 portfolio.py --days 30` 确认新钱包有数据。
|
||||||
|
|
||||||
|
### 钉死 floor_pin
|
||||||
|
|
||||||
|
> 如果第 4 步显示某个钱包 floor 漂移太大
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /opt/winning-wallet-finder
|
||||||
|
|
||||||
python3 -c "
|
python3 -c "
|
||||||
import json
|
import json
|
||||||
pin_name = 'Kruto2027' # 要钉的钱包
|
pin_name = 'Kruto2027' # 要钉的钱包
|
||||||
@@ -174,6 +247,10 @@ chown wwf:wwf live/copybot.paper.json config.live.json
|
|||||||
systemctl restart copybot-paper copybot-live
|
systemctl restart copybot-paper copybot-live
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 更新 setup_wallets.py
|
||||||
|
|
||||||
|
> 每次换完钱包后,同步更新 setup_wallets.py 里的 OUR_WALLETS 数组,下次 sync-upstream 时不会丢
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 当前状态
|
## 当前状态
|
||||||
@@ -208,23 +285,4 @@ systemctl restart copybot-paper copybot-live
|
|||||||
|
|
||||||
## 1Panel 快速命令
|
## 1Panel 快速命令
|
||||||
|
|
||||||
**每周 review 一键跑回测 + 看排名**:
|
**每周 review 一键跑全部 7 步**:见 `每周五Review命令.md` 末尾的整段脚本,复制到 1Panel 保存为快速命令。
|
||||||
```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