feat: 持仓锁 — 同币种只跟一个 trader

【背景】之前 BTC 同时跟多个 trader (Jasonleo/麻吉/巨鲸), 信号互相干扰
- 群体投票算法复杂,实际投出分歧
- 单一币种单一信号源更清晰

【实现】
- position_locks 表 (symbol PK, trader, side, locked_at, reason, auto_locked)
- acquire_lock / release_lock / get_lock / list_locks / check_signal_lock
- 72h 自动过期 (按 _hours_since 算)
- 同 trader 同方向 → 续期
- 同方向不同 trader → 检查过期,过期后接管
- 不同方向 → 跳过 (允许一多一空并存)
- is_real_trader 黑名单: 'X聚合社区'/'某转发' 等不参与锁

【集成】process_signal.format_message 开头调 check_signal_lock:
- 锁被占用 → 推 '🔒 {reason}' (被锁,跳过)
- 锁允许 → 继续推信号

【测试】6 场景全过: 同 trader 续期/反向跳过/同向跳过/聚合放行/新币自动锁 
【当前锁】BTC long → 矿工Jasonleo, HYPE short → 麻吉大哥 (手动加锁)
This commit is contained in:
2026-07-23 22:09:16 +08:00
parent efbea5448c
commit 25812bb905
2 changed files with 181 additions and 7 deletions
+17 -7
View File
@@ -30,7 +30,7 @@ DEDUP_DB = Path.home() / ".hermes/trading/signal_dedup.db"
# Import signal tracker
sys.path.insert(0, str(SKILL_DIR / "scripts"))
from signal_tracker import format_comparison, record_signal as _tracker_record, record_confirmed, format_trader_rating, get_last_signal
from signal_tracker import format_comparison, record_signal as _tracker_record, record_confirmed, format_trader_rating, get_last_signal, check_signal_lock
# ─── 解析 ────────────────────────────────────────────────────────────────
@@ -241,6 +241,16 @@ def format_message(fields, rec, signal_type):
if 'error' in rec:
return f"⚠️ advisor错误: {rec['error']}"
symbol = fields.get('symbol', '?')
side_cn = fields.get('side', '做多')
side_en = fields.get('side_en', 'long' if side_cn == '做多' else 'short')
trader = fields.get('trader', '?')
# 锁检查: 同币种只跟一个 trader
allowed, lock_msg = check_signal_lock(symbol, trader, side_en)
if not allowed:
return f"🔒 {lock_msg}"
def _fmt(x, n=4):
"""格式化数字: 字符串保留原样, 数字 round 到 n 位."""
try:
@@ -248,11 +258,8 @@ def format_message(fields, rec, signal_type):
except (ValueError, TypeError):
return str(x)
symbol = fields.get('symbol', '?')
side_cn = fields.get('side', '做多')
emoji = '🟩' if fields.get('side_en') == 'long' else '🟥'
leverage = fields.get('leverage', '10')
trader = fields.get('trader', '?')
size = fields.get('size', '?')
unit = fields.get('unit', '') # 仓位单位: BTC/USDT/张
value = fields.get('value', '?')
@@ -297,10 +304,13 @@ def format_message(fields, rec, signal_type):
our_position = None
our_advice = ""
try:
from okx_position_advisor import get_positions
positions = get_positions(symbol=symbol)
import ccxt
from okx_position_advisor import load_credentials, create_exchange
creds = load_credentials()
ex = create_exchange(creds)
positions = ex.fetch_positions()
for p in positions:
if p.get('symbol') == f"{symbol}/USDT:USDT" or p.get('symbol') == symbol:
if symbol in p.get('symbol', '') and p.get('contracts', 0) != 0:
our_position = p
break
if our_position and current_size > 0: