Статус: СПЕКА НА РЕВЬЮ. Код в money-path (
copy-loop-mu.mjs, реальные деньги) НЕ написан/НЕ задеплоен. Деплой только после ОК Rick'а по процедуре §6. Предшествует: Chunk A8b47b17—GET /api/copy/market-tokens(резолв комплементарного токена). Уже в проде на Malaysia, поведение не меняет.
Лидер покупает YES @ p → мы (fade) покупаем NO @ (1-p). Шортов на Polymarket нет → fade = покупка комплементарного outcome-токена того же бинарного рынка.
0.20–0.80 по цене ЛИДЕРА. Лидер взял по 0.93 → пропускаем (комплемент по 0.07 — копеечный шум). Поле в CopyConfigModal (chunk C).t.outcome не in {yes,no} → fade-сделку пропускаем (лог skip fade: non-binary outcome). Мультиаутком и нестандартные метки — вне v1 (иначе realized-PnL на резолве посчитается неверно).copy_subscriptions.config уже хранится как свободный JSON и проходит насквозь в демон. Добавляем в deriveCfg(c):
direction = (c.direction === 'fade') ? 'fade' : 'copy' // дефолт copy → нулевая регрессия
fadeMin = c.fadeMin != null ? Number(c.fadeMin) : 0.20
fadeMax = c.fadeMax != null ? Number(c.fadeMax) : 0.80
Возврат deriveCfg дополняется { direction, fadeMin, fadeMax }. ⚠️ Существующее cfg.mode = режим АЛЛОКАЦИИ (fixed/percent/proportional), НЕ путать — поэтому новое поле зовётся direction, не mode.
copy-loop-mu.mjs)MARKET_TOKENS_URL = env, дефолт SUBS_URL.replace('/api/copy/active','/api/copy/market-tokens').
resolveComplement(conditionId, leaderAsset):
GET {MARKET_TOKENS_URL}?cond=<conditionId> (x-service-token)
tokens = data.tokens.map(String)
return tokens.find(id => id !== String(leaderAsset)) // комплемент = id != токена лидера
ERC1155-id комплемента не выводится арифметически → только lookup (chunk A эндпоинт).
handleTradeForUser (fade-инверсия перед decideCopy)Если cfg.direction === 'fade':
if (px < cfg.fadeMin || px > cfg.fadeMax) → skip (лог).const oc = String(t.outcome||'').toLowerCase(); if (oc!=='yes' && oc!=='no') → skip (non-binary).comp = await resolveComplement(t.conditionId, t.asset); if (!comp) → skip (лог fade: complement unresolved).tf = { ...t, asset: comp, price: +(1 - px).toFixed(4),
outcome: (oc === 'yes' ? 'No' : 'Yes') }
Далее decideCopy/sizing/caps/exec.buy идут по tf БЕЗ изменений (size считается от 1-px).direction: 'fade'leaderAsset: t.asset ← оригинальный токен лидера (нужен для выхода, см. 3.3/3.4)tokenId: comp, outcome: tf.outcome, leaderPrice: tf.price, marketId: t.conditionId
copy-позиции: direction:'copy', leaderAsset = тот же tokenId (инвариант).Сейчас: held = u.ledger.get(t.asset) — ключ по токену лидера. Для fade наш ключ = комплемент → не найдётся.
Правка: если прямого held нет, искать fade-позицию по оригиналу лидера:
held = u.ledger.get(t.asset)
|| [...u.ledger.values()].find(p => p.direction==='fade'
&& String(p.leaderAsset)===String(t.asset)
&& String(p.leader).toLowerCase()===leader
&& p.status==='open')
Закрываем exec.sellAll({ tokenID: held.tokenId, price: 1 - Number(t.price) }) (наш комплемент по инвертированной цене). Остальной close-флоу без изменений.
reconcileUser, money-критично) — ФИКС ЛОЖНОГО ВЫХОДАСтрока ~251-254 сейчас: if (!lh.has(tid)) leaderExit. Для fade tid=комплемент, лидер его НЕ держит → ложный выход каждый цикл → мгновенное закрытие fade-позиции. Фикс:
const leaderTok = p.leaderAsset ? String(p.leaderAsset) : tid; // fade сверяет ОРИГИНАЛ лидера
if (!lh.has(leaderTok)) { ch.leaderExit = true; ch.exitPrice = curPrice.get(tid) || 0; }
else ch.leaderStillIn = true;
tid (наш токен) по-прежнему используется для mark-PnL/баланса — меняется ТОЛЬКО ключ сверки наличия у лидера.
copy_positions (Rust + миграция)Персист нужен, чтобы fade-позиции пережили рестарт демона (иначе leaderAsset/direction теряются → reconcile снова ложно закроет).
ALTER TABLE copy_positions ADD COLUMN direction TEXT DEFAULT 'copy';
ALTER TABLE copy_positions ADD COLUMN leader_asset TEXT;copytrade.rs: positions_get SELECT + positions_upsert INSERT/UPDATE списки колонок дополнить direction, leader_asset (+ JSON-поля direction, leaderAsset). Старые строки: direction='copy', leader_asset=token_id (бэкфилл одним UPDATE).direction дефолт copy везде → нулевая регрессия для текущих 145 подписок/живого банка.p.outcome vs winningOutcome. Для fade outcome=комплемент (No/Yes) → корректно при v1 бинар-ограничении.1-px (а не noPrice из эндпоинта) — мгновенный снимок цены лидера, без рассинхрона с DB-ценой. (Можно свериться с эндпоинтовой как sanity, но не блокировать.)tokenId=комплемент — изоляция от copy-позиции на том же событии корректна.leader — общий для copy/fade, ок.scp malaysia:/root/poly-exec/copy-loop-mu.mjs ~/openclaw-backups/copy-loop-mu-<дата>.mjs._impl/daemon-ref/ → node --check copy-loop-mu.mjs.market-tokens уже в проде; пересборка нужна только под §4 колонки → cargo build --release на Malaysia → pm2 restart polymarket-screener (проверить restarts=0).DRY-RUN сначала — выкатить с LIVE off / тестовый FOLLOWER allow-list, прогнать 🟢 WOULD COPY (fade) логи на реальных трейдах лидера, проверить что комплемент-токен и 1-px верные.pm2 save), мониторить банк-логи (✅ COPIED/closed … pnl≈$X).WOULD COPY: BUY <N> "No" @ 0.400 на комплемент-токене; вне band 0.20-0.80 → skip; non-binary → skip.