Files
winning-wallet-finder/每周Bench-Review流程.md
T
gavindiaz 8656e528af 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)
2026-07-21 08:09:44 +08:00

7.3 KiB
Raw Blame History

每周 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 步:跑回测(在服务器上)

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 步:看每个钱包的前瞻表现

# 看回测里每个钱包的盈亏
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

# 当前跟的 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):

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):

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),钉死它:

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 一键跑回测 + 看排名

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}%')
"